When Kotlin calls a Java API that uses wildcards like `List<? extends Number>`, how does Kotlin see that type, and what pitfalls arise around platform types and variance?
answer
- ? extends -> out, ? super -> in, ? -> *
- Java types -> platform types with `!`
- platform type = relaxed null checks -> possible NPE
- (Mutable)List flexibility can surprise
- annotate Java @Nullable/@NotNull + -Xjsr305=strict
basics
~20 sKotlin translates Java's ? extends Number into out Number and ? super Number into in Number. Java types without nullability info become platform types (Number!), so you must be careful about nulls and what you can read or write.
solid answer
~40 sCrossing from Java to Kotlin, the compiler maps wildcards back to projections: `List<? extends Number>` becomes `(Mutable)List<out Number>!`, `List<? super Number>` becomes `(Mutable)List<in Number>!`, and `List<?>` becomes `(Mutable)List<*>!`. The trailing `!` marks a **platform type** — Java carried no nullability metadata, so the Kotlin compiler relaxes null-checks, and a careless dereference can throw `NullPointerException` at runtime. Variance is preserved: you still can't `add` to an `out`-projected list nor read a useful type from an `in`-projected one. Pitfalls: (1) platform types defeat null-safety, so annotate Java with `@Nullable`/`@NotNull` or handle defensively; (2) `MutableList` vs `List` flexibility (`(Mutable)List`) means Kotlin can treat the same Java `List` as read-only or mutable, which can surprise callers.
code
kotlin · 6 lines// Java: static <T> T head(java.util.List<? extends T> xs) { ... }
val nums: List<Int> = listOf(1, 2)
val h = JavaUtil.head(nums) // h: Int! (platform type)
// Safe handling:
val safe: Int = h ?: 0 // treat platform value as nullablego deeper
Knows Java types can be null and Kotlin maps wildcards to in/out.
Explains the wildcard->projection table and basic platform-type nullability risk.
Details platform-type NPE hazards, (Mutable)List flexibility, and -Xjsr305=strict mitigation.
Designs an interop strategy (annotations, strict flags, wrapper layers) to remove platform-type risk across a large Java/Kotlin codebase.
## Wildcard -> projection translation (Java to Kotlin) When Kotlin consumes a Java signature, wildcards become use-site projections: | Java | Kotlin sees | |------|-------------| | `List<? extends Number>` | `(Mutable)List<out Number>!` | | `List<? super Number>` | `(Mutable)List<in Number>!` | | `List<?>` | `(Mutable)List<*>!` | | raw `List` | `(Mutable)List<*>!` | Variance semantics are preserved: an `out`-projected list rejects writes; an `in`-projected list yields only `Any?` on reads. ## Platform types — the `!` suffix A **platform type** (written `Number!`, `List<Number>!` — you can't write `!` yourself) is a type that came from Java with **no nullability information**. Kotlin relaxes its usual null checks for it: you may treat it as nullable or non-null, and the compiler won't force a `?` check. ```kotlin // Java: Number firstNumber(List<? extends Number> xs) val n = Java.firstNumber(list) // n: Number! — could be null at runtime! val x: Int = n.toInt() // no compile error, but NPE if n was null ``` The danger: code compiles cleanly but throws **`NullPointerException`** at the dereference if Java returned `null`. ## `(Mutable)List` flexibility Kotlin maps Java's `java.util.List` to a *flexible* type `(Mutable)List<...>!`, meaning it can be used where either the read-only `kotlin.collections.List` or `MutableList` is expected. This lets you call mutating methods, but if you assign to a read-only `List` and the underlying Java collection is immutable, a mutation attempt throws `UnsupportedOperationException`. ## Pitfalls and mitigations - **Null-safety hole**: add JSR-305 / `org.jetbrains.annotations` `@Nullable`/`@NotNull` (or `javax.annotation`) to the Java side so Kotlin infers real nullable/non-null types instead of platform types. The `-Xjsr305=strict` flag makes the compiler honor them strictly. - **Accidental mutation**: be explicit — declare the Kotlin variable as `List<...>` (read-only) when you don't intend to mutate. - **Over-reading from `in` projections**: remember an `in Number` list reads back only `Any?`. - **Star-projection from raw types**: a Java raw `List` arrives as `List<*>!`, so you can read `Any?` but not add. ## Summary Variance round-trips cleanly (wildcards <-> projections), but the *real* interop hazard is platform-type nullability, not variance. Annotate Java, prefer explicit read-only types, and treat platform values as nullable until proven otherwise.
- Why doesn't Kotlin just make every Java return type nullable?It would force `!!`/`?.` everywhere, making Java interop painfully verbose; platform types are a pragmatic compromise that trusts the developer.
- How can you eliminate platform types from a Java dependency?Add `@NotNull`/`@Nullable` (JetBrains or JSR-305) annotations on the Java side and compile Kotlin with `-Xjsr305=strict`.
- What does `List<? super Number>` let you read in Kotlin?Only `Any?` — it is `in`-projected (contravariant), so the element type is unknown on the read side.
saying these in an interview costs you the question
- Claiming Java types come into Kotlin as non-null by default
- Saying platform types cause compile errors (they cause runtime NPEs)
- Forgetting variance is preserved across the boundary
- Believing you can write to an `out`-projected Java list
- Ignoring the read-only vs MutableList flexibility hazard