What is a Kotlin platform type, how is it written, and why does calling Java code sometimes risk a NullPointerException even in Kotlin?
answer
- Unannotated Java value -> platform type
- Denoted with trailing ! (String!)
- Compiler skips null checks for it
- NPE surfaces at use site, not read site
- Pin to T or T? at the boundary
basics
~20 sWhen Kotlin calls Java code that doesn't say whether a value can be null, Kotlin can't tell either. It treats the value as a 'platform type' and skips its null checks, so a null can slip through and crash at runtime.
solid answer
~40 sA platform type comes from a Java declaration that has no nullability annotation (no @Nullable/@NotNull). The Kotlin compiler can't prove whether the value is nullable, so it relaxes null checks for it. In compiler messages it's written with a single trailing exclamation mark, e.g. String!, meaning 'could be String or String?'. You can assign it to either a non-null String or a nullable String? without a warning. The danger: if you pin it to non-null and the Java value is actually null, you get a NullPointerException — but it surfaces at the use site, not as a compile error. The fix is to decide nullability yourself at the boundary: declare the variable as String or String? explicitly based on what the Java API really guarantees.
code
kotlin · 10 lines// Java side (no annotations):
// class Box { public String getLabel() { return null; } }
val box = Box()
val risky: String = box.label // box.label is String! -> compiles
println(risky.length) // NPE at runtime
val safe: String? = box.label // pin to nullable at the boundary
println(safe?.length) // null, no crashgo deeper
Knows Java values without annotations become platform types and that this can cause a runtime NPE.
Explains the T! notation, that null checks are relaxed, and pins values to T or T? at the boundary.
Discusses where the NPE surfaces (use site), intrinsic null checks on non-null assignment, and chooses nullability from the real Java contract.
Frames platform types as a deliberate ergonomics-vs-soundness trade-off and sets team conventions for interop boundaries.
## What a platform type is Kotlin's type system tracks nullability: `String` can never be null, `String?` can. Java has no such distinction — any reference can be null. When Kotlin sees a Java value with **no nullability annotation**, it has no information to decide which Kotlin type it is. Rather than forcing a guess, the compiler assigns a **platform type**. A platform type means: *"this might be non-null or nullable; I'm trusting you, the developer, to know."* For that value Kotlin **relaxes (skips) its usual compile-time null checks**. ## How it's written You can't write a platform type yourself in source code. The compiler **denotes** it with a trailing exclamation mark in error messages and IDE hints: - `String!` means "`String` or `String?`" - `List<String!>!` means the list and its elements are both platform types ## Why the NPE risk Because the check is relaxed, you may freely assign a platform type to a **non-null** Kotlin type: ```kotlin // Java: class Box { public String getLabel() { return null; } } val box = Box() val label: String = box.label // compiles fine — getLabel() is String! println(label.length) // NullPointerException at runtime if it was null ``` The compiler raised no error: it trusted you. The NPE appears at the **use site** (here, `.length`), not where you read the value. This is the core hazard: the safety net Kotlin normally gives you is off for that value. ## How to make it safe — pin it at the call site Decide the nullability yourself the moment the value enters Kotlin: ```kotlin val label: String? = box.label // honest: treat it as possibly null val len = label?.length // safe call, no NPE ``` If you *know* the Java API never returns null, you may pin to non-null `String` — Kotlin inserts an **intrinsic null check** (e.g. `checkNotNull`/`Intrinsics.checkNotNullExpressionValue`) so you fail fast with a clear message right at the boundary rather than mysteriously later. ## Key takeaways - Platform type = unannotated Java value; denoted `T!`. - Compile-time null checks are **skipped** for it. - Risk: a hidden null assigned to a non-null Kotlin type → runtime NPE at use. - Fix: explicitly type the variable `T` or `T?` based on the real Java contract.
- Can you declare a platform type explicitly in Kotlin source, like 'val x: String! = ...'?No. The `T!` notation only appears in compiler/IDE messages. In source you must write `T` or `T?`; the platform type exists only at the Java interop boundary.
- Where does the NPE actually occur in the risky example?At the use site `risky.length`, when dereferencing the value, not at the assignment `val risky = box.label`.
It's an unlabeled jar from the Java pantry: Kotlin won't stop you eating it, but doesn't promise it isn't empty.
saying these in an interview costs you the question
- Claiming Kotlin guarantees no NPE when calling Java
- Saying you write 'String!' in your own source code
- Thinking the compiler errors at assignment, not realizing it silently allows it
- Confusing platform types with the !! not-null assertion operator