One of the most common problems coming with programming languages is that accessing a variable with a null
value causes a null reference exception at runtime. This leads to several issues that may be difficult to address while programming.
This is why a few programming languages introduced the possibility to indicate that a variable is nullable, or in other words, that it can accept the null
value. Consequently, any variable cannot contain the null
value unless you explicitly say it can. This way, you can avoid the danger of null
references, and you do not have to wait for exceptions or errors to be thrown at runtime.
Kotlin supports this possibility since its first release, and it is called null safety. This characteristic undoubtedly represents one of the coolest, most beautiful, and innovative features coming with the Kotlin programming language. This is why it is so important to know how to properly use it.
So, let’s dive into null safety in Kotlin and learn everything you should know.
As opposed to what happens in Java, Kotlin distinguishes between references that cannot hold null
and those that can. The first ones are called non-nullable references, and their type must be of a non-nullable type. On the other hand, the second ones are called nullable references and must be of a nullable type.
While initializing a variable in Kotlin as you would in Java, you are using non-nullable types. This is because Kotlin imposes strict null-safety by default. Let’s see what this means:
// fooString is a non-nullable reference var fooString: String = "foo"
The real difference between Java and Kotlin when dealing with null
values becomes clear when trying to give fooString
a null
value, as follows:
fooString = null
In Kotlin, this leads to a compilation error because the fooString
variable was declared with a non-nullable type. In Java, this would not lead to any error, except for a NullPointerException
at runtime when trying to call a method or accessing a field on fooString
.
The reason is that Java does not support null safety, and non-nullable types do not exist. In other terms, any variable is always a nullable reference and there is no way to avoid null
values except with custom logic. Thus, any reference in Java may be null
by default.
Even in Kotlin, variables can accept null
values, but you have to declare it explicitly. In the example above, you can achieve it by changing the type of fooString
variable and replacing it with the corresponding nullable type:
// fooString will now be able to accept null values var fooString: String? = "foo"
As you can see, by adding the ?
character to the end of the type name, you are making the variable a nullable reference. This is how nullable types are used in Kotlin.
Now the following line of code would not lead to any compile-time errors, just like it would in Java:
fooString = null
Nullable types can be also used as generics, but again you must explicitly declare them as nullable:
// a list do no accepting null values val nonNullableList: List<Int> = listOf(1, 2, 3, 4) // a list accepting null values val nullableList: List<Int?> = listOf(1, 2, 3, null)
Nullable types are also useful when dealing with casts. This is because the operation would result in a ClassCastException
if the object did not match the target type. But Kotlin introduced the safe cast operator as?
, which returns null
when the cast operation fails:
// safe cast operator in action val safeIntVariable: Int? = originalVariable as? Int // regular cast operator in action // a ClassCastException may be thrown at runtime val intVariable: Int = originalVariable as Int
Learning how to properly handle null safety in Kotlin takes time. Luckily, Kotlin is a versatile programming language, and it supports two approaches to make dealing with null safety easier, especially for beginners. Let’s see them in action.
null
If you want to avoid using advanced techniques to tackle null safety in Kotlin, all you have to do is use the same approach you would use in Java to avoid NullPointerException
. Basically, before accessing a nullable variable field by calling one of its methods, you always have to explicitly check whether the variable is not null, and handle the two possible cases separately.
This can be easily achieved with an if-else
expression:
var fooString: String? = "foo" // ... // Explicitly checking for the null value // and handling the two possible cases separately val fooStringlength = if (fooString != null) { fooString.length } else { -1 }
The Kotlin compiler is smart enough to track the code logic and understand that there is a fallback logic when fooString
is null. So, no errors at compile time will be thrown because the strict Kotlin null safety is enforced as expected. In fact, the fooString
variable will be accessed only when it has a proper value.
The main problem with this approach is that it only works when the variable to check is immutable. Specifically, it works only with local variables that are not modified between the check and their usage, or val
class members that have a backing non-overridable field value. This is because the compiler could not otherwise be sure that the nullable variable was not changed to null
after the check.
null
valuesWhen dealing with a collection of a nullable type, you can simply remove them from the equation by filtering them all. This is easily achievable by employing the filterNotNull()
method coming with any Kotlin collection, as follows:
val nullableList: List<Int?> = listOf(1, 2, 3, null) // filterNotNull() returns a list with no longer a nullable type val intList: List<Int> = nullableList.filterNotNull()
As you can see, the filterNonNull()
method returns a list of the corresponding non-nullable type, making the null safety handling issue implicitly solved.
Kotlin comes with a few custom operators that represent the recommended and advanced way to properly address null safety. Let’s learn how to use them all.
?.
The Kotlin safe call operator ?.
allows you to access a field or call a method on a nullable variable. In particular, this operator executes the action on the variable only when its reference is not null. Otherwise, it returns null
. Let’s see it in action through an example:
var fooString: String? = "foo" // ... // it returns fooString's length, or null if fooString in null println(fooString?.length) // it returns fooString's value in uppercase, or null if fooString in null println(fooString?.uppercase())
Plus, this operator is particularly useful when performing multiple chain calls. In this case, the chain calls return null
if any of the properties are null
:
fooCity?.mayor?.name?.uppercase()
In other words, if any variable in the chain calls is not null
, the name in uppercase of the mayor of the fooCity
is returned. Otherwise, null
is returned.
Keep in mind that the safe call operator can also be used on the left side of an assignment. What happens is that if one of the properties in the safe calls chain is null
, then the expression on the right is not evaluated, and the assignment is skipped as a result:
fooCity?.mayor?.name = "Alicia"
In this case, the assignment is performed only when fooCity
and its mayor
property are not null
.
Also, this operator can be used together with the let()
scope function to perform a particular operation only for non-null values:
val nullableList: List<Int?> = listOf(1, 2, 3, null) for (number in nullableList) { // printing only nun-null values number?.let { println(it) } }
Learn more about Kotlin’s scope functions here.
?:
The Kotlin implementation of the Elvis operator ?
: allows you to return a default value when the original nullable variable is null
. In other words, if the expression before the ?:
operator is not null
, the Elvis operator returns it.
Otherwise, it returns the expression to the right of the ?:
operator. This means that the expression on the right-hand side is evaluated only if the left-hand side is null
. Otherwise, it is completely skipped. Let’s see in action below:
val length = fooString?.length ?: -1
This expression reads just like an entire if-else
expression, as follows:
val length: Int = if (fooString != null) { fooString.length } else { -1 }
Notice that throw
and return
are regular Kotlin expressions. This means that they can be used on the right-hand side of the ?:
operator:
fun foo(fooCity: FooCity): String? { // returning null if fooCity has no mayor val mayor = fooCity.mayor ?: return null // trhowing an exception is mayor has no name val mayorName = mayor.name ?: throw IllegalArgumentException("The mayor must have a name") return mayorName }
!!
The Kotlin not-null assertion operator !!
throws a Kotlin NullPointerException
if the variable to which it is applied is null
. What this operator does is convert any value to a non-null type and ensure that it is not null by throwing an exception otherwise:
var fooString: String? = "foo" // ... // fooString should not be null, // otherwise a NullPointerException will be thrown val length = fooString!!.length
This operator should be used carefully. Specifically, when you have more information than the compiler can have, and you are sure that a nullable variable cannot be null when you are using it.
You have seen both basic and advanced ways to deal with null safety in Kotlin. So, you are ready to see the Kotlin operators in action through three real-world examples.
?.
operatorLet’s see the ?.
operator in action through an example:
val names: List<String?> = listOf("Mary", null, "James", "Jennifer") fun printAllUppercase(values: List<String?>) { for (value in values) { // the ?. operator is mandatory to avoid compile-time errors println(value?.uppercase()) } } fun printAllNonNull(values: List<String?>) { for (value in values) { // printing only when value is not null value?.let { println(it) } } } printAllUppercase(names) println("-----------") printAllNonNull(names)
If executed, this snippet would return:
MARY null JAMES JENNIFER ----------- Mary James Jennifer
As you can see, in the first case all names are printed, while in the second case only the non-null names are taken into consideration.
?:
operatorLet’s see the ?
: operator in action through an example:
val names: List<String?> = listOf("Mary", null, "James", "Jennifer") fun printAllOrMissing(values: List<String?>) { for (value in values) { println(value ?: "<Missing name>") } } printAllOrMissing(names)
When run, this snippet returns:
Mary <Missing name> James Jennifer
As you can see, the null
value in the name list is replaced by the default <Missing name>
string in the printAllOrMissing
function thanks to the Elvis operator.
!!
operatorLet’s see the !!
operator in action through an example:
class User { var name: String? = null var surname: String? = null } val fooUser = User() fooUser.name = "John" fooUser.surname = "Smith" println("${fooUser.name!!.uppercase()} ${fooUser.surname!!.uppercase()}")
If run, the following result is printed:
JOHN SMITH
In this case, you can be sure that the name
and surname
fields will not be null
when accessed. On the other hand, the compiler cannot infer this because they do not meet the immutability requirement defined previously.
So, if the !!
operator was omitted, the following two errors at compile time would be thrown:
Smart cast to 'String' is impossible, because 'fooUser.name' is a mutable property that could have been changed by this time Smart cast to 'String' is impossible, because 'fooUser.name' is a mutable property that could have been changed by this time
In this article, we looked at what Kotlin null safety represents, how to properly address it, and through which techniques and approaches. As shown, Kotlin comes with many operators and inbuilt functions to deal with null safety and offers you much freedom of action. Plus, Kotlin has been supporting these operators since day one, making null safety one of the most important features coming with the language.
Since Kotlin variables are non-nullable by default, you may encounter a few problems if you are used to programming with Java. This is why we learned the main difference between Java’s and Kotlin’s default behaviors. Also, we delved into the Kotlin null safety operator, understanding how they work, when to use them, why, and how.
Thanks for reading! I hope that you found this article helpful. Feel free to reach out to me with any questions, comments, or suggestions.
LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your Android apps — try LogRocket for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowImplement a loading state, or loading skeleton, in React with and without external dependencies like the React Loading Skeleton package.
The beta version of Tailwind CSS v4.0 was released a few months ago. Explore the new developments and how Tailwind makes the build process faster and simpler.
ChartDB is a powerful tool designed to simplify and enhance the process of visualizing complex databases. Explore how to get started with ChartDB to enhance your data storytelling.
Learn how to use JavaScript scroll snap events for dynamic scroll-triggered animations, enhancing user experience seamlessly.