Radiant Cheese

Radiant Cheese

Andrew O'Malley's development blog

Null Handling in Kotlin

27 Jul 2013

Kotlin enforces null safety during compilation, statically verifying that you'll never see a NullPointerException originating from Kotlin code[1].

To achieve achieve this, Kotlin needs to know whether a reference can be nullable. By default, references to types are considered to be non-null and attempting to assign null to them will result in a compilation error. Nullable references are declared using a ? annotation following the type name. e.g.

var a : String = "hello" 
a = null // Error: Null can not be a value of a non-null type String

var b : String? = "hello" 
b = null // Compiles

Furthermore, Kotlin protects you from calling a function on a nullable reference that could result in a NullPointerException.

var a : String = "hello" 
a.size // Compiles

var b : String? = "hello" 
b.size // Error: Only safe (?.) or non-null asserted (!!.) calls are allowed...

As hinted at by the error message, Kotlin has dedicated operators for handling nulls. The following sections will explore these operators along with other approaches to null handling.

[1] Kotlin avoids NullPointExceptions assuming you don't throw NullPointerExceptions manually, and don't override the type system with the !!. operator

The Safe call operator: ?.

The safe call operator ?. will return null if the object is null, or otherwise the result of the expression. e.g.

var a : String? = ...
val size = a?.size // Resulting type will be Int?, not Int

Safe call operators can be chained together. If any of the expressions evaulates to null then the result will be null. e.g.

val number = person?.address?.street?.number // Will be null if any property is null

The Sure operator: !!.

If you're sure that an object can't be null at a particular statement, you can override the type system with the !!. operator. e.g.

var a : String? = ...
val size = a!!.size // Resulting type will be Int

Note: This operation is unsafe and will immediately throw a NullPointerException if the object is null.

The Elvis operator: ?:

The Elvis operator ?: allows you to supply a default value in the case of null. e.g.

var a : String? = ...
val size = a?.size ?: -1 // Resulting type will be Int, -1 when a is null

The Elvis operator can also be combined with a throw clause. e.g.

var a : String? = ...
val size = a?.size ?: throw IllegalArgumentException() // Type will be Int

Data flow analysis

The Kotlin compiler performs limited data flow analysis enabling it to automatically determine if a variable can't be null at a particular statement. e.g.

var a : String? = ...
if (a != null) {
    val size = a.size // Type will be Int, no need for ?. or !!. operators
}

Presently, the data flow analysis is pretty limited. e.g. common Java idoms of using checkNotNull methods will not be detected as non-null.

var a : String? = ...
checkNotNull(a, "a should never be null here")
val size = a.size // Won't compile as analysis won't extend into checkNotNull

However, Kotlin extension functions do provide an elegant way of implementing such checks in a fashion that the type system can understand.

Extension functions

Extension functions in Kotlin allow you to add methods to existing types. This includes Any, the root of all types in Kotlin. By applying a non-null upper bound (<T: Any>) and defining the method on nullable T?, you can effectively add methods to any nullable type.

Below I've defined two methods for nullable types that I find useful:

  • orThrow: throws an exception if the reference is null, otherwise returns the non-null result
  • orError: throws an IllegalArgumentException if the reference is null, otherwise returns the non-null result
public inline fun <T : Any> T?.orThrow(throwable: Throwable): T {
    return if (this == null) throw throwable else this
}

public inline fun <T : Any> T?.orError(message: String): T {
    return if (this == null) throw IllegalArgumentException(message) else this
}

And here's some examples of usage:

val a : String? = ...
val b = a.orThrow(MyException()) // b & c will be of type String, not String?
val c = a.orError("You must supply a value for 'a'")

Extensions functions also provide a mechanism to satisfy those who complain that Kotlin's null handling isn't correct as it's not an Option class or a monad, etc. If you really want an Option class solution, you can use an extension function to convert any nullable type to an option.

public inline fun <T : Any> T?.toOption(): Option<T> {
    return if (this == null) None() else Some(this)
}

Delegated Properties

Finally, there are situations where your intention is that an object will not be nullable but you can't supply the value at the point of declaration — the object is initialised after construction.

class Foo {
    var bar : Bar? = null // Works, but now type is Bar?

    fun initialise() {
        bar = Bar()
    } 
}

This works, but now you have to handle the null case every time you use the bar variable.

Kotlin has a feature called Delegated Properties that allows you to customise the behaviour of properties, by providing your own Delegate implementation that handles get and set operations.

One built-in delegate is notNull(). This effectively provides a non-null placeholder that alleviates the need to deal with nulls — as long as you set it before you use it!

class Foo {
    var bar : Bar by kotlin.properties.Delegates.notNull() // type is now Bar

    fun initialise() {
        val s = bar // Using before setting throws an IllegalStateException!
        bar = Bar()
    } 
}

Conclusion

Kotlin can statically verify your program is null-safe during compilation — providing you stick to safe operations. It also provides a rich set of operators, extensions and delegates to make the reality of handling nulls as pleasant as it can be. Finally, Kotlin is a pragmatic language, allowing you override the type system as required.

Recent posts

About me

I'm a senior consultant based in Melbourne, Australia, specialising in JVM languages. Contact Information