You receive a List<String> from a Java API. Even though the type looks non-null, why might individual elements be null, and how do you defensively process them with ?.let and Elvis?
answer
- Java List<String> -> List<String!>! at every level
- ?.let runs block only when non-null, binds it
- ?: substitutes a default per element
- filterNotNull() -> truly non-null List
- mapNotNull transforms and drops nulls in one pass
basics
~20 sJava collections can contain null elements even when Kotlin shows them as non-null platform types. Guard each element with ?.let to run code only when present, and use ?: for a default when it is null.
solid answer
~40 sKotlin sees a Java `List<String>` as the platform type `(Mutable)List<String!>!`. The compiler will not check the list itself, the elements, or whether iteration yields null — so a `null` element can flow into code that assumes non-null and throw later. Defensively: iterate and treat each element as nullable. `element?.let { use(it) }` runs the block **only** when the element is non-null, binding it as the non-null `it`. To substitute a default instead of skipping, use Elvis: `val safe = element ?: ""`. To drop nulls entirely, `list.filterNotNull()` returns a `List<String>` (truly non-null). The key mental shift is: at the boundary, *assume* nullable until you have actively filtered or guarded, because the platform type silently permits null at every level.
code
kotlin · 7 lines// javaList: MutableList<String!>! from Java
val clean: List<String> = javaList.filterNotNull()
for (raw in javaList) {
raw?.let { send(it) } // skip nulls
val safe = raw ?: "<unknown>" // or default them
}go deeper
Knows ?.let skips nulls and ?: gives a default.
Explains why Java collection elements may be null and chooses filterNotNull/mapNotNull appropriately.
Hardens the collection at the boundary into a non-null Kotlin type and documents the contract for downstream callers.
Defines codebase rules for ingesting external/Java collections and may add annotation/wrapping layers to make nullability explicit project-wide.
## Why elements can be null A Java `List<String>` becomes the Kotlin platform type `MutableList<String!>!`. The `!` markers mean the compiler will **not** enforce null safety on the list reference *or* its elements. Java code can legally store `null` into such a list (`list.add(null)`), so even though the Kotlin type *looks* like non-null `String`, a `null` may appear at runtime. ## `?.let { }` — guard and bind The safe-call `?.` invokes `let` only when the receiver is non-null. Inside, `it` is the smart-cast non-null value. The whole expression is `null` (skipped) when the element is null. ```kotlin for (name in javaList) { // name: String! (platform) name?.let { println(it.uppercase()) } // runs only if name != null } ``` ## Elvis for a default When you want to *substitute* rather than *skip*: ```kotlin val cleaned: List<String> = javaList.map { it ?: "" } ``` ## `filterNotNull()` — the cleanest fix When you simply want the non-null subset, the stdlib gives you a truly non-null result type: ```kotlin val names: List<String> = javaList.filterNotNull() // List<String>, no nulls ``` ## Combining ```kotlin javaList .filterNotNull() // drop nulls .mapNotNull { it.trim().ifBlank { null } } // also drop blanks ``` `mapNotNull` transforms and drops any element whose lambda returns null in one pass. ## The boundary discipline - Treat anything coming from Java as **nullable at every level** until proven otherwise. - Convert to a real non-null Kotlin type (`filterNotNull`, explicit `List<String>` with guards) at the **entry point**, so downstream code is clean. - `?.let` = skip when null; `?:` = default when null; `filterNotNull`/`mapNotNull` = purge nulls from collections.
- Difference between filterNotNull() and mapNotNull { }?filterNotNull only removes null elements and keeps the rest unchanged; mapNotNull applies a transform and removes any element whose transform returns null — combining map and filter in one pass.
- Does ?.let return anything useful when the receiver is null?The whole `receiver?.let { }` expression evaluates to null when the receiver is null, so the block is skipped; you can chain `?: default` after it.
saying these in an interview costs you the question
- Assuming a Java List<String> can never contain null elements
- Calling .uppercase() directly on platform elements without guarding
- Confusing filterNotNull (removes nulls) with map (transforms)
- Thinking the platform ! only applies to the list, not the elements
- Using !! on each element instead of filtering