Kotlin Null Safety

Understand how Kotlin prevents null pointer errors with nullable types, safe calls, the Elvis operator, smart casts and let.

Lesson 5 of 43Beginner18 min read

null means "no value". A user might not have a middle name, a search might find nothing, a form field might be empty. In many languages, using a variable that turns out to be null crashes the program with a null pointer exception, so common that its inventor, Tony Hoare, called it his "billion-dollar mistake".

Kotlin tackles the problem at its root. A normal type like String can never be null. If a value might be missing, you must say so with a question mark: String?. The compiler then refuses to let you use a nullable value as if it were definitely there, so the check has to happen in your code, not as a surprise crash in your users' hands.

In this lesson you'll declare nullable types and handle them with Kotlin's tools: the safe call ?., the Elvis operator ?:, smart casts after an if check, ?.let blocks, and the !! operator you should almost never use.

Nullable and non-nullable types

Kotlin
var name: String = "Ada"
name = null            // error: Null can not be a value of a non-null type String

var nickname: String? = "Addy"
nickname = null        // fine
  • String always holds a real string.
  • String? holds a string or null. Any type can be made nullable: Int?, Double?, List<String>?.
  • You can't call nickname.length directly, because nickname might be null. The compiler insists you handle that case first.

Safe calls and the Elvis operator

The safe call ?. calls a function or reads a property only if the value isn't null; otherwise the whole expression is null:

Kotlin
val len: Int? = nickname?.length      // null if nickname is null

The Elvis operator ?: supplies a default when the left side is null:

Kotlin
val len: Int = nickname?.length ?: 0
val display = nickname ?: "Guest"

Read a ?: b as "a, or if that's null, b". Chains like user?.address?.city ?: "Unknown" handle several missing values in one readable line.

Smart casts and let

After you check a value against null, Kotlin smart casts it to the non-null type inside that branch:

Kotlin
if (nickname != null) {
    println(nickname.length)    // allowed: nickname is a String here
}

?.let { } runs a block only when the value isn't null, with the value available as it:

Kotlin
nickname?.let { println("Hi, $it") }

You can also return early with Elvis: val email = user.email ?: return.

The !! operator

!! tells the compiler "trust me, this isn't null" and converts String? to String. If you're wrong, the program crashes with a NullPointerException at that line:

Kotlin
val length = nickname!!.length

Treat !! as a warning sign. There's almost always a safer option: a safe call, an Elvis default, an if check or let. Seeing !! in a code review usually prompts the question "what happens if this is null?".

Working with lists that contain nulls

Collections often contain gaps: a list of parsed numbers where some input wasn't valid, or a list of optional middle names. Kotlin has functions that remove the nulls and give you a list of the non-null type:

Kotlin
val raw = listOf("12", "abc", "7")
val numbers = raw.mapNotNull { it.toIntOrNull() }   // [12, 7]
val names: List<String?> = listOf("Ana", null, "Ben")
println(names.filterNotNull())                        // [Ana, Ben]

mapNotNull transforms each item and drops the ones that become null, which is perfect for parsing. filterNotNull simply removes nulls. Both return List<Int> or List<String>, so later code doesn't need any null checks.

The Elvis operator can also end a function early, which keeps the main logic simple: val user = findUser(id) ?: return "No such user". If the left side is null, the function returns straight away; otherwise user is a non-null value.

Kotlin Null Safety Syntax

Null-safety tools

var s: String? = null
s?.length                 // Int? (null if s is null)
s?.length ?: 0            // Int with a default
if (s != null) s.length   // smart cast
s?.let { println(it) }    // run only when not null
s!!.length                // crash if null: avoid
"12".toIntOrNull() ?: 0

Kotlin Null Safety Summary

  • Types are non-null by default; add ? (String?) to allow null.
  • ?. calls something only when the value isn't null, otherwise gives null.
  • ?: (Elvis) provides a default when the left side is null.
  • After if (x != null), x is smart cast to the non-null type; x?.let { } runs only for non-null values.
  • !! forces a non-null type and crashes if it's wrong; avoid it.

Kotlin Null Safety Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Greeting users who may not have a nickname

Kotlin
fun greet(name: String, nickname: String?) {
    val shown = nickname ?: name
    println("Hi, $shown! (nickname length: ${nickname?.length ?: 0})")
}

fun main() {
    greet("Margaret", "Maggie")
    greet("Linus", null)
}
Output
Hi, Maggie! (nickname length: 6)
Hi, Linus! (nickname length: 0)
Line-by-line explanation
  1. Line 1name must always be given; nickname may be null.
  2. Line 2Elvis uses the nickname if there is one, otherwise the name.
  3. Line 3Safe call plus Elvis: the length, or 0 when there's no nickname.
Example 2

Parsing input safely

Kotlin
fun main() {
    val inputs = listOf("42", "seven", "", "15")
    var total = 0
    for (text in inputs) {
        val n = text.toIntOrNull()
        if (n == null) {
            println("Skipping \"$text\"")
            continue
        }
        total += n
    }
    println("Total: $total")
}
Output
Skipping "seven"
Skipping ""
Total: 57
Line-by-line explanation
  1. Line 5toIntOrNull returns an Int? instead of throwing for bad text.
  2. Lines 6 to 9Handle the null case and skip to the next item.
  3. Line 10After the null check, n is smart cast to Int, so it can be added.
Example 3

Chains, let and smart casts

Kotlin
data class Address(val city: String?)
data class User(val name: String, val address: Address?)

fun main() {
    val users = listOf(
        User("Ana", Address("Lisbon")),
        User("Ben", Address(null)),
        User("Cy", null)
    )
    for (u in users) {
        val city = u.address?.city ?: "Unknown"
        println("${u.name}: $city")
    }
    users[0].address?.city?.let { println("First user's city has ${it.length} letters") }
}
Output
Ana: Lisbon
Ben: Unknown
Cy: Unknown
First user's city has 6 letters
Line-by-line explanation
  1. Lines 1 to 2A data class is a compact way to hold data; you'll learn them later. city and address may both be missing.
  2. Line 11The chain stops at the first null and Elvis supplies a default.
  3. Line 14let runs only if the city exists, with it as the non-null String.

Common Kotlin Null Safety Mistakes

Calling a method on a nullable value

Don't
val email: String? = getEmail()
println(email.length)         // error: Only safe (?.) or non-null asserted (!!.) calls are allowed
Do
println(email?.length ?: 0)

Using !! to silence the compiler

Don't
val age = input.toIntOrNull()!!     // crashes when input isn't a number
Do
val age = input.toIntOrNull() ?: 0

Making everything nullable

Don't
var title: String? = "Untitled"     // never actually null, but every use must check
Do
var title: String = "Untitled"

Only use ? when a value can genuinely be missing.

Kotlin Null Safety in Real Projects

Null safety in Android

Android apps deal with missing values constantly: a screen that hasn't loaded yet, an optional profile photo, a network response without a field. Google has reported that Android apps written in Kotlin have significantly fewer crashes than those in Java, and null safety is a large part of the reason. Kotlin also marks values coming from Java code specially, so you're reminded to check them.

Kotlin Null Safety FAQ

Can a Kotlin program still throw a NullPointerException?

Yes, in a few situations: using !! on a null value, reading a lateinit property before it's set, and values returned from Java code, whose nullability Kotlin can't always know. In ordinary Kotlin code without !!, the compiler prevents almost all of them.

What is the difference between ?. and !!?

?. is safe: if the value is null, the whole expression becomes null and the program continues. !! asserts the value isn't null and throws a NullPointerException if it is. Prefer ?. combined with ?: for a default, and keep !! for cases where null really would be a bug.

Should I use let or an if check for nullable values?

Both work. if (name != null) reads naturally and smart casts name inside the block. name?.let { ... } is handy at the end of a chain or when you want to use the result as an expression. Pick whichever is clearer in context; for a single check, if is often simplest.

Can the Elvis operator throw an exception?

Yes. Because throw is an expression in Kotlin, you can write val id = input.toIntOrNull() ?: throw IllegalArgumentException("Not a number: $input"). That's a clear way to reject invalid data while keeping id non-null.

What does a type ending in ! mean in error messages?

Types such as String! are platform types: values coming from Java code, where Kotlin doesn't know whether null is possible. Treat them carefully, and declare them as String? when the Java method might return null.

Kotlin Null Safety Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the declaration so middleName can be null.

var middleName: String = null
Predict the outputeasy

What does this print?

val city: String? = null
println(city ?: "Unknown")
Predict the outputmedium

What does this print?

val word: String? = "kotlin"
println(word?.uppercase()?.length)
Multiple choicemedium

What does s?.let { println(it) } do when s is null?

Multiple choicehard

Why is !! usually a warning sign in code reviews?

Kotlin Null Safety Quiz

5 questions to check what you learned.

  1. Can a variable of type String hold null in Kotlin?

    • Yes
    • Only inside main
    • No, only String? can
    • Only if it's a var
  2. What type is nickname?.length if nickname is a String??

    • Int?
    • Int
    • String
    • Boolean
  3. What is a smart cast?

    • Converting a String to an Int
    • A cast that never fails
    • Casting with !!
    • Kotlin treating a value as non-null after you've checked it isn't null
  4. What does "abc".toIntOrNull() return?

    • 0
    • null
    • An exception
    • "abc"
  5. What does a ?: b ?: c return when a is null, b is null and c is 3?

    • null
    • An error
    • 3
    • b