Introduction

When Kotlin code with lateinit var properties is called from Java, accessing an uninitialized lateinit property throws NullPointerException rather than the more descriptive UninitializedPropertyAccessException. This happens because Java sees Kotlin properties as plain fields and doesn't have access to Kotlin's initialization checking.

This issue is common in mixed Kotlin/Java codebases, especially during incremental Kotlin migration or when Java code calls Kotlin classes.

Symptoms

  • Java code calling Kotlin throws NullPointerException on a lateinit property
  • Same Kotlin code called from Kotlin throws UninitializedPropertyAccessException
  • Stack trace points to a Kotlin class but the exception type is NPE

Common Causes

  • Java accesses a Kotlin lateinit var before the Kotlin initialization lifecycle method runs
  • Kotlin lateinit property is not initialized in the constructor (by design)
  • Dependency injection fails to inject the lateinit property, leaving it null

Step-by-Step Fix

  1. 1.Check initialization before access from Java: Use Kotlin's ::property.isInitialized.
  2. 2.```kotlin
  3. 3.// Kotlin class:
  4. 4.class MyService {
  5. 5.lateinit var repository: DataRepository

fun isInitialized(): Boolean = ::repository.isInitialized }

// Java code: MyService service = new MyService(); if (service.isInitialized()) { service.getRepository().getData(); } else { throw new IllegalStateException("Service not initialized"); } ```

  1. 1.Use nullable type with null check instead of lateinit: Safer for Java interop.
  2. 2.```kotlin
  3. 3.// Instead of lateinit:
  4. 4.// lateinit var repository: DataRepository

// Use nullable with default: var repository: DataRepository? = null

fun getData(): List<String> { val repo = repository ?: throw IllegalStateException("Repository not set") return repo.fetchAll() } ```

  1. 1.Add @JvmField with initialization guard: Make the field accessible with a clear error.
  2. 2.```kotlin
  3. 3.class MyService @Inject constructor(
  4. 4.private val repository: DataRepository // Constructor injection, never null
  5. 5.) {
  6. 6.fun getData() = repository.fetchAll()
  7. 7.}
  8. 8.`

Prevention

  • Prefer constructor injection over lateinit for properties needed by Java callers
  • Use Kotlin's isInitialized check before accessing lateinit from Java
  • Add @NotNull annotations to help Java understand nullability contracts
  • Write integration tests that call Kotlin code from Java to catch interop issues