Null safety
Null safety is a Kotlin feature designed to significantly reduce the risk of null references, also known as The Billion-Dollar Mistake.
One of the most common pitfalls in many programming languages, including Java, is that accessing a member of a null reference results in a null reference exception. In Java, this would be the equivalent of a NullPointerException
, or an NPE for short.
Kotlin explicitly supports nullability as part of its type system, meaning you can explicitly declare which variables or properties are allowed to be null
. Also, when you declare non-null variables, the compiler enforces that these variables cannot hold a null
value, preventing an NPE.
Kotlin's null safety ensures safer code by catching potential null-related issues at compile time rather than runtime. This feature improves code robustness, readability, and maintainability by explicitly expressing null
values, making the code easier to understand and manage.
The only possible causes of an NPE in Kotlin are:
An explicit call to
throw NullPointerException()
.Usage of the not-null assertion operator
!!
.Data inconsistency during initialization, such as when:
An uninitialized
this
available in a constructor is used somewhere else (a "leakingthis
").A superclass constructor calling an open member whose implementation in the derived class uses an uninitialized state.
Java interoperation:
Attempts to access a member of a
null
reference of a platform type.Nullability issues with generic types. For example, a piece of Java code adding
null
into a KotlinMutableList<String>
, which would requireMutableList<String?>
to handle it properly.Other issues caused by external Java code.
Nullable types and non-nullable types
In Kotlin, the type system distinguishes between types that can hold null
(nullable types) and those that cannot (non-nullable types). For example, a regular variable of type String
cannot hold null
:
You can safely call a method or access a property on a
. It's guaranteed not to cause an NPE because a
is a non-nullable variable. The compiler ensures that a
always holds a valid String
value, so there's no risk of accessing its properties or methods when it's null
:
To allow null
values, declare a variable with a ?
sign right after the variable type. For example, you can declare a nullable string by writing String?
. This expression makes String
a type that can accept null
:
If you try accessing length
directly on b
, the compiler reports an error. This is because b
is declared as a nullable variable and can hold null
values. Attempting to access properties on nullables directly leads to an NPE:
In the example above, the compiler requires you to use safe calls to check for nullability before accessing properties or performing operations. There are several ways to handle nullables:
Read the next sections for details and examples of null
handling tools and techniques.
Check for null with the if conditional
When working with nullable types, you need to handle nullability safely to avoid an NPE. One way to handle this is checking for nullability explicitly with the if
conditional expression.
For example, check whether b
is null
and then access b.length
:
In the example above, the compiler performs a smart cast to change the type from nullable String?
to non-nullable String
. It also tracks the information about the check you performed and allows the call to length
inside the if
conditional.
More complex conditions are supported as well:
Note that the example above only works when the compiler can guarantee that b
doesn't change between the check and its usage, same as the smart cast prerequisites.
Safe call operator
The safe call operator ?.
allows you to handle nullability safely in a shorter form. Instead of throwing an NPE, if the object is null
, the ?.
operator simply returns null
:
The b?.length
expression checks for nullability and returns b.length
if b
is non-null, or null
otherwise. The type of this expression is Int?
.
You can use the ?.
operator with both var
and val
variables in Kotlin:
A nullable
var
can hold anull
(for example,var nullableValue: String? = null
) or a non-null value (for example,var nullableValue: String? = "Kotlin"
). If it's a non-null value, you can change it tonull
at any point.A nullable
val
can hold anull
(for example,val nullableValue: String? = null
) or a non-null value (for example,val nullableValue: String? = "Kotlin"
). If it's a non-null value, you cannot change it tonull
subsequently.
Safe calls are useful in chains. For example, Bob is an employee who may be assigned to a department (or not). That department may, in turn, have another employee as a department head. To obtain the name of Bob's department head (if there is one), you write the following:
This chain returns null
if any of its properties are null
. Here's the equivalent of the same safe call but with the if
conditional:
You can also place a safe call on the left side of an assignment:
In the example above, if one of the receivers in the safe call chain is null
, the assignment is skipped, and the expression on the right is not evaluated at all. For example, if either person
or person.department
is null
, the function is not called.
Elvis operator
When working with nullable types, you can check for null
and provide an alternative value. For example, if b
is not null
, access b.length
. Otherwise, return an alternative value:
Instead of writing the complete if
expression, you can handle this in a more concise way with the Elvis operator ?:
:
If the expression to the left of ?:
is not null
, the Elvis operator returns it. Otherwise, the Elvis operator returns the expression to the right. The expression on the right-hand side is evaluated only if the left-hand side is null
.
Since throw
and return
are expressions in Kotlin, you can also use them on the right-hand side of the Elvis operator. This can be handy, for example, when checking function arguments:
Not-null assertion operator
The not-null assertion operator !!
converts any value to a non-nullable type.
When you apply the !!
operator to a variable whose value is not null
, it's safely handled as a non-nullable type, and the code executes normally. However, if the value is null
, the !!
operator forces it to be treated as non-nullable, which results in an NPE.
When b
is not null
and the !!
operator makes it return its non-null value (which is a String
in this example), it accesses length
correctly:
When b
is null
and the !!
operator makes it return its non-null value, and an NPE occurs:
The !!
operator is particularly useful when you are confident that a value is not null
and there’s no chance of getting an NPE, but the compiler cannot guarantee this due to certain rules. In such cases, you can use the !!
operator to explicitly tell the compiler that the value is not null
.
Nullable receiver
You can use extension functions with a nullable receiver type, allowing these functions to be called on variables that might be null
.
By defining an extension function on a nullable receiver type, you can handle null
values within the function itself instead of checking for null
at every place where you call the function.
For example, the .toString()
extension function can be called on a nullable receiver. When invoked on a null
value, it safely returns the string "null"
without throwing an exception:
In the example above, even though person
is null
, the .toString()
function safely returns the string "null"
. This can be helpful for debugging and logging.
If you expect the .toString()
function to return a nullable string (either a string representation or null
), use the safe-call operator ?.
. The ?.
operator calls .toString()
only if the object is not null
, otherwise it returns null
:
The ?.
operator allows you to safely handle potential null
values while still accessing properties or functions of objects that might be null
.
Let function
To handle null
values and perform operations only on non-null types, you can use the safe call operator ?.
together with the let
function.
This combination is useful for evaluating an expression, check the result for null
, and execute code only if it's not null
, avoiding manual null checks:
Safe casts
The regular Kotlin operator for type casts is the as
operator. However, regular casts can result in an exception if the object is not of the target type.
You can use the as?
operator for safe casts. It tries to cast a value to the specified type and returns null
if the value is not of that type:
The code above prints null
because a
is not an Int
, so the cast fails safely. It also prints "Hello, Kotlin!"
because it matches the String?
type, so the safe cast succeeds.
Collections of a nullable type
If you have a collection of nullable elements and want to keep only the non-null ones, use the filterNotNull()
function:
What's next?
Learn how to handle nullability in Java and Kotlin.
Learn about generic types that are definitely non-nullable.