Kotlin Null Safety

Kotlin's null safety system eliminates the infamous NullPointerException at compile time. It's one of the most loved features of the language.

Nullable Types

Kotlin
// Non-null (default)
var name: String = "Alice"
// name = null  // COMPILE ERROR

// Nullable (add ? after type)
var maybeName: String? = "Alice"
maybeName = null  // OK

// Nullable collections
val nonNullList: List<String> = listOf("a", "b")
val nullableList: List<String?> = listOf("a", null, "b")

Safe Calls

Kotlin
// ?. — Safe call operator
val length: Int? = maybeName?.length  // null if maybeName is null

// Chained safe calls
data class Address(val city: String?)
data class User(val address: Address?)

val user: User? = User(Address("New York"))
val city = user?.address?.city  // "New York"

val userNull: User? = null
val cityNull = userNull?.address?.city  // null (no crash!)

Elvis Operator

Kotlin
// ?: — Elvis operator (default value if null)
val length = maybeName?.length ?: 0  // 0 if null

// With function calls
val name = getUserName() ?: "Anonymous"

// With multiple fallbacks
val value = primaryValue() ?: secondaryValue() ?: defaultValue()

Let Operator

Kotlin
// ?.let { } — Execute block only if not null
maybeName?.let { name ->
    println("Name is $name, length is ${name.length}")
}

// Common patterns
val email: String? = "test@email.com"
email?.let {
    sendWelcomeEmail(it)  // Only sends if email is not null
}

// Transform if not null
val upperName = maybeName?.let {
    it.uppercase()
}

Not-Null Assertion

Kotlin
// !! — Force unwrap (throws NPE if null — use sparingly!)
val length = maybeName!!.length  // KIException if null

// Only use when you're CERTAIN it's not null
val user = getCurrentUser()!!  // Acceptable if guaranteed by logic

Smart Casts

Kotlin
fun process(value: Any) {
    // Kotlin automatically casts after null check
    if (value is String) {
        println(value.length)  // Smart cast to String
    }
    
    // Safe cast with as?
    val num: Int? = value as? Int  // null if not Int
    
    when (value) {
        is String -> println("String: $value")
        is Int -> println("Int: $value")
        is List<*> -> println("List with ${value.size} items")
        else -> println("Unknown type")
    }
}

Require and Check

Kotlin
// require — validates function arguments
fun setAge(age: Int) {
    require(age in 0..150) { "Age must be between 0 and 150" }
    // Throws IllegalArgumentException if invalid
}

// check — validates internal state
fun processUser(user: User?) {
    check(user != null) { "User must not be null" }
    // Throws IllegalStateException if null
}

// assert — debug-only check (disabled in release)
assert(user.age > 0)

Platform Types

Kotlin
// Java interop: Java types are "platform types" (String! or Int!)
// They can be either nullable or non-null
val javaString: String = javaMethod()    // Assume non-null
val javaMaybeString: String? = javaMethod()  // Assume nullable

// Always be explicit with Java return values
fun handleJavaResult(result: String?) {
    result?.let {
        processString(it)
    } ?: handleNull()
}

💡 Tip: Use ?.let for one-off operations on nullable values, and the Elvis operator for default values. Avoid !! in production code — it hides bugs that the compiler could catch.

Next: Kotlin Classes