What is the runtime difference between `null as String` and `null as String?`, and how do platform types from Java affect cast safety?
answer
- null as String -> CCE; null as String? -> null
- as checks nullability, not just class
- Java values are platform types T! (nullability unknown)
- platform-to-non-null inserts hidden NPE assertion
- annotate Java (JSpecify) or declare Kotlin side nullable
basics
~20 sCasting null to a type that forbids null fails, but casting null to a nullable type is fine. Values coming from Java have an unknown null status, so a careless cast can crash later when a hidden null appears.
solid answer
~40 snull as String throws ClassCastException because the target type is non-null and null violates it; null as String? succeeds and yields null since the target permits null. This shows as enforces nullability, not just class identity. Java values arrive as platform types (written T! in diagnostics) where the compiler relaxes null checks. If you cast or assign a Java value to a non-null Kotlin type and it is actually null, you get a NullPointerException at the implicit non-null assertion, not at an obvious cast. Defenses: use as? for an unknown class, annotate Java with @Nullable/@NonNull (or JSpecify) so types are precise, and prefer explicit nullable Kotlin types at Java boundaries so the compiler forces a null check rather than trusting the platform type.
code
kotlin · 10 lines// Nullability is part of the cast target
fun demo() {
val ok: String? = null as String? // null, no throw
try {
val bad = null as String // ClassCastException
println(bad)
} catch (e: ClassCastException) {
println("non-null target rejects null")
}
}go deeper
Knows null can't be cast to a non-null type but can to a nullable one.
Explains that as enforces nullability and uses String? targets to allow null.
Articulates platform types, the hidden non-null assertion, and the deferred NPE risk at Java boundaries.
Drives annotation strategy (JSpecify/-Xjsr305=strict) and boundary conventions to eliminate platform-type cast hazards across the codebase.
## Casts enforce nullability, not just class Kotlin's `as` checks both the **class** and the **nullability** of the target type: ```kotlin val a = null as String? // ok -> null (target allows null) // val b = null as String // throws ClassCastException (target is non-null) ``` The second case fails not because of class identity (null has no class) but because the **non-null** target `String` cannot hold `null`. So `as` to a non-null type doubles as a non-null assertion. ## Platform types from Java When Kotlin calls Java code lacking nullability annotations, the result has a **platform type**, shown in error messages and IDEs as `String!`. A platform type means "nullability unknown" — the compiler lets you treat it as either `String` or `String?` **without a warning**, trusting you. ```kotlin // Java: String getName() { return possiblyNull(); } val name: String = javaObj.name // no warning; NPE here if actually null val safe: String? = javaObj.name // explicit, forces null handling ``` The danger with casts: assigning or casting a platform value into a non-null Kotlin type inserts a hidden **non-null assertion** (an `Intrinsics.checkNotNull` call). If the Java value is null, you get a `NullPointerException` at that assertion — which may be far from the visible cast, making the bug hard to locate. ## Combining with `as?` When the **class** is also uncertain (e.g., reading from a Java `Map<String, Object>` or `Bundle`), use `as?` so both a wrong class and a null degrade to `null`: ```kotlin val port = (config["port"] as? Int) ?: 8080 ``` ## Making boundaries precise Best practices at Java seams: - Annotate Java APIs with `@Nullable`/`@NonNull` (JetBrains, JSR-305, or **JSpecify**); Kotlin then produces proper `String?`/`String` types instead of platform types. - At the boundary, **declare the Kotlin side as nullable** (`String?`) so the compiler forces an explicit check rather than silently trusting a `String!`. - Avoid bare `as` on platform values; prefer `as?` plus Elvis, or validate then narrow. - `-Xjsr305=strict` (or strict JSpecify mode) turns annotated platform types into hard nullability, surfacing problems at compile time.
- Why does `null as String` throw but `null as Any?` not?`String` is a non-null target so null violates it; `Any?` is nullable, so null is a valid member and the cast succeeds.
- Where does the NPE actually occur when a null Java platform value lands in a non-null Kotlin type?At the implicit non-null assertion (an Intrinsics.checkNotNull) the compiler inserts at the assignment/cast, not necessarily at the Java call site.
saying these in an interview costs you the question
- Saying null can be cast to any type including non-null ones
- Treating platform types as always non-null and casting blindly
- Confusing the resulting NPE with a ClassCastException
- Not knowing annotations/JSpecify remove platform types
- Believing as ignores nullability and only checks the class