Deep Dive into Kotlin Nullability

Kyle Carson
Version 1
Published in
10 min readFeb 16, 2023

By Kyle Carson, Hazel O’Donohoe & Adam Thomas-Mitchell

Kotlin is a modern programming language, released in 2016, that is becoming increasingly popular, particularly among Android developers. Developed by JetBrains, the company behind popular IDEs like IntelliJ IDEA and Android Studio, the aim was to improve the age-old Java language by addressing key issues and adding new features.

One of the main features of Kotlin is its interoperability with Java, meaning both languages can be used in the same project. This, combined with the fact that existing Java developers can learn Kotlin relatively quickly, allows for teams to easily incorporate this new language into new and existing projects.

Aiming to overcome one of the main pitfalls of programming in Java, Kotlin has a large emphasis on null safety. This helps developers avoid the null reference exception which many developers will be all too familiar with.

Overall, Kotlin is a powerful language that offers many benefits including improved null safety and concise syntax. In this article, we take a deep dive into Kotlin’s nullability features and see how they compare to Java. By the end you might even consider replacing Java with the shiny, new Kotlin.

What is Nullability?

First, what do we mean by nullability? Nullability refers to the ability of a value to be either null or non-null. Null is a special value that represents the absence of information. Often, it is given as the default value to a variable or property before it has been initialised with a non-null value.

In Java, variables and properties can have a value of Null. This can lead to a NullPointerException when the programme tries to access it. To overcome this issue, Kotlin introduced nullability as a way of explicitly specifying whether a variable or property can hold a null value. Variables and properties can be nullable or non-nullable. As one would expect, non-nullable types cannot hold a null value, whereas null types can.

Nullable and non-nullable variables are declared below. In Kotlin, we can use the ? symbol to specify a nullable type:

var str: String = “Hello, Kotlin”    // non-nullable type 
var str2: String? = “Hello, Kotlin” // nullable type

Accessing properties in Null Variables

What to do when you need to access properties in null variables? Luckily, Kotlin provides several different options to fulfil this need. These include using null in a condition and the safe operator !=.

In the next few sections we’ll take a closer look at each of these options in turn, with some simple example use cases.

Option 1: Null in a condition

Whilst Kotlin has several built in tools that you can use to avoid throwing a NullPointerException, there might be situations where you want to check if a value is Null explicitly. This can be done by simply using the != operator on a variable. In the example below, this is done with an if-else structure.

val blog: String? = “medium”

if (blog != null) {
print(“Post the blog entry!”)
}else {
print(“Write the blog post!”)
}

Option 2: Safe calls

Kotlin’s safe call operator ?.was introduced to minimise the necessity of nesting if-else conditions to check for null values. There are two ways it can be used. Normally:

val blog: String? = “medium”
print(blog?.length) // returns 6

val blog2:String? = null
print(blog2?.length) // returns null

And when trying to perform operations in a chain:

    class FirstClass {

var anExample:String = "First"
}

val myExample: FirstClass = FirstClass() // explicitly assigning the value of FirstClass's anExample to myExample
print(myExample.anExample.length) // returns 5


class SecondClass {

var anotherExample: String? = null
}

val myOtherExample: SecondClass = SecondClass() // explicitly assigning the value of SecondClass's anExample to anotherExample
print(myOtherExample.anotherExample?.length) // returns null

In the example above, myExample and anExample both contain a string value which has been explicitly declared. When determining the length of the string held in myExample and anExample, no safe call operator is required as the string has been initialised and currently holds a string value. Should we try to change the value of these variables the code will not work.

To contrast this, anotherExample and myOtherExample both contain a (currently) nulled string. When determining the length of the string held in these variables, a safe call operator is used as we know that the string has currently been initialised to be null. If this changes in the future, the evaluation will still return a value (either null or a number) because the safe call has been used and the possibility of a null value has been considered and prepared for.

Using null to avoid casting errors

A common exception that you might run into when trying to cast from one type to another is the ClassCastException. This exception is thrown when you’re trying to cast between incompatible types. Here’s an example demonstrating an attempt to cast from Double to Int:

val a:Double = 1.2
val b = a as Int

returns this error:
Exception in thread "main" java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.Integer (java.lang.Double and java.lang.Integer are in module java.base of loader 'bootstrap') at MainKt.main(Main.kt:4)

It’s possible to avoid this exception entirely by the safe cast operator in Kotlin:

    val a:Double = 1.2
val c: Int? = a as? Int

print(c) // returns null as a Double can't be cast to an Int

By doing this it’s possible to avoid the exception occurring in the first place, however it’s important to remember that c now holds a null value and this will have to be managed later on.

Nulling datatypes and collections

An interesting feature of Kotlin is the ability to declare variables and collections as nullable. This can help in preventing a wide variety of errors from arising.

Most programming languages allow for explicit typing and/or implicit typing, which have covered almost every possible use case needed. Kotlin has introduced the ability to declare a data type as nullable. What this means in reality, is that when the type declaration is paired with the nullable operator?the data type becomes ‘x or null’.

    val blog:String? = "nulling things is cool" // is valid because blog holds a string
val blog2:String? = null // is valid because blog2 is nullable and can hold a null
val blog3:String? = true // is not valid because true is not a string in this example
val blog4:String? = 3.14 // is also not valid because this is a numerical value, not a string

When using collections, it is also possible to declare elements as null. This can have several applications. For example, you could declare an immutable list that has one or more null elements in it:

    val readOnlyList = listOf<String?>("John", "Doe", null)
print(readOnlyList) // returns [John, Doe, null]

Or you could update one of the items in the array to be null, thus removing the item from use without having to destroy and re-create an array in order to change the number of items stored.

Kotlin collections allow Null to be used in a variety of ways. For example .filterNotNullreturns a list of not-null values :

val myList = List<String> = nullableList.filterNotNull()

This can be expanded upon in.filterNotNullTo, which allows these not-null values to be appended to a predefined destination:

    val myShoppingList: List<String?> = listOf("milk", "bread", "newspaper", null) // null value is included to take into account any surprise items that might need to be added to the shopping list
val nonNullItems = mutableListOf<String>() // the destination that non-null values will be filtered to

myShoppingList.filterNotNullTo(nonNullItems)

print(nonNullItems) // returns [milk, bread, newspaper]

While .elementAtOrNull can be used to return an element (or a null) from a specific index location in a collection:

    val myShoppingList: List<String?> = listOf("milk", "bread", "newspaper", null) // null value is included to take into account any surprise items that might need to be added to the shopping list

println(myShoppingList.elementAtOrNull(0)) // returns milk
println(myShoppingList.elementAtOrNull(1)) // returns bread
println(myShoppingList.elementAtOrNull(2)) // returns newspaper
println(myShoppingList.elementAtOrNull(3)) // returns null

Declaring variables or collections as nullable allows for flexibility in cases where a situation might arise that an explicitly declared variable might want to take null as a property and throw an error because of it. Though the potential error has now been avoided, it’s still important that the now (potentially) null values must be handled effectively in the remainder of the code to prevent errors or unexpected behaviour further down the line.

Comparison of Nullability in Java and Kotlin

Nullability is the ability of a variable to hold a null value. When a variable contains null, an attempt to reference the variable leads to a NullPointerException. There are many ways to write code in order to minimize the probability of receiving null pointer exceptions (NPE). This section will cover some examples of the differences between Java and Kotlin’s approaches in handling and avoiding the exception being thrown.

Kotlin’s ability to have Nullable Types

In Java, NullPointerException is an unchecked exception, and it occurs when trying to access a member of a null reference. The solution is through null checks and defensive/preventive coding techniques. This can be time consuming and decrease the readability of the code:

public void printWords(String myString) {
if(myString == null) myString = “”; //preventive coding
System.out.println(myString);

To avoid this in Kotlin, as mentioned previously we have the type system which distinguishes between references that can hold null and those that cannot. In Kotlin all regular types are non-nullable by default. This means that if we declare the printWords() as follows, myString must always contain a String instance and it cannot be null meaning it is impossible for a NPE to be thrown at runtime. Any attempt at passing in a null value into printWords() here will result in a compile-time error:

fun printWords(myString : String) = print(myString) //myString cannot be null

To allow for myString to take null values, we can use the earlier discussed nullable operator after the argument type String? . We then need to do the additional null check inside the function body. After the check is successful, the compiler will treat myString as if it is of non-nullable type:

fun printWords(myString : String?) = if(myString != null) print(myString) else print(“”)

To enhance readability in Kotlin we can use the let keyword. This ensures the code block will only be called if myString is not null:

fun printWords(myString : String?) = myString.let {print(myString)}

Default values in event of Null Variable

In Java, once you have checked for a null value and you get a positive check, you often assign a default value. The code would typically look like this:

Random random = new Random();
String[] myList = {“string1”,”string2”,null,”string3”};
String myString = myList[random.nextInt(0,4)];
if(myString == null){
myString = “Item was null”;
}

In Kotlin we can shorten this code and enhance readability by using the Elvis operator ?: :

val random = Random()
val myList = arrayOf(“string1”,”string2”,null.”string3”)
var myString = myList[random.nextInt(0,4)] ?: “Item was null”

Functions returning a value or Null

Revisiting the example of the previous section, we need to be careful when working with list elements — especially searching for an element outside of the size of the list. For example — if we were to set myString to equal myList[6], in both Kotlin and Java a runtime exception would be thrown. In Java we can get around this by using an if statement to check if the index is within range.

String[] myList = {“string1”,”string2”,null,”string3”};
int index = 6;
String myString;
if(index < myList.length()){
myString = myList[index];
}else{
myString = “Index out of range”;
}

For easier development, the Kotlin standard library often provides functions that allow a null value to be returned if you search for an index outside of the range. In this case the getOrNull function comes in handy :

val random = Random()
val myList = arrayOf(“string1”,”string2”,null,”string3”)
var myString = myList.getOrNull(6)

Calling Java from Kotlin

In Java you can signal whether a variable can or cannot be null by using annotations. The JetBrains annotations @Nullable and @NotNull are the most used by developers. Kotlin can recognize such annotations when you’re calling Java code from Kotlin code, and will treat types according to their annotations.

However, if your Java code doesn’t have these annotations, we have an issue. This is because any reference in Java may be null, which means Kotlin’s requirements of strict null-safety is impractical for objects coming from Java. As a result, types of Java declarations are treated in Kotlin as platform types. Null checks are relaxed for such types, so that safety guarantees for them are the same as in Java. This means that you must be careful and decide whether to perform null checks. See the following example below:

val list = ArrayList<String>() //non-null
list.add(“myString”)
val item = list[0] //this is an ordinary Java Object – platform type inferred

In the above example the item variable is of platform type. This means Kotlin will not issue nullability errors at compile time meaning that if item==null a NPE will be thrown. In this case we should have a null check within the code.

To solve this, instead of relying on the type inference (as item has in the above example), you can state which type you expect. This means that if you choose a non-null type by adding the question mark, the compiler will ensure the variable cannot hold a null type and so an NPE will be avoided.

val nullableItem : String? = item
val notNullableItem : String = item //may fail at runtime

Conclusion

In this article we looked at one of the most important features of Kotlin — null safety and how it compares to the more traditional Java. We have explored what nullability actually is and the various operators and inbuilt functions that Kotlin has to offer. We have seen the benefits that this language can have for developers and learnt whilst Kotlin may have some shiny new features, any Java developer should be able to pick up the language very quickly due to the similar, yet more concise syntax. Will you consider this shiny new language in your teams next project ? Check out all the documentation at https://kotlinlang.org/docs/home.html .

About the authors

Kyle Carson is a Full Stack Java Developer, and Hazel O’Donohoe and Adam Thomas-Mitchell are Associate Consultants at Version 1.

--

--