Nullable Types and Null Safety in Kotlin

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. (Excerpt from Wikipedia)

-Sir Tony Hoare, The inventor of null reference

If you have been programming in Java or any other language that has the concept of null reference then you must have heard about or experienced NullPointerException in your programs.

NullPointerExceptions are Runtime Exceptions which are thrown by the program at runtime causing application failure and system crashes.

Wouldn’t it be nice if we could detect possible NullPointerException exception errors at compile time itself and guard against them?

Well, Enter Kotlin!

Nullability and Nullable Types in Kotlin

Kotlin supports nullability as part of its type System. That means You have the ability to declare whether a variable can hold a null value or not.

By supporting nullability in the type system, the compiler can detect possible NullPointerException errors at compile time and reduce the possibility of having them thrown at runtime.

Let’s understand how it works!

All variables in Kotlin are non-nullable by default. So If you try to assign a null value to a regular variable, the compiler will throw an error -

var greeting: String = "Hello, World"
greeting = null // Compilation Error

To allow null values, you have to declare a variable as nullable by appending a question mark in its type declaration -

var nullableGreeting: String? = "Hello, World"
nullableGreeting = null // Works

We know that NullPointerException occurs when we try to call a method or access a property on a variable which is null. Kotlin disallows method calls and property access on nullable variables and thereby prevents many possible NullPointerExceptions.

For example, The following method access works because Kotlin knows that the variable greeting can never be null -

val len = greeting.length 
val upper = greeting.toUpperCase() 

But the same method call won’t work with nullableGreeting variable -

val len = nullableGreeting.length // Compilation Error
val upper = nullableGreeting.toUpperCase()  // Compilation Error

Since Kotlin knows beforehand which variable can be null and which cannot, It can detect and disallow calls which could result in NullPointerException at compile-time itself.

Working with Nullable Types

All right, It’s nice that Kotlin disallows method calls and property access on nullable variables to guard against NullPointerException errors. But we still need to do that right?

Well, There are several ways of safely doing that in Kotlin.

1. Adding a null Check

The most trivial way to work with nullable variables is to perform a null check before accessing a property or calling a method on them -

val nullableName: String? = "John"

if(nullableName != null) {
    println("Hello, ${nullableName.toUpperCase()}.")
    println("Your name is ${nullableName.length} characters long.")
} else {
    println("Hello, Guest")
}

Once you perform a null comparison, the compiler remembers that and allows calls to toUpperCase() and length inside the if branch.

2. Safe call operator: ?.

Null Comparisons are simple but too verbose. Kotlin provides a Safe call operator, ?. that reduces this verbosity. It allows you to combine a null-check and a method call in a single expression.

For example, The following expression -

nullableName?.toUpperCase()

is same as -

if(nullableName != null) 
    nullableName.toUpperCase()
else
    null    

Wow! That saves a lot of keystrokes, right? :-)

So if you were to print the name in uppercase and its length safely, you could do the following -

val nullableName: String? = null

println(nullableName?.toUpperCase())
println(nullableName?.length)
// Prints 
null
null

That printed null since the variable nullableName is null, otherwise, it would have printed the name in uppercase and its length.

But what if you don’t want to print anything if the variable is null?

Well, To perform an operation only if the variable is not null, you can use the safe call operator with let -

val nullableName: String? = null

nullableName?.let { println(it.toUpperCase()) }
nullableName?.let { println(it.length) }

// Prints nothing

The lambda expression inside let is executed only if the variable nullableName is not null.

That’s great but that’s not all. Safe call operator is even more powerful than you think. For example, You can chain multiple safe calls like this -

val currentCity: String? = user?.address?.city

The variable currentCity will be null if any of user, address or city is null. (Imagine doing that using null-checks.)

3. Elvis operator: ?:

The Elvis operator is used to provide a default value when the original variable is null -

val name = nullableName ?: "Guest"

The above expression is same as -

val name = if(nullableName != null) nullableName else "Guest"

In other words, The Elvis operator takes two values and returns the first value if it is not null, otherwise, it returns the second value.

The Elvis operator is often used with Safe call operator to provide a default value other than null when the variable on which a method or property is called is null -

val len = nullableName?.length ?: -1

You can have more complex expressions on the left side of Elvis operator -

val currentCity = user?.address?.city ?: "Unknown"

Moreover, You can use throw and return expressions on the right side of Elvis operator. This is very useful while checking preconditions in a function. So instead of providing a default value in the right side of Elvis operator, you can throw an exception like this -

val name = nullableName ?: throw IllegalArgumentException("Name can not be null")

4. Not null assertion : !! Operator

The !! operator converts a nullable type to a non-null type, and throws a NullPointerException if the nullable type holds a null value.

So It’s a way of asking for NullPointerException explicitly. Please don’t use this operator.

val nullableName: String? = null
nullableName!!.toUpperCase() // Results in NullPointerException

Null Safety and Java Interoperability

Kotlin is fully interoperable with Java but Java doesn’t support nullability in its type system. So what happens when you call Java code from Kotlin?

Well, Java types are treated specially in Kotlin. They are called Platform types. Since Kotlin doesn’t have any information about the nullability of a type declared in Java, It relaxes compile-time null checks for these types.

So you don’t get any null safety guarantee for types declared in Java, and you have full responsibility for operations you perform on these types. The compiler will allow all operations. If you know that the Java variable can be null, you should compare it with null before use, otherwise, just like Java, you’ll get a NullPointerException at runtime if the value is null.

Consider the following User class declared in Java -

public class User {
    private final String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Since Kotlin doesn’t know about the nullability of the member variable name, It allows all operations on this variable. You can treat it as nullable or non-nullable, but the compiler won’t enforce anything.

In the following example, We simply treat the variable name as non-nullable and call methods and properties on it -

val javaUser = User(null)

println(javaUser.name.toUpperCase()) // Allowed (Throws NullPointerException)
println(javaUser.name.length) // Allowed (Throws NullPointerException)

The other option is to treat the member variable name as nullable and use the safe operator for calling methods or accessing properties -

val javaUser = User(null)

println(javaUser.name?.toUpperCase()) // Allowed (Prints null)
println(javaUser.name?.length) // Allowed (Prints null)

Nullability Annotations

Although Java doesn’t support nullability in its type system, You can use annotations like @Nullable and @NotNull provided by external packages like javax.validation.constraints, org.jetbrains.annotations etc to mark a variable as Nullable or Not-null.

Java compiler doesn’t use these annotations, but these annotations are used by IDEs, ORM libraries and other external tools to provide assistance while working with null values.

Kotlin also respects these annotations when they are present in Java code. Java types which have these nullability annotations are represented as actual nullable or non-null Kotlin types instead of platform types.

Nullability and Collections

Kotlin’s collection API is built on top of Java’s collection API but it fully supports nullability on Collections.

Just as regular variables are non-null by default, a normal collection also can’t hold null values -

val regularList: List<Int> = listOf(1, 2, null, 3) // Compiler Error

1. Collection of Nullable Types

Here is how you can declare a Collection of Nullable Types in Kotlin -

val listOfNullableTypes: List<Int?> = listOf(1, 2, null, 3) // Works

To filter non-null values from a list of nullable types, you can use the filterNotNull() function -

val notNullList: List<Int> = listOfNullableTypes.filterNotNull()

2. Nullable Collection

Note that there is a difference between a collection of nullable types and a nullable collection.

A collection of nullable types can hold null values but the collection itself cannot be null -

var listOfNullableTypes: List<Int?> = listOf(1, 2, null, 3) // Works
listOfNullableTypes = null // Compilation Error

You can declare a nullable collection like this -

var nullableList: List<Int>? = listOf(1, 2, 3)
nullableList = null // Works

3. Nullable Collection of Nullable Types

Finally, you can declare a nullable collection of nullable types like this -

var nullableListOfNullableTypes: List<Int?>? = listOf(1, 2, null, 3) // Works
nullableListOfNullableTypes = null // Works

Conclusion

That’s all in this article folks. I hope you understood how kotlin helps you avoid NullPointerException errors with its nullable type concept.

Thanks for reading. See you in the next post.