When you call a Java method from Kotlin, how do @Nullable and @NotNull annotations on that Java method change the type Kotlin sees?
answer
- No annotation -> platform type T! (relaxed)
- @NotNull -> T (non-null)
- @Nullable -> T? (forced handling)
- Annotation removes the platform type
- JetBrains / JSR-305 / Android / JSpecify recognized
basics
~20 sIf the Java method is marked @NotNull, Kotlin treats the result as a normal non-null type. If it is @Nullable, Kotlin treats it as nullable and makes you check for null. Without an annotation, Kotlin can't tell.
solid answer
~40 sJava types have no null information, so by default Kotlin imports them as platform types (written T!), which skip null checks. When the Java declaration carries a recognized nullability annotation, Kotlin removes the platform type and substitutes a real Kotlin type: @NotNull becomes the non-null T, and @Nullable becomes the nullable T?. So a @NotNull String returns Kotlin's String (no ?. needed and assignable to a non-null variable), while a @Nullable String returns String? and the compiler forces you to handle null via ?., ?:, or a smart cast. Recognized families include JetBrains org.jetbrains.annotations, JSR-305 javax.annotation, Android androidx.annotation, Checker Framework, Eclipse, and others. This turns silent Java NPEs into compile-time guarantees at the interop boundary.
code
kotlin · 9 lines// Java side:
// public @NotNull String safe() { return "x"; }
// public @Nullable String maybe() { return null; }
fun use(j: Demo) {
val a: String = j.safe() // @NotNull -> String, no check
val b: String? = j.maybe() // @Nullable -> String?
println(b?.length ?: 0) // compiler requires null handling
}go deeper
Knows @NotNull -> String and @Nullable -> String?, and that you must null-check the nullable one.
Explains platform types as the un-annotated default and that the annotation removes them; names a couple of annotation families.
Discusses that @NotNull is a trusted contract not a runtime guarantee, lists multiple recognized families, and the runtime-NPE consequences when the contract is violated.
Frames it as a policy decision for library interop boundaries, weighs annotating Java sources vs. JSpecify module-level defaults vs. wrapping at the boundary, and reasons about intrinsic null checks and binary-compatibility implications.
## The problem: Java doesn't track nullability In Java, any reference (`String`, `List<T>`, etc.) can be `null`, and the type system records nothing about whether a given value may be null. When Kotlin calls Java, it therefore can't know if a returned `String` is safe. ## Platform types (the default) For an *un-annotated* Java declaration, Kotlin assigns a **platform type**, written `String!` (you can't write `!` yourself; it's compiler-internal). Platform types **relax** null checking: you may use the value as either `String` or `String?`, and if you treat a secretly-null value as non-null you get a runtime `NullPointerException` exactly where you'd get one in Java. This is a pragmatic escape hatch, not a guarantee. ## What nullability annotations do When the Java declaration is annotated with a **recognized** nullability annotation, Kotlin *removes the platform type* and imports a precise Kotlin type: - `@NotNull String foo()` -> Kotlin sees `String` (non-null). Assignable to a non-null `val s: String`, no null check needed. - `@Nullable String foo()` -> Kotlin sees `String?` (nullable). The compiler **forces** safe handling (`?.`, `?:`, `!!`, or a null check + smart cast). ```kotlin // Java: @NotNull String name(); @Nullable String nick(); val n: String = javaObj.name() // OK: @NotNull -> String val k: String? = javaObj.nick() // @Nullable -> String? val bad: String = javaObj.nick() // COMPILE ERROR: String? is not String println(javaObj.nick().length) // COMPILE ERROR: must use ?. println(javaObj.nick()?.length) // OK ``` ## Recognized annotation families Kotlin understands many widely-used annotations, including: - **JetBrains**: `org.jetbrains.annotations.Nullable` / `NotNull` - **JSR-305**: `javax.annotation.Nullable` / `Nonnull` - **Android**: `androidx.annotation.Nullable` / `NonNull` - **JSpecify**: `org.jspecify.annotations.Nullable` / `NonNull` - Checker Framework, Eclipse JDT, Lombok, FindBugs/SpotBugs, RxJava, and others. It matches by the **simple name** (`Nullable`/`NotNull`/`NonNull`/`Nonnull`/`CheckForNull`), so several vendor packages are honored. ## Why it matters Annotations convert an interop boundary that would silently throw NPEs into one where the Kotlin compiler enforces null handling — pushing failures from runtime to compile time.
- If a Java method has no nullability annotation, what type does Kotlin assign and what is the risk?A platform type (T!). Kotlin lets you use it as non-null or nullable; if it's actually null and you used it as non-null, you get a runtime NPE at that point — null checking is relaxed, not enforced.
- Does adding @NotNull guarantee the value can never be null at runtime?No. It's a contract Kotlin trusts at compile time. If the Java code lies and returns null, you can still get a runtime NPE; Kotlin may insert an intrinsic null check that throws.
An un-annotated Java value is a parcel with no label — Kotlin lets it through but you carry the risk; the annotation is the 'fragile / contains-null' sticker that makes the compiler insist you handle it.
saying these in an interview costs you the question
- Saying Java types always import as nullable T? by default
- Thinking @NotNull provides a runtime guarantee rather than a compile-time contract
- Confusing platform types with nullable types
- Believing Kotlin only recognizes JetBrains annotations
- Claiming the annotations affect Java code's own behavior