Claude
Skills
Sign in
Back

Kotlin Null Safety

Included with Lifetime
$97 forever

Use when kotlin's null safety system including nullable types, safe calls, Elvis operator, smart casts, and patterns for eliminating NullPointerExceptions while maintaining code expressiveness and clarity.

Data & Analytics

What this skill does


# Kotlin Null Safety

## Introduction

Kotlin's null safety system eliminates NullPointerExceptions at compile time by
distinguishing between nullable and non-nullable types in the type system. This
approach makes null handling explicit and forces developers to consciously
handle potential null values.

Unlike Java where any reference can be null, Kotlin requires explicit
declaration of nullability with the `?` operator. The compiler enforces null
checks before dereferencing nullable values, preventing the vast majority of
null-related crashes that plague Java applications.

This skill covers nullable types, safe call operators, smart casts, nullability
in generic types, and patterns for designing null-safe APIs while maintaining
code clarity.

## Nullable Types

Nullable types explicitly indicate that a variable or property can hold null,
while non-nullable types provide compile-time guarantees of non-null values.

```kotlin
// Non-nullable types
var name: String = "Alice"
// name = null // Compilation error

// Nullable types
var nullableName: String? = "Bob"
nullableName = null // OK

// Function parameters
fun greet(name: String) {
    println("Hello, $name")
}

fun greetNullable(name: String?) {
    if (name != null) {
        println("Hello, $name")
    } else {
        println("Hello, guest")
    }
}

// greet(null) // Compilation error
greetNullable(null) // OK

// Nullable return types
fun findUser(id: Int): User? {
    return if (id > 0) User(id, "Alice") else null
}

data class User(val id: Int, val name: String)

// Nullable properties
class Person(
    val name: String,
    val email: String?,
    var phoneNumber: String?
)

// Collections with nullable elements
val nullableList: List<String?> = listOf("A", null, "B")
val listOfNullable: List<String>? = null

// Platform types from Java (String!)
// Treated as nullable for safety in Kotlin

// Nullable this
class Service {
    fun process() {
        val self: Service? = this
        self?.validate()
    }

    fun validate() {}
}
```

The `?` suffix makes a type nullable. Non-nullable types cannot be assigned null
without explicit nullability declaration, preventing accidental null references.

## Safe Call Operator

The safe call operator `?.` safely accesses properties and methods on nullable
references, returning null if the receiver is null instead of throwing NPE.

```kotlin
// Basic safe calls
val name: String? = "Alice"
val length: Int? = name?.length

val nullName: String? = null
val nullLength: Int? = nullName?.length // Returns null

// Chaining safe calls
data class Address(val street: String?, val city: String?)
data class Company(val address: Address?)
data class Employee(val company: Company?)

val employee: Employee? = Employee(Company(Address("Main St", "NYC")))

val city: String? = employee?.company?.address?.city
println(city) // "NYC"

val nullEmployee: Employee? = null
val nullCity: String? = nullEmployee?.company?.address?.city
println(nullCity) // null

// Safe calls with methods
fun processUser(user: User?) {
    user?.let { u ->
        println("Processing ${u.name}")
    }
}

// Safe calls with extension functions
fun String?.orDefault(default: String): String {
    return this ?: default
}

val result = nullName?.orDefault("Unknown")

// Safe calls in expressions
class Profile(val bio: String?)

fun displayBio(profile: Profile?) {
    val bioLength = profile?.bio?.length ?: 0
    println("Bio length: $bioLength")
}

// Safe calls with mutable properties
class Container {
    var value: String? = null

    fun updateValue() {
        value?.let { current ->
            value = current.uppercase()
        }
    }
}
```

Safe call chains short-circuit at the first null, making deeply nested optional
access clean and safe without multiple null checks.

## Elvis Operator and Null Coalescing

The Elvis operator `?:` provides default values for null expressions, enabling
concise fallback logic without verbose if-else statements.

```kotlin
// Basic Elvis operator
val name: String? = null
val displayName = name ?: "Guest"
println(displayName) // "Guest"

// Elvis with safe calls
fun getUserCity(employee: Employee?): String {
    return employee?.company?.address?.city ?: "Unknown"
}

// Elvis with expressions
fun calculateTotal(subtotal: Double?, taxRate: Double?): Double {
    val sub = subtotal ?: 0.0
    val tax = taxRate ?: 0.15
    return sub * (1 + tax)
}

// Elvis with return/throw
fun requireName(name: String?): String {
    return name ?: throw IllegalArgumentException("Name required")
}

fun processUser(user: User?) {
    val u = user ?: return
    println("Processing ${u.name}")
}

// Chaining Elvis operators
fun findValidValue(
    primary: String?,
    secondary: String?,
    tertiary: String?
): String {
    return primary ?: secondary ?: tertiary ?: "default"
}

// Elvis with nullable properties
class Config {
    var timeout: Int? = null

    fun getTimeout(): Int {
        return timeout ?: 30000
    }
}

// Elvis in constructors
class Service(name: String?) {
    val serviceName: String = name ?: "DefaultService"
}

// Elvis with function calls
fun fetchFromCache(): String? = null
fun fetchFromNetwork(): String? = "data"

fun getData(): String {
    return fetchFromCache() ?: fetchFromNetwork() ?: "fallback"
}
```

The Elvis operator evaluates the right side only if the left side is null,
supporting lazy evaluation of default values.

## Smart Casts

Smart casts automatically cast nullable types to non-nullable after null checks,
eliminating redundant casts and improving code clarity.

```kotlin
// Smart cast after null check
fun printLength(text: String?) {
    if (text != null) {
        println(text.length) // text is smart-cast to String
    }
}

// Smart cast in expressions
fun processName(name: String?): Int {
    return if (name != null) {
        name.length // Smart-cast
    } else {
        0
    }
}

// Smart cast with return
fun requireUser(user: User?): User {
    if (user == null) {
        throw IllegalStateException("User required")
    }
    return user // Smart-cast to User
}

// Smart cast with Elvis
fun getLength(text: String?): Int {
    val nonNull = text ?: return 0
    return nonNull.length // Smart-cast
}

// Smart cast with when expressions
fun describe(obj: Any?): String {
    return when {
        obj == null -> "null"
        obj is String -> "String of length ${obj.length}"
        obj is Int -> "Int: $obj"
        else -> "Unknown type"
    }
}

// Smart cast with let
fun processNullable(value: String?) {
    value?.let { nonNull ->
        println(nonNull.uppercase()) // nonNull is String
    }
}

// Smart cast limitations
class Container(var value: String?) {
    fun process() {
        // Cannot smart-cast var properties
        if (value != null) {
            // println(value.length) // Compilation error
        }

        // Use local variable for smart cast
        val localValue = value
        if (localValue != null) {
            println(localValue.length) // OK
        }
    }
}

// Smart cast with contracts
fun requireNotNull(value: String?) {
    require(value != null)
    println(value.length) // Smart-cast after require
}
```

Smart casts work with immutable variables and val properties but not var
properties, which could change between the check and usage.

## Not-Null Assertion and Platform Types

The not-null assertion operator `!!` explicitly throws NPE if a value is null,
useful for cases where null is impossible but the compiler cannot verify.

```kotlin
// Not-null assertion operator
fun processName(name: String?) {
    val length = name!!.length // Throws NPE if name is null
    println("Length: $length")
}

// Use cases for !!
fun initializeFromConfig(config: Map<String, String>) {
    // We know these keys exist
    val apiKey = config["api_key"]!!
    val endpoint = config["endpoint"]!!

    println("Configured with $apiKey at $endpoint")
}

// Avoid chaining !!
val city = employee!!.company!

Related in Data & Analytics