skip to content

Platform Types from Java

Unannotated Java values arrive as platform types, written T!, which the compiler lets you use as either nullable or not — and an NPE is the price of guessing wrong. The practical habit is pinning them to an explicit T or T? the moment they enter Kotlin code.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

4

What is a Kotlin platform type, how is it written, and why does calling Java code sometimes risk a NullPointerException even in Kotlin?

level: juniorimportance: must knowfreq 70%

answer

  1. Unannotated Java value -> platform type
  2. Denoted with trailing ! (String!)
  3. Compiler skips null checks for it
  4. NPE surfaces at use site, not read site
  5. Pin to T or T? at the boundary

basics

~20 s

When 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 s

A 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
kotlin
// 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 crash

go deeper

for a junior

Knows Java values without annotations become platform types and that this can cause a runtime NPE.

for a middle

Explains the T! notation, that null checks are relaxed, and pins values to T or T? at the boundary.

for a senior

Discusses where the NPE surfaces (use site), intrinsic null checks on non-null assignment, and chooses nullability from the real Java contract.

for a principal

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

context

open as a page

When a Java method returns an unannotated value, how do you choose between pinning it to a non-null T versus a nullable T? in Kotlin, and what does each choice generate at the bytecode level?

level: middleimportance: must knowfreq 55%

basics

~20 s

Look at what the Java method really does. If it can return null, store it as T? and use safe calls. If it never returns null, store it as T; Kotlin then adds a check that crashes immediately at the boundary if you were wrong.

open as a page

How do platform types behave with Java generics and collections — e.g. a Java method returning List<String> — and what subtle NPE traps appear when iterating or destructuring such results in Kotlin?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Both the collection and the things inside it become platform types. So a Java List<String> can be null, and any element can be null too, even though Kotlin lets you treat them as non-null. Iterating may NPE on a null element.

open as a page

When a Kotlin class implements a Java interface with unannotated method parameters/returns, what nullability must the override declare, and how can a team make the compiler stricter about platform types?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

When you override an unannotated Java method, Kotlin lets you choose whether the types are nullable or not, but the choice becomes a hard contract for your callers. Teams can also turn on a compiler flag to warn whenever a platform type is misused.

open as a page