When a Java method returns java.util.List<String>, what Kotlin type does it appear as, and what are the safety implications of that mapping?
answer
- Java List -> MutableList<String!> by default
- Mutable because Java has no read-only split
- add may throw UnsupportedOperationException
- Copy defensively: toList()/toMutableList()
- @Unmodifiable / nullability annotations tighten inference
basics
~20 sIt usually shows up as a MutableList, so Kotlin lets you add and remove from it. But Java may not expect that list to change, so calling add could throw or corrupt the caller's data.
solid answer
~40 sKotlin maps java.util.List to a single underlying type but presents it to your Kotlin code as MutableList<String!> by default — both because Java has no read-only/mutable split and because the element type is a platform type (String!, unknown nullability). So you can call add/remove on it directly, with no compile-time barrier. The risk: the Java side may have returned an unmodifiable list (Collections.unmodifiableList, List.of) or a list it relies on staying constant. Mutating it can throw UnsupportedOperationException at runtime or silently break the Java caller's invariants. Treat Java-returned collections as untrusted: copy defensively with toList()/toMutableList(), or annotate the Java API with @Unmodifiable / use proper @NotNull-style annotations so Kotlin infers a tighter type.
code
kotlin · 5 lines// Java returns Collections.unmodifiableList(internal)
val names = javaApi.names // MutableList<String!>
// names.add("x") // compiles, but throws UnsupportedOperationException at runtime
val safe = names.toMutableList() // defensive copy; safe to mutate
safe.add("x")go deeper
Knows a Java List can be modified from Kotlin but may be unsure why or what breaks.
Explains the MutableList<String!> default, names UnsupportedOperationException, and copies defensively.
Discusses mapped types, platform types, and uses @Unmodifiable/nullability annotations to harden the boundary.
Sets team conventions for crossing Java/Kotlin collection boundaries (always copy or annotate) and reasons about mutation-leak bugs at scale.
## How Java collections map into Kotlin Kotlin maps several JDK collection types onto its own declarations through **mapped types**. `java.util.List`, `java.util.Set`, `java.util.Map`, `java.util.Collection`, and `java.util.Iterator` each map onto a Kotlin interface. Crucially, each Java type corresponds to **both** a read-only and a mutable Kotlin view — and at a Java boundary the compiler defaults to the **mutable** one. So a Java signature `List<String> getNames()` is seen from Kotlin as roughly `(Mutable)List<String!>`: - **`Mutable`** — because Java's `List` is a read/write interface with no read-only counterpart, Kotlin assumes it may be mutated, exposing `add`, `remove`, `set`. - **`String!`** — a **platform type**: nullability is unknown, so Kotlin neither forces a null check nor guarantees non-null. ```kotlin // Java: List<String> getNames() val names = api.names // inferred MutableList<String!> names.add("extra") // compiles — no read-only barrier ``` ## Why this is dangerous The Java method might return: - A list wrapped in `Collections.unmodifiableList(...)` or created with `List.of(...)` / `Arrays.asList(...)`. Calling `add`/`remove` throws **`UnsupportedOperationException`** at runtime. - A list the Java code holds internally and assumes won't change. Mutating it from Kotlin **silently corrupts** the Java side's state. Kotlin gives you **no compile-time warning** here, because Java exposes no read-only contract for the compiler to honor. ## Defensive strategies 1. **Copy on receipt:** `val safe = api.names.toList()` (read-only snapshot) or `.toMutableList()` (your own writable copy). This decouples you from the Java object entirely. 2. **Declare the intent in Kotlin:** assign to an explicit `List<String>` reference so *your* code can't mutate it accidentally: `val names: List<String> = api.names`. 3. **Annotate the Java API:** annotations like JetBrains `@Unmodifiable` (on the return), or nullability annotations (`@NotNull`/`@Nullable`), let the Kotlin compiler infer a read-only and/or non-null type instead of the permissive default. ## Keywords/APIs `toList()`, `toMutableList()`, mapped types, platform type (`String!`), `Collections.unmodifiableList`, `List.of`, `@Unmodifiable`, `UnsupportedOperationException`.
- Why doesn't the Kotlin compiler stop you from calling add() on a Java-returned list?Because Java's List interface has no read-only/mutable distinction, so Kotlin defaults to the permissive MutableList view; the compiler has no information that the list is unmodifiable.
- How can a Java library author make Kotlin treat the return as read-only?Annotate the return type with @Unmodifiable (JetBrains annotations); Kotlin then infers the read-only List view instead of MutableList.
saying these in an interview costs you the question
- Assuming Java lists always arrive as read-only List
- Mutating a Java-returned collection without copying
- Not knowing UnsupportedOperationException is a runtime risk
- Confusing the mutability default with the platform-type nullability issue