Chapter 4 of 12 Key Chapter ~18 min read

Null Safety ⭐

The single biggest upgrade from Java. Kotlin's type system makes null explicit — eliminating NullPointerExceptions at compile time.


The Billion-Dollar Mistake

Tony Hoare, inventor of the null reference, called it his "billion-dollar mistake" — because null dereferences have caused countless production crashes. Kotlin was designed from day one to make null safety a first-class language feature.

Non-Nullable vs Nullable Types

In Kotlin, every type is non-nullable by default. You must explicitly opt in to allow null with a ? suffix:

Java — any reference can be null
String name = "Alice";
name = null;  // perfectly legal!

// No compile-time protection:
System.out.println(name.length());
// ☠️ NullPointerException at runtime!
Kotlin — null requires explicit opt-in
var name: String = "Alice"
// name = null  // ❌ Won't compile!

// Nullable type uses '?'
var name2: String? = "Alice"
name2 = null  // ✅ allowed

// Kotlin prevents you from calling .length
// on a nullable String? without checking
// name2.length  // ❌ compile error

The Safe Call Operator ?.

Call methods on nullable values without manual null checks. Returns null if the receiver is null:

Java
String name = getName(); // might be null
int length;
if (name != null) {
    length = name.length();
} else {
    length = 0;
}
// Verbose null-check chain:
if (user != null
        && user.getProfile() != null) {
    System.out.println(
        user.getProfile().getAvatarUrl());
}
Kotlin
val name: String? = getName()
val length = name?.length ?: 0
// Returns null if name is null

// Chaining safe calls — clean!
val avatarUrl = user?.profile?.avatarUrl
println(avatarUrl) // null if any is null

// Only execute if non-null:
name?.let { n ->
    println("Name length: ${n.length}")
}

The Elvis Operator ?:

Provide a default value when a nullable expression is null. This replaces Java's ternary-with-null-check pattern:

Java
String name = user.getName();
String displayName = name != null
    ? name
    : "Anonymous";

int age = user.getAge() != null
    ? user.getAge()
    : 0;
Kotlin
val name: String? = user.name
val displayName = name ?: "Anonymous"

val age = user.age ?: 0

// Elvis can also throw:
val userId = user.id
    ?: throw IllegalStateException("No ID!")

Non-Null Assertion !!

Tells the compiler "I guarantee this is not null." Throws NullPointerException if it is. Use sparingly — it's an escape hatch, not a solution:

Kotlin
val name: String? = "Alice"

// !! asserts non-null — throws NPE if null
val length = name!!.length  // 5

// ❌ Anti-pattern — defeats null safety
val bad: String? = null
val crash = bad!!.length  // ☠️ NPE!
Avoid !! whenever possible

Think of !! as a code smell. Every !! is a potential NPE. Prefer safe calls (?.) and Elvis (?:) instead. Reserve !! for cases where null is truly a program bug, not a normal state.

let — Execute Block on Non-Null

Kotlin
val email: String? = getUserEmail()

// Execute only if email is not null
email?.let { e ->
    sendWelcomeEmail(e)
    println("Email sent to $e")
}
// 'e' is guaranteed non-null inside the block

// Also useful for transformations:
val nameLen: Int? = name?.let { it.length }

Null Safety Quick Reference

Operator / PatternMeaningWhen to use
String?Nullable typeWhen the value can legitimately be null
x?.method()Safe call — returns null if x is nullChaining nullable calls
x ?: defaultElvis — fallback if nullProviding default values
x!!Non-null assert — throws NPE if nullOnly when null is a bug (sparingly)
x?.let { }Execute block if non-nullSide effects on nullable values
if (x != null)Explicit check — smart cast inside blockComplex logic needing non-null reference

FAQ

Kotlin prevents most NPEs at compile time. However, !! usage or Java interoperability (where Java types have platform types) can still produce NPEs at runtime. Always prefer safe calls over !!.

The Elvis operator ?: provides a fallback value when a nullable expression is null. val name = user?.name ?: "Unknown" returns "Unknown" if user or user.name is null.