DEV Community

Cover image for Null Safety in Kotlin
Anirban
Anirban

Posted on • Originally published at theanirban.dev on

Null Safety in Kotlin

1. Overview

In this article, we’ll look into the handling of null references in Kotlin.

Any programming language which has the concept of null reference throws a NullPointerException. It has been referred to as a billion-dollar mistake. Wiki

2. Nullable and Non-Nullable Type

Kotlin aims at eliminating the risk of NullPointerException. It distinguishes between nullable and non-nullable references as a part of its type system. In Kotlin, all variables are non-nullable by default. So, we cannot assign a null value to a variable because it’ll throw a compilation error:

var country: String = "India"
country = null //compilation error

Enter fullscreen mode Exit fullscreen mode

To define a nullable variable, we must append a question mark(?) to the type declaration:

var city: String? = "Kolkata"
city = null

Enter fullscreen mode Exit fullscreen mode

We can call a method or access a property on a non-nullable variable. However, in the case of nullable variables, we need to handle the null case explicitly. Otherwise, it will throw a compilation error since Kotlin knows that the variable contains null references:

val a : String = country.length
val b : String = city.length //compilation error

Enter fullscreen mode Exit fullscreen mode

Let’s look at the different ways how we can handle null references safely in Kotlin.

3. Working With Nullable Types

3.1. Null Check

We can use the if-else expression to explicitly check for nullable variables. However, this option works only where the variable is immutable. Depending on the complexity of the conditions, this can also lead to nested expressions.

Let’s look at an example:

val city: String? = "Kolkata"
return if (city != null) {
    city.length
} else {
    null
}

Enter fullscreen mode Exit fullscreen mode

3.2. Safe Call Operator (?.)

Kotlin has a safe call operator (?.) to handle null references. This operator executes any action only when the reference has a non-null value. Otherwise, it returns a null value. The safe call operator combines a null check along with a method call in a single expression.

Let’s see how to use a safe call operator :

val country: String? = "India"
assertEquals(5, country?.length)
val city: String? = null

assertNull(city?.length)

Enter fullscreen mode Exit fullscreen mode

In addition, we can also use the safe call operator for multiple chain calls :

val country: Country? = Country(City("Kolkata", "003"))
val code: String? = country?.city?.code
assertEquals("003", code)

Enter fullscreen mode Exit fullscreen mode

However, the chain calls return null if any of the properties are null:

val country: Country? = Country(null)
val code: String? = country?.city?.code

assertNull(code)

Enter fullscreen mode Exit fullscreen mode

3.3. Using let() Method

We can use the let() method along with the safe call operator to act on a non-nullable variable:

val cities: List<String?> = listOf("Kolkata", null, "Mumbai")
var name: List<String?> = emptyList()
for (city in cities) {
    city?.let { name = name.plus(it) }
}
return name

assertEquals(2, name.size)

Enter fullscreen mode Exit fullscreen mode

3.4. Using also() Method

We can use the also() method to execute additional operations like logging and printing of the non-nullable variables. Furthermore, this method can be used in a chain with let() or run() method.

Here's how we can use also() method along with let() method:

val cities: List<String?> = listOf("Kolkata", null, "Mumbai")
var name: List<String?> = emptyList()
for (city in cities) {
    city?.let {
        name = name.plus(it)
        it
    }?.also { println("Logging the value: $it") }
}
return name

assertEquals(2, name.size)

Enter fullscreen mode Exit fullscreen mode

3.5. Using run() Method

We can use the run() method to execute some operations on a non-nullable reference. This method operates using this reference and returns the value of the lambda result :

val countries: List<String?> = listOf("India", null, "Germany")
var name: List<String?> = emptyList()
for (country in countries) {
    country?.run {
        name = name.plus(this)
        this
    }?.also { println("Logging the value: $it") }
}
return name

assertEquals(2, name.size)

Enter fullscreen mode Exit fullscreen mode

3.6. Elvis Operator (?:)

We can use the Elvis operator (?:) to return a default value only if the original variable has a null value. If the left-side expression of the Elvis operator has a non-nullable value, then it is returned. Otherwise, the right-side expression is returned.

Let’s take a look at how the Elvis operator works:

val country: Country? = Country(City("New Delhi", null))
val result = country?.city?.code ?: "Not available"

assertEquals("Not available", result)

Enter fullscreen mode Exit fullscreen mode

We can use the Elvis operator with a safe call operator to invoke a method or property of the variable :

val country: Country? = Country(City("Mumbai", "002"))
val result = country?.city?.code ?: "Not available"

assertEquals("002", result)

Enter fullscreen mode Exit fullscreen mode

We can also use throw and return expression in the right-side expression of the Elvis operator. So instead of default values, we can throw specific exceptions in the right-side expressions of the Elvis operator:

val country: Country? = Country(City("Chennai", null))
val result = country?.city?.code ?: throw IllegalArgumentException("Not a valid code")

assertThrows<IllegalArgumentException> { result }

Enter fullscreen mode Exit fullscreen mode

3.7. Not Null Assertion Operator (!!)

We can use the not-null assertion operator (!!) to explicitly throw a NullPointerException. This operator converts any reference to its non-nullable type and throws an exception if the reference has a null value. Let’s have a look into how we can throw NullPointerException using not-null assertion operator(!!):

val country: String? = null
val result : Int = country!!.length
assertThrows<NullPointerException> { result }

Enter fullscreen mode Exit fullscreen mode

However, if the reference has a non-nullable value, then it is executed successfully:

val country: String? = "India"
val result : Int = country!!.length
assertEquals(5, result)

Enter fullscreen mode Exit fullscreen mode

The not-null assertion operator should be used carefully since it’s a potential sign of a NullPointerException. We should avoid using multiple non-null assertions like the following since it makes it harder to debug which property is null:

country!!.city!!.code

Enter fullscreen mode Exit fullscreen mode

Additionally, We should always try to use safe call operator in such cases to ensure NullPointerException doesn’t occur :

country?.city?.code

Enter fullscreen mode Exit fullscreen mode

4. Nullability in Collections

Kotlin collections are non-nullable by default. So, in order to define a collection of nullable types in Kotlin, we have to append the question mark (?) to the type declaration:

val countries: List<String?> = listOf("India", null, "Germany", "Russia", null)

Enter fullscreen mode Exit fullscreen mode

We can use the following way to define a nullable collection in Kotlin:

var countries: List<String>? = listOf("India", "Germany", "Russia")
countries = null

Enter fullscreen mode Exit fullscreen mode

4.1. Filtering Nullable Types

We can filter a list that contains nullable values to return only the non-nullable values using the filterNotNull() method. Let’s have a look at an example:

val countries: List<String?> = listOf("India", null, "Germany", "Russia", null)
val result: List<String> = countries.filterNotNull()
assertEquals(3, result.size)
assertEquals("Russia", result[2])

Enter fullscreen mode Exit fullscreen mode

5. Conclusion

In this article, we look into the various ways to handle nullable references in Kotlin.

The code for these examples is available on GitHub.

Originally published on Anirban's Tech Blog.

Top comments (0)