Null Safety

Kotlin's null safety system — nullable types, safe calls, elvis operator, and common patterns

Kotlin eliminates NullPointerException at compile time by distinguishing nullable and non-nullable types.

Nullable types

var name: String = "Kotlin"       // non-null — cannot hold null
// name = null                    // compile error

var nullable: String? = null      // nullable — can hold null
nullable = "hello"                // ok

Safe calls

val nullable: String? = null

// Safe call operator ?.
val length: Int? = nullable?.length     // null — no NPE

// Chain safe calls
val user: User? = null
val city: String? = user?.address?.city // null — any null in chain short-circuits

Elvis operator

// Provide a default when null
val length = nullable?.length ?: 0          // 0 if null
val name = nullable ?: "default"             // "default" if null

// Elvis with throw
val value = nullable ?: throw IllegalArgumentException("required")

// Elvis with return
val value = nullable ?: return

Non-null assertion

// !! throws NPE if null — use sparingly
val length = nullable!!.length              // NPE if null

// Prefer safe calls or let instead

Safe casts

// as? returns null on failure instead of ClassCastException
val x: Any = "hello"
val num: Int? = x as? Int                   // null
val str: String? = x as? String             // "hello"

let, also, run, apply, with — scope functions for null safety

// let — execute block only if non-null
nullable?.let {
    println(it.length)                       // it is String, not String?
}

// also — perform side effect, return original
nullable?.also {
    println("logging: $it")
}

// Multiple null checks
val a: String? = "hello"
val b: String? = "world"
a?.let { first ->
    b?.let { second ->
        println("$first $second")           // only runs if both non-null
    }
}

Collections and null filtering

val list: List<String?> = listOf("a", null, "b", null, "c")

list.filterNotNull()                          // [a, b, c] — removes nulls
list.mapNotNull { it?.uppercase() }           // [A, B, C] — maps and removes nulls

require, check, error — fail-fast assertions

// require — validates arguments (throws IllegalArgumentException)
fun greet(name: String?) {
    requireNotNull(name)                      // throws if null
    require(name.length > 0) { "name cannot be empty" }
    println("Hello, $name")                   // smart-cast to String
}

// check — validates state (throws IllegalStateException)
fun process() {
    check(isInitialized) { "not initialized" }
}

// error — throws IllegalStateException with a message
fun fail(message: String): Nothing = throw IllegalStateException(message)

Lateinit

// For late initialization of non-nullable properties
class MyService {
    lateinit var repository: Repository       // will be set later

    fun init(repo: Repository) {
        repository = repo
    }
}

// Check if initialized
if (service::repository.isInitialized) {
    service.repository.doSomething()
}

Nullable types in generics

// T? allows null regardless of T
fun <T> firstOrNull(list: List<T>): T? {
    return list.firstOrNull()
}

// Platform types from Java — treat as nullable
val javaString: String? = JavaClass.getName()  // Java returns may be null