Over a million developers have joined DZone.

Null Safety in Kotlin

· Java Zone

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

Kotlin is a statically typed JVM language developed byJetbrains. It has some good documentation so today I will focus on a tiny part of it - null safety.

There are at least couple of approaches to nullhandling in JVM languages: 

  • Java doesn’t go much further than C - every reference (“pointer”) can be null, whether you like it or not. If it’s not a primitive, every single field, parameter or return value can be null.
  • Groovy has similar background but adds some syntactic sugar, namely Elvis Operator (?:) and Safe Navigation Operator (?.).
  • Clojure renames null to nil, additionally treating it as false in boolean expressions. NullPointerException is still possible.
  • Scala is first to adopt systematic, type safe Option[T] monad (Java 8 will haveOptional<T> as well!) Idiomatic Scala code should not contain nulls but when interoperating with Java you must sometimes wrap nullable values. 
Kotlin takes yet another approach. References that can be null have different type, thus null-safety is encoded in the type system and enforced only during compilation. We getNullPointerException-free code and no runtime overhead due to extra Optionwrapper.

In the syntax layer each type T has a super type T? that allows null. Have a look at these trivial examples:

fun hello(name: String) {
    println("Hello, ${name}")
fun main(args: Array<String>) {
    val str = "Kotlin"
    val maybeStr: String? = "Maybe?"
    hello(maybeStr)     //doesn't COMPILE
    if(maybeStr != null) {

Type of str is inferred to String. Function hello() accepts String so hello(str) is fine. However we explicitly declare maybeStr as String? type (nullable String). The compiler prevents us from calling hello() with String? due to incompatible type.

However if the compiler can prove that a call is safe, e.g. because we just checked fornull, compilation succeeds. To be precise, the compiler can prove that downcasting fromString? to String is safe. Similarly I always found it annoying in Java that after usinginstanceof operator (being annoying on its own) I still have to down cast my object:

Object obj = "Any object"
if(obj instanceof String) {

Not in Kotlin: 

val obj: Any = "Any object"
if(obj is String) {

See? obj is of type Any (Object in Java terms) so calling hello(obj) is doomed to fail, right? Not quite. The compiler can prove that obj is actually of type String so it performs automatic, safe downcasting for us. Neat! But back to null handling.

I said a lot about downcasting, remembering that any non-null type T has a super type of nullable T?. Just like in any other polymorphic language upcasting is implicit. In other words we can pass type T when T? is required - which is quite obvious:

val str: String = "Hello"     //String type can be inferred here
fun unsafeHello(name: String?) {

Interestingly primitives can also be nullable:

fun safePositive(x: Int) = x > 0
fun unsafePositive(x: Int?): Boolean = x != null && x > 0

In generated bytecode former method takes int while the latter java.lang.Integer. While we are at it, first two expressions compile, but not the last one:

if(unsafePositive(maybeInt)) {
if(maybeInt != null && safePositive(maybeInt)) {
if(safePositive(maybeInt)) {

First expression has a perfect type match (Int? vs. Int?). In the second case the compiler can prove that maybeInt can be downcasted to Int, required by safePositive(). This can’t be proven in the last case, resulting in type mismatch compilation error.

So far it looks great - null safety with no extra runtime overhead. However Java interoperability is Achilles’ heel of Kotlin. In Scala Option[T] wrapper is implemented on top of the language and Scala itself allows null for Java interop. You won’t see null in idiomatic Scala code, but it pops up sometimes when interacting with Java collections and libraries. Typically extra Option(javaMethod()) delegation is required.

However Kotlin takes much more aggressive approach: every parameter of every Java method is considered nullable (that we don’t care), but also every return value is nullable - unless stated otherwise. It turns out that Kotlin compiler has some knowledge of JDK:

val formatted: String = String.format("Kotlin-is-%s", "cool")
val joined:    String = String.join("-", "Kotlin", "is", "cool")

First line compiles just fine, Kotlin knows that String.format() never returns null. However it can’t say that about String.join(), new in Java 8. Thus, even thoughString.join()never returns null as well, you still get String? inferred type. The same applies to any library or your custom Java code. Unfortunately@javax.validation.constraints.NotNull annotation doesn’t help, not to mention you can’t add annotations to library/JDK code.

Well.. you sort of can… IntelliJ IDEA has an obscure feature called External Annotationswhich lets you annotate arbitrary method, even in external JARs. You cannot change external code so such annotations are kept in special annotations.xml file:

    <item name='java.lang.String java.lang.String join(java.lang.CharSequence, java.lang.CharSequence...)'>
        <annotation name='org.jetbrains.annotations.NotNull'/>

This declaration (of course IntelliJ manages it for you) tells Kotlin compiler thatString.join() can’t return null. Because our code won’t compile without it, it must be checked into version control and becomes part of your code base.

Doesn’t seem like this problem would go away soon. There will always be libraries without@NotNull annotations and the compiler can’t possibly detect whether Java method is nullable or not (especially taking dynamic nature of class loading and CLASSPATH). More portable solution is to simply force down casting to null-safe type:

String.join("-", "Kotlin", "is", "cool") as String

…but it feels superfluous.

To wrap things up: null handling in Kotlin is both radical (type-safety and compile-time checking) and conservative (null is still here, no functional, monadic style). I only hope that rough edges in Java interoperability will eventually go away.

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.


Published at DZone with permission of Tomasz Nurkiewicz, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}