What is a Kotlin 'platform type' and why can a value coming from Java code throw a NullPointerException even though your Kotlin code looks fully null-safe?
answer
- String! = nullability unknown
- Java has no null info -> platform type
- NPE at use, not at assignment-compile
- Fix: explicit ? or requireNotNull
- @Nullable/@NotNull restore checks
basics
~20 sWhen Java returns a value, Kotlin doesn't know if it can be null, so it trusts you. If you treat it as non-null and it actually is null, you only crash later when you use it.
solid answer
~40 sJava types carry no nullability information, so when a Java method returns something, Kotlin assigns it a 'platform type', shown in the IDE as String! (with an exclamation mark). A platform type relaxes null checks: you may treat it as either String or String?. If you assign it to a non-null Kotlin type and the actual value is null, the compiler does not complain at the assignment; instead it inserts an implicit null-check (intrinsic) that throws NullPointerException at the point of use/assignment. This is the boundary 'hole': Kotlin's normally airtight null safety is suspended for un-annotated Java values. The fix is to explicitly choose a nullable type (val x: String? = javaCall()) or assert/guard with requireNotNull / checkNotNull, so the failure is intentional and located where you expect it.
code
kotlin · 4 lines// Java: String findName() { return null; }
val bad: String = repo.findName() // compiles; NPE at runtime
val safe: String? = repo.findName() // explicit nullable
val name = requireNotNull(repo.findName()) { "name missing" } // intentional guardgo deeper
Knows Java values become platform types and that a hidden NPE can result; can fix it with ? or a guard.
Explains the String! notation, the both-assignable rule, and where the intrinsic null check fires.
Discusses honoring @Nullable/@NotNull, choosing explicit boundary types, and why the crash location differs from the source of null.
Frames it as a deliberate convenience/safety trade-off and sets team conventions (annotate Java, type the boundary, fail fast with requireNotNull).
## The problem: Java has no nullability in its type system In Kotlin, every type is either non-null (`String`) or nullable (`String?`), and the compiler enforces the difference. Java has no such distinction — a `String` in Java may freely hold `null`. So when Kotlin calls Java code, it cannot know whether a returned reference is nullable. ## Platform types Rather than pessimistically forcing every Java value to be nullable (annoying) or optimistically forcing it non-null (unsafe), Kotlin introduces a **platform type**, written `String!` (you cannot write this notation yourself; it appears in IDE hints and error messages). A platform type means *"nullability unknown — you decide."* For a platform type you may: - assign it to a **non-null** Kotlin type (`val s: String = javaGetName()`), or - assign it to a **nullable** Kotlin type (`val s: String? = javaGetName()`). The compiler permits **both** with no warning. That flexibility is exactly the **null-safety hole**. ## Where the NPE actually happens If you choose the non-null path and the value is really `null`, you do **not** get an error at compile time. Kotlin inserts an **intrinsic null check** (`Intrinsics.checkNotNull` / `checkNotNullExpressionValue`) at the assignment or first dereference, which throws `NullPointerException` — possibly far from the real source of the bad `null`. ```kotlin // Java: class Repo { String findName() { return null; } } val name: String = repo.findName() // platform type String! -> String println(name.length) // NPE thrown here (or at the assignment) ``` The code *looks* null-safe — there is no `!!` anywhere — yet it crashes. ## Guarding the boundary - **Declare it nullable** and handle it: `val name: String? = repo.findName()` then use `?.`, `?:`, `let`, etc. - **Assert intent explicitly** when you truly require non-null: `val name = requireNotNull(repo.findName()) { "name missing" }` or `checkNotNull(...)`. This throws an `IllegalArgumentException`/`IllegalStateException` with a message at the boundary, not a mystery NPE downstream. - **Annotate the Java side** with `@Nullable` / `@NotNull` (JetBrains, JSR-305, Android, Jakarta). Kotlin honors these and turns `String!` into a real `String?` or `String`, restoring compile-time checking. ## Key takeaway Platform types trade compile-time safety for interop convenience. The discipline is: **assign every Java value to an explicit Kotlin nullable type at the boundary**, then narrow deliberately.
- How does the IDE display a platform type, and why can't you write it in source?It shows as `String!` in inferred-type hints and errors. The `!` syntax is compiler/IDE-only because it represents 'either nullable or not'; source code must commit to `String` or `String?`.
- If you never give the Java value an explicit type, what type does Kotlin pick?It keeps the platform type via inference (e.g. `val x = javaCall()` gives `x` a platform type), so the relaxed checking propagates — better to annotate the variable explicitly.
A platform type is like an unlabeled jar from someone else's kitchen: Kotlin lets you assume it's full, but you only find out it's empty when you reach in.
saying these in an interview costs you the question
- Claiming Kotlin makes all Java values nullable automatically
- Saying the NPE happens at compile time
- Thinking `!!` is required for the crash to occur
- Believing platform types can be written by hand as `String!`
- Assuming the IDE annotation hints have no runtime effect