skip to content

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?

level: middleimportance: should knowfreq 58%

answer

  1. Java List<String> -> List<String!>! at every level
  2. ?.let runs block only when non-null, binds it
  3. ?: substitutes a default per element
  4. filterNotNull() -> truly non-null List
  5. mapNotNull transforms and drops nulls in one pass

basics

~20 s

Java 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 s

Kotlin 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
kotlin
// 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

for a junior

Knows ?.let skips nulls and ?: gives a default.

for a middle

Explains why Java collection elements may be null and chooses filterNotNull/mapNotNull appropriately.

for a senior

Hardens the collection at the boundary into a non-null Kotlin type and documents the contract for downstream callers.

for a principal

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

context