skip to content

Platform Types & Nullability

A Java value with no nullability annotation arrives as a platform type, where the compiler stops checking and trusts you. Annotations on the Java side restore the guarantee; without them, an NPE surfaces at the dereference rather than the boundary.

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

questions

5

What is a Kotlin platform type, why does it show up as String! in the IDE, and what happens to null checking when you use one?

level: juniorimportance: must knowfreq 72%

answer

  1. Java value -> platform type
  2. Shown as String! (tooling only, not writable)
  3. Null checks relaxed at the boundary
  4. NPE happens at use, not at crossing
  5. Pin contract with explicit String? type

basics

~20 s

A platform type is a value coming from Java whose nullability Kotlin can't know. The compiler relaxes null checks for it. If you treat it as non-null but it's actually null, you get a NullPointerException at runtime.

solid answer

~40 s

Java has no built-in nullability info, so when a Java method returns a value Kotlin assigns it a platform type, written String! in IDE tooling (the ! is not real syntax you can type). For platform types the compiler suspends strict null-safety: you may assign the value to a non-null String or a nullable String?, and you may call members without ?. The trade-off is that the safety net is gone — if the Java value is actually null and you used it as non-null, you get a NullPointerException where you dereference it, not at the boundary. Best practice: decide the contract at the boundary by explicitly typing the variable (val s: String? = javaObj.getName()) so the compiler enforces a null check, rather than letting the platform type silently flow as non-null.

code

kotlin · 12 lines
kotlin
// Java side:
//   public class User { public String getName() { return null; } }

val user = User()
val name = user.name      // type is String! (platform type)

// Treated as non-null -> NPE at runtime when actually null:
// val len = name.length

// Pin it explicitly to force compiler null-safety:
val safeName: String? = user.name
println(safeName?.length)  // null, no crash

go deeper

for a junior

Knows a platform type comes from Java and that mishandling it can throw an NPE.

for a middle

Explains the relaxed-check trade-off and pins the contract with an explicit nullable type at the boundary.

for a senior

Discusses where the NPE actually surfaces and why Kotlin chose this pragmatic compromise over forcing nullable everywhere.

for a principal

Frames boundary discipline as an API-design and team-convention concern, e.g. wrapping noisy Java APIs in null-safe Kotlin facades.

## What a platform type is Kotlin's type system tracks nullability: `String` can never be null, `String?` can. **Java** has no such distinction at the language level — any reference can be null. When a value crosses from Java into Kotlin, the compiler often cannot prove whether null is possible, so it assigns a **platform type**. ## The `String!` notation In IDE hovers, error messages, and docs the platform type is written `String!`, meaning "`String` or `String?`, the compiler doesn't know". **`!` is not syntax you can write** — it only appears in tooling. You can't declare `val x: String!`. ## Relaxed null checks For a platform type the compiler **lifts** its usual rules: - You may assign it to a **non-null** type (`val s: String = javaObj.name`) — no error. - You may assign it to a **nullable** type (`val s: String? = javaObj.name`). - You may **dereference it directly** (`javaObj.name.length`) with no `?.` or `!!`. ## Where the NPE happens If the Java value is actually `null` and you treated the platform type as non-null, the failure is a **NullPointerException at the point of use** (the dereference), not at the boundary crossing. This makes platform-type bugs feel "late" and far from the cause. ```kotlin // Java: String getName() { return null; } val name = javaObj.name // platform type String! println(name.length) // NPE here at runtime if name is null // Safer: pin the contract at the boundary val safe: String? = javaObj.name // now compiler forces a null check println(safe?.length) // prints null, no crash ``` ## Takeaway Platform types are a **pragmatic compromise**: full strictness would make every Java call require `!!` or `?.`. Instead Kotlin trusts you at the boundary and asks you to **assign an explicit nullable/non-null type** when you know the real contract. The keywords involved are the safe-call `?.`, the not-null assertion `!!`, and explicit type annotations.

  • Can you write the type String! yourself in Kotlin source?
    No. The ! suffix is only a tooling/diagnostic notation. You declare String or String?; the platform type exists only implicitly for unannotated Java values.
  • Why doesn't Kotlin just treat every Java return as nullable?
    That would force ?. or !! on essentially every Java call, making interop unbearably noisy. Platform types are the pragmatic middle ground.

It's an unlabeled package from a vendor who never marks 'fragile' — Kotlin lets you carry it however you like, but drops it (NPE) if it really was fragile.

saying these in an interview costs you the question

  • Claiming Kotlin throws at the boundary when the Java value is null (it throws at use)
  • Saying you can declare a variable of type String!
  • Thinking platform types are always non-null
  • Believing platform types disable null safety for the whole program rather than just that value

context

open as a page

How do @Nullable / @NotNull annotations on Java code change how Kotlin sees those values, and which annotation libraries does Kotlin recognize?

level: middleimportance: must knowfreq 60%

basics

~20 s

If the Java code is annotated with @Nullable or @NotNull, Kotlin stops treating the value as a loose platform type and treats it as String? or String. Kotlin recognizes several common annotation libraries like JetBrains, JSR-305, and AndroidX.

open as a page

You consume a large unannotated Java library whose methods all return platform types. How do you design the Kotlin side so platform-type NPEs don't leak deep into your code?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Wrap the Java library behind a thin Kotlin layer. At that boundary, give every value an explicit nullable or non-null type and validate it, so the rest of your code only ever sees proper Kotlin types and never raw platform types.

open as a page

What is JSpecify, how does it improve Java-to-Kotlin nullability over older annotations, and how does Kotlin's strict mode treat JSpecify-annotated code?

level: seniorimportance: should knowfreq 38%

basics

~10 s

JSpecify is a standard, vendor-neutral set of nullability annotations for Java. Kotlin understands it, including module-wide defaults and generic type-argument nullability, and can treat any mismatch as a compile error in strict mode.

open as a page

When you override a Java method that returns a platform type in Kotlin, what nullability must your override declare, and how do platform types interact with generic type parameters?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

When you override a Java method in Kotlin, you must pick a concrete nullability — nullable or non-null — for the return and parameters; you can't leave it as a platform type. With generics, Kotlin substitutes the platform nullability into the type argument the same way.

open as a page