Null safety is one of the fundamental features of Kotlin, designed to eliminate the infamous NullPointerExceptions (NPE) that so plague developers.

This blog post will explore Kotlin’s null safety in detail, including its benefits, how it works, and practical examples to illustrate its application. Whether you are a seasoned developer or new to Kotlin, this comprehensive guide will help you understand and apply null safety guards effectively in your projects.

The Problem With Null Pointers

Null pointers are a common source of bugs and crashes in many programming languages. In Java, for instance, dereferencing a null reference results in a NullPointerException (NPE). These exceptions can be difficult to track down and fix, especially in large codebases.

For more history have a look at the talk “Null References: The Billion Dollar Mistake” by Tony Hoare, who describes the costly impact of null-related bugs.

Kotlin’s Approach to Null Safety

Kotlin addresses the problem of null pointers by introducing a type system that differentiates between nullable and non-nullable types. This distinction allows Kotlin to enforce null checks at compile-time, significantly reducing the likelihood of NPEs.

Types are non-nullable by default. This means that a variable of type String cannot hold a null value:

1
2
var nonNullableString: String = "Hello, Kotlin"
// nonNullableString = null // Compile-time error

To declare a variable that can hold a null value, you append a ? to the type:

1
2
var nullableString: String? = null
nullableString = null // All good

By explicitly distinguishing between nullable and non-nullable types, Kotlin forces developers to think about nullability and handle it appropriately.

Safe Calls

Kotlin provides a safe call operator ?. to handle nullable types safely. The safe call operator allows you to access properties and methods of a nullable object without risking an NPE:

1
val length: Int? = nullableString?.length

In this example, nullableString?.length returns null if nullableString is null, otherwise it returns the length of the string. This prevents a potential NPE and makes the code more concise and readable.

Elvis Operator

The Elvis operator ?: is another useful construct for dealing with nullable types. It allows you to provide a default value in case a nullable expression evaluates to null:

1
val length: Int = nullableString?.length ?: 0

Here, if nullableString is null, the length will be set to 0. The Elvis operator is a concise way to handle null values and provide fallback logic.

Not-Null Assertion

Sometimes you might be certain that a nullable variable is not null at a particular point in your code. In such cases, you can use the not-null assertion operator !! to tell the compiler that the value is not null:

1
val length: Int = nullableString!!.length

Use the not-null assertion operator with caution. If the variable is actually null, the application will throw an NPE at runtime, defeating the purpose of null safety! It is generally better to use safe calls and the Elvis operator to handle null values more gracefully.

Working With Collections

Kotlin also provides null safety mechanisms for collections. You can declare collections of nullable or non-nullable types, and Kotlin’s type system ensures that you handle nullability correctly:

1
2
val nonNullableList: List<String> = listOf("Kotlin", "Java", "Swift")
val nullableList: List<String?> = listOf("Kotlin", null, "Swift")

When working with nullable collections, you can use functions like filterNotNull to remove null values:

1
println(nullableList.filterNotNull())

Which prints:

1
[Kotlin, Swift]

I encourage you to explore further Kotlin’s collections API as there are many more functions provided to make working with nulls easier.

Null Safety in Functions

Kotlin’s null safety extends to functions as well. You can define functions that accept nullable or non-nullable parameters and return nullable or non-nullable values. The type system enforces null safety at every step, preventing you from passing or returning null values where they are not expected.

Nullable Parameters

Here’s an example of a function with a nullable parameter:

1
2
3
4
5
6
7
fun printLength(str: String?) {
    if (str != null) {
        println("Length: ${str.length}")
    } else {
        println("String is null")
    }
}

The function printLength checks if the parameter str is null before attempting to access its length, ensuring that no NPE occurs. Note that there’s no ? after checking that value is not null. Kotlin “knows” by utilising smart casts (on that later) that str is not null and therefor treats it as such.

Nullable Return Types

You can also define functions that return nullable types:

1
2
3
fun findString(strings: List<String>, query: String): String? {
    return strings.find { it == query }
}

In this example, the function findString returns a nullable String.

Related Null Safety Features

Kotlin provides several related features to handle nullability more effectively, including smart casts, the let function, and the run function.

Smart Casts

Kotlin’s smart cast feature automatically casts a nullable type to a non-nullable type after a null check (as we noticed in previous example in nullable parameters section):

1
2
3
4
5
6
7
fun printLength(str: String?) {
    if (str != null) {
        println("Length: ${str.length}") // str is automatically cast to non-nullable
    } else {
        println("String is null")
    }
}

Smart casts reduce the need for explicit casting and make the code more concise and readable.

The let Function

The let function belongs to scope functions and is a powerful tool for working with nullable values. It executes a block of code only if the value is not null:

1
nullableString?.let { println("Length: ${it.length}") }

The let function ensures that the block of code is only executed when nullableString is not null, providing a safe and idiomatic way to handle nullable values.

The run Function

The run function is similar to let, but it allows you to work with a nullable object and return a result:

1
val lengthOrNull: Int? = nullableString?.run { length }

In this example, run executes the block of code if nullableString is not null and returns the result of the block. If nullableString is null, the result is null.

Conclusion

Kotlin’s null safety is a powerful feature that helps developers write safer and more reliable code by eliminating the risk of null pointer exceptions. By distinguishing between nullable and non-nullable types, providing safe call operators, and offering advanced features like smart casts and the let function, Kotlin ensures that nullability is handled explicitly and correctly.

Understanding and leveraging these features will make your Kotlin code more robust, maintainable, and less prone to runtime exceptions. Embrace Kotlin’s null safety and experience the benefits of a language designed to minimize common pitfalls and improve code quality.