Antonello Zanini I'm a software engineer, but I prefer to call myself a technology bishop. Spreading knowledge through writing is my mission.

A complete guide to null safety in Kotlin

7 min read 2053

Complete Guide to Null Safety In Kotlin

One of the most common problems coming with programming languages is that accessing a variable with a null value causes a null reference exception at runtime. This leads to several issues that may be difficult to address while programming.

This is why a few programming languages introduced the possibility to indicate that a variable is nullable, or in other words, that it can accept the null value. Consequently, any variable cannot contain the null value unless you explicitly say it can. This way, you can avoid the danger of null references, and you do not have to wait for exceptions or errors to be thrown at runtime.

Kotlin supports this possibility since its first release, and it is called null safety. This characteristic undoubtedly represents one of the coolest, most beautiful, and innovative features coming with the Kotlin programming language. This is why it is so important to know how to properly use it.

So, let’s dive into null safety in Kotlin and learn everything you should know.

Non-nullable types vs. nullable types

As opposed to what happens in Java, Kotlin distinguishes between references that cannot hold null and those that can. The first ones are called non-nullable references, and their type must be of a non-nullable type. On the other hand, the second ones are called nullable references and must be of a nullable type.

While initializing a variable in Kotlin as you would in Java, you are using non-nullable types. This is because Kotlin imposes strict null-safety by default. Let’s see what this means:

// fooString is a non-nullable reference
var fooString: String = "foo" 

The real difference between Java and Kotlin when dealing with null values becomes clear when trying to give fooString a null value, as follows:

fooString = null

In Kotlin, this leads to a compilation error because the fooString variable was declared with a non-nullable type. In Java, this would not lead to any error, except for a NullPointerException at runtime when trying to call a method or accessing a field on fooString.

The reason is that Java does not support null safety, and non-nullable types do not exist. In other terms, any variable is always a nullable reference and there is no way to avoid null values except with custom logic. Thus, any reference in Java may be null by default.

Even in Kotlin, variables can accept null values, but you have to declare it explicitly. In the example above, you can achieve it by changing the type of fooString variable and replacing it with the corresponding nullable type:

// fooString will now be able to accept null values
var fooString: String? = "foo"

As you can see, by adding the ? character to the end of the type name, you are making the variable a nullable reference. This is how nullable types are used in Kotlin.

Now the following line of code would not lead to any compile-time errors, just like it would in Java:

fooString = null

Nullable types can be also used as generics, but again you must explicitly declare them as nullable:

// a list do no accepting null values
val nonNullableList: List<Int> = listOf(1, 2, 3, 4)

// a list accepting null values
val nullableList: List<Int?> = listOf(1, 2, 3, null)

Nullable types are also useful when dealing with casts. This is because the operation would result in a ClassCastException if the object did not match the target type. But Kotlin introduced the safe cast operator as?, which returns null when the cast operation fails:

// safe cast operator in action
val safeIntVariable: Int? = originalVariable as? Int

// regular cast operator in action
// a ClassCastException may be thrown at runtime 
val intVariable: Int = originalVariable as Int

Basic null safety

Learning how to properly handle null safety in Kotlin takes time. Luckily, Kotlin is a versatile programming language, and it supports two approaches to make dealing with null safety easier, especially for beginners. Let’s see them in action.

Explicitly checking for null

If you want to avoid using advanced techniques to tackle null safety in Kotlin, all you have to do is use the same approach you would use in Java to avoid NullPointerException. Basically, before accessing a nullable variable field by calling one of its methods, you always have to explicitly check whether the variable is not null, and handle the two possible cases separately.

This can be easily achieved with an if-else expression:

var fooString: String? = "foo"

// ...

// Explicitly checking for the null value 
// and handling the two possible cases separately
val fooStringlength = if (fooString != null) { 
 fooString.length 
} else {
  -1
}

The Kotlin compiler is smart enough to track the code logic and understand that there is a fallback logic when fooString is null. So, no errors at compile time will be thrown because the strict Kotlin null safety is enforced as expected. In fact, the fooString variable will be accessed only when it has a proper value.

The main problem with this approach is that it only works when the variable to check is immutable. Specifically, it works only with local variables that are not modified between the check and their usage, or val class members that have a backing non-overridable field value. This is because the compiler could not otherwise be sure that the nullable variable was not changed to null after the check.



Filtering null values

When dealing with a collection of a nullable type, you can simply remove them from the equation by filtering them all. This is easily achievable by employing the filterNotNull() method coming with any Kotlin collection, as follows:

val nullableList: List<Int?> = listOf(1, 2, 3, null)
// filterNotNull() returns a list with no longer a nullable type
val intList: List<Int> = nullableList.filterNotNull()

As you can see, the filterNonNull() method returns a list of the corresponding non-nullable type, making the null safety handling issue implicitly solved.

Advanced null safety using Kotlin operators

Kotlin comes with a few custom operators that represent the recommended and advanced way to properly address null safety. Let’s learn how to use them all.

Safe calls operator ?.

The Kotlin safe call operator ?. allows you to access a field or call a method on a nullable variable. In particular, this operator executes the action on the variable only when its reference is not null. Otherwise, it returns null. Let’s see it in action through an example:

var fooString: String? = "foo"

// ...

// it returns fooString's length, or null if fooString in null
println(fooString?.length)

// it returns fooString's value in uppercase, or null if fooString in null
println(fooString?.uppercase())

Plus, this operator is particularly useful when performing multiple chain calls. In this case, the chain calls return null if any of the properties are null:

fooCity?.mayor?.name?.uppercase()

In other words, if any variable in the chain calls is not null, the name in uppercase of the mayor of the fooCity is returned. Otherwise, null is returned.

Keep in mind that the safe call operator can also be used on the left side of an assignment. What happens is that if one of the properties in the safe calls chain is null, then the expression on the right is not evaluated, and the assignment is skipped as a result:

fooCity?.mayor?.name = "Alicia"

In this case, the assignment is performed only when fooCity and its mayor property are not null.

Also, this operator can be used together with the let() scope function to perform a particular operation only for non-null values:

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

for (number in nullableList) {
    // printing only nun-null values
    number?.let { 
      println(it) 
    }
}

Learn more about Kotlin’s scope functions here.

Elvis operator ?:

The Kotlin implementation of the Elvis operator ?: allows you to return a default value when the original nullable variable is null. In other words, if the expression before the ?: operator is not null, the Elvis operator returns it.

Otherwise, it returns the expression to the right of the ?: operator. This means that the expression on the right-hand side is evaluated only if the left-hand side is null. Otherwise, it is completely skipped. Let’s see in action below:

val length = fooString?.length ?: -1

This expression reads just like an entire if-else expression, as follows:

val length: Int = if (fooString != null) {
  fooString.length
} else { 
  -1
}

Notice that throw and return are regular Kotlin expressions. This means that they can be used on the right-hand side of the ?: operator:

fun foo(fooCity: FooCity): String? {
    // returning null if fooCity has no mayor
    val mayor = fooCity.mayor ?: return null

    // trhowing an exception is mayor has no name
    val mayorName = mayor.name ?: throw IllegalArgumentException("The mayor must have a name")

    return mayorName
}

Not-null assertion operator !!

The Kotlin not-null assertion operator !! throws a Kotlin NullPointerException if the variable to which it is applied is null. What this operator does is convert any value to a non-null type and ensure that it is not null by throwing an exception otherwise:

var fooString: String? = "foo"

// ...

// fooString should not be null, 
// otherwise a NullPointerException will be thrown
val length = fooString!!.length

This operator should be used carefully. Specifically, when you have more information than the compiler can have, and you are sure that a nullable variable cannot be null when you are using it.

Null safety Kotlin operators in action

You have seen both basic and advanced ways to deal with null safety in Kotlin. So, you are ready to see the Kotlin operators in action through three real-world examples.

?. operator

Let’s see the ?. operator in action through an example:

val names: List<String?> = listOf("Mary", null, "James", "Jennifer")

fun printAllUppercase(values: List<String?>) {
  for (value in values) {
    // the ?. operator is mandatory to avoid compile-time errors
    println(value?.uppercase())
  }  
}

fun printAllNonNull(values: List<String?>) {
  for (value in values) {
    // printing only when value is not null
    value?.let {
      println(it)
    }
  }  
}

printAllUppercase(names)
println("-----------")
printAllNonNull(names)

If executed, this snippet would return:

MARY
null
JAMES
JENNIFER
-----------
Mary
James
Jennifer

As you can see, in the first case all names are printed, while in the second case only the non-null names are taken into consideration.


More great articles from LogRocket:


?: operator

Let’s see the ?: operator in action through an example:

val names: List<String?> = listOf("Mary", null, "James", "Jennifer")

fun printAllOrMissing(values: List<String?>) {
  for (value in values) {
    println(value ?: "<Missing name>")
  }  
}

printAllOrMissing(names)

When run, this snippet returns:

Mary
<Missing name>
James
Jennifer

As you can see, the null value in the name list is replaced by the default <Missing name> string in the printAllOrMissing function thanks to the Elvis operator.

!! operator

Let’s see the !! operator in action through an example:

class User {
    var name: String? = null
    var surname: String? = null
}

val fooUser = User()
fooUser.name = "John"
fooUser.surname = "Smith"



println("${fooUser.name!!.uppercase()} ${fooUser.surname!!.uppercase()}")

If run, the following result is printed:

JOHN SMITH

In this case, you can be sure that the name and surname fields will not be null when accessed. On the other hand, the compiler cannot infer this because they do not meet the immutability requirement defined previously.

So, if the !! operator was omitted, the following two errors at compile time would be thrown:

Smart cast to 'String' is impossible, because 'fooUser.name' is a mutable property that could have been changed by this time

Smart cast to 'String' is impossible, because 'fooUser.name' is a mutable property that could have been changed by this time

Conclusion

In this article, we looked at what Kotlin null safety represents, how to properly address it, and through which techniques and approaches. As shown, Kotlin comes with many operators and inbuilt functions to deal with null safety and offers you much freedom of action. Plus, Kotlin has been supporting these operators since day one, making null safety one of the most important features coming with the language.

Since Kotlin variables are non-nullable by default, you may encounter a few problems if you are used to programming with Java. This is why we learned the main difference between Java’s and Kotlin’s default behaviors. Also, we delved into the Kotlin null safety operator, understanding how they work, when to use them, why, and how.

Thanks for reading! I hope that you found this article helpful. Feel free to reach out to me with any questions, comments, or suggestions.

LogRocket: Instantly recreate issues in your Android apps.

LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.

Start proactively monitoring your Android apps — .

Antonello Zanini I'm a software engineer, but I prefer to call myself a technology bishop. Spreading knowledge through writing is my mission.

Leave a Reply