About Tomasz Nurkiewicz

Java EE developer, Scala enthusiast. Enjoying data analysis and visualization. Strongly believes in the power of testing and automation.

Null safety in Kotlin

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

Gjersjøen lake


There are at least couple of approaches to null handling 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 have Optional<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 get NullPointerException-free code and no runtime overhead due to extra Option wrapper.

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"
    hello(str)
 
    val maybeStr: String? = "Maybe?"
    hello(maybeStr)     //doesn't COMPILE
 
    if(maybeStr != null) {
        hello(maybeStr)
    }
 
}

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 for null, compilation succeeds. To be precise, the compiler can prove that downcasting from String? to String is safe. Similarly I always found it annoying in Java that after using instanceof operator (being annoying on its own) I still have to down cast my object:

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

Not in Kotlin:

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

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
unsafeHello(str)
 
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 though String.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 Annotations which lets you annotate arbitrary method, even in external JARs. You cannot change external code so such annotations are kept in special annotations.xml file:

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

This declaration (of course IntelliJ manages it for you) tells Kotlin compiler that String.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.
 

Reference: Null safety in Kotlin from our JCG partner Tomasz Nurkiewicz at the Java and neighbourhood blog.
Related Whitepaper:

Java Essential Training

Author David Gassner explores Java SE (Standard Edition), the language used to build mobile apps for Android devices, enterprise server applications, and more!

The course demonstrates how to install both Java and the Eclipse IDE and dives into the particulars of programming. The course also explains the fundamentals of Java, from creating simple variables, assigning values, and declaring methods to working with strings, arrays, and subclasses; reading and writing to text files; and implementing object oriented programming concepts. Exercise files are included with the course.

Get it Now!  

Leave a Reply


seven × 2 =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books