JVM already provides a safety net in the form of bytecode verification, buffer overflow, type safety, etc.; Kotlin took this to a step further and baked null safety right into the type system. Which means we can deal with null
at compile time rather than bumping into Null Pointer Exception. Nevertheless, we can still encounter NPE by:
- Invoking external Java’s code which in turn can throw NPE.
- Using
!!
operator - Explicitly throwing NPE
- Using uninitialised
this
in a constructor (data inconsistency)
In this post, we will focus on how to take advantage of null safety mechanism when invoking Java’s code.
Java’s declarations are treated as platform(flexible) types in Kotlin. These types cannot be mentioned explicitly in the program, i.e. if we try to declare a variable of platform type, we will receive compilation error. E.g.

Compilation error – trying to explicitly declaring platform type in the program
From Kotlin’s compiler perspective, it is a type that can be used as both nullable and non-nullable. Hence, there is no syntax in the language to represent them. The following mnemonic notation can be used to denote them:
When we try to invoke Java’s code from Kotlin; null checks are relaxed due to platform types. The way to leverage existing null-safety mechanism is by representing platform types as an actual Kotlin type (nullable or non-nullable). Digging in the source code of Kotlin’s compiler one can find various flavour of nullability annotation that aims to achieve this. Let’s see an example on how to use them.
We have a class called Message.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Message { | |
private final String greetingMessage = "Hello "; | |
public String getEchoMessage() { | |
return greetingMessage; | |
} | |
} |
When we invoke getEchoMessage()
from kotlin, the compiler infers it as platform type. The infer type String! means the variable message
may or may not have a String value. Due to this reason, the compiler cannot enforce us to do null handling on platform types.

Compiler infer it as platform type, hence no null-safety
Looking at the source code of getEchoMessage()
one can spot that it always returns a non-nullable value. We can use @Nonnull
annotation to document this. E.g.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.annotation.Nonnull; | |
import javax.annotation.meta.When; | |
public class Message { | |
private final String greetingMessage = "Hello "; | |
@Nonnull(when = When.ALWAYS) //When.ALWAYS is default option and makes the annotated type as non-nullable | |
public String getEchoMessage() { | |
return greetingMessage; | |
} | |
} |
Now the Kotlin compiler will no longer infer it as a platform type.

Compiler can now infer it as kotlin.String
We can use one of the following values of When with @Nonnull:
When.ALWAYS
– type will always be non-nullable. This is default option.When.MAYBE/NEVER
– type may be nullable.When.UNKNOWN
– type is resolved as platform one.
E.g.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.annotation.Nonnull; | |
import javax.annotation.meta.When; | |
public class Message { | |
private final String greetingMessage = "Hello "; | |
@Nonnull | |
public String getEchoMessage() { | |
return greetingMessage; | |
} | |
@Nonnull(when = When.MAYBE) | |
public String getThirdPartyMessage() { | |
return fetchFromExternalService(); | |
} | |
@Nonnull(when = When.UNKNOWN) | |
public String getDummyMessage() { | |
return "foo"; | |
} | |
} |

Compiler inference for @Nonnull with different values of When
Refactoring large codebase – leverage JSR-305 support
Note: Starting from Kotlin 1.1.50 we can use custom nullability qualifiers (both @TypeQualifierNickname
and@TypeQualifierDefault
are supported, for more details see this).
Let’s say we have a package in which majority of classes have methods that:
- returns a non-nullable value.
- takes non-nullable parameters.
Rather than editing each and every source file, we can introduce package level nullability/non-nullability behaviour and only override the exceptional cases. Let see how to do it.
One can start by creating a custom annotation, let’s call it @NonNullableApi
Note: When creating custom annotation such as @NonNullableApi
it is mandatory to annotate it with both @TypeQualifierDefault
and JSR-305 annotation such as @Nonull
, @Nullable
, @CheckForNull
, etc.
E.g.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.annotation.Nonnull; | |
import javax.annotation.meta.TypeQualifierDefault; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
@Target(ElementType.PACKAGE) | |
@Nonnull | |
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER}) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface NonNullableApi { | |
} |
The following values of ElementType
can be used with @TypeQualifierDefault(...)
:
ElementType.FIELD
– for fieldsElementType.METHOD
– for methods return typesElementType.PARAMETER
– for parameters value
Next we need to apply our annotation for a particular package by placing it inside package-info.java
. E.g.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@NonNullableApi | |
package api; | |
import annotation.NonNullableApi; |
After that we may override default behaviour (if needed) for a particular class/method parameter. E.g.
public String greet2(@Nonnull(when = When.MAYBE) String name) { return String.format("%s%s !!", greetingMessage, name); }
Finally, we need to configure JSR-305 checks by passing the compiler flag -Xjsr305
in build.gradle
(or in specify file for different build tool).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
compileKotlin { | |
kotlinOptions.freeCompilerArgs = ["-Xjsr305=strict"] | |
} |
The following compiler flags are supported:
-Xjsr305=strict
– produce compilation error (experimental feature).-Xjsr305=warn
– produce compilation warnings (default behaviour)-Xjsr305=ignore
– do nothing.
Note: You can also pass those flags directly via command line in the absence of build tool.
E.g.-Xjsr305=warn

IDE view:-Xjsr305=warn
results in compilation warning when supplying null value for a parameter

Build output -Xjsr305=warn
results in successful build (with warning)
E.g.(-Xjsr305=strict
)

IDE view:-Xjsr305=strict
results in compilation error when supplying null value for a parameter

Build output:-Xjsr305=strict
results in build failure

IDE view: compiler detected type mismatch
You can find the complete source for this post here
Spring Framework 5 + null safety
Spring framework 5 introduced null-Safety in their code-base (see this & this). Bunch of annotations like @NonNullApi, @NonNullFields, etc. were introduced inside the package org.springframework.lang
. These annotations use the similar approach described above i.e. they are meta-annotated with JSR-305. Kotlin developers can use projects like Reactor, Spring-data-mongo, Spring-data-cassandra, etc. with null-safety support. Please note, currently null-safety is not targeted for:
- Varargs.
- Generic type arguments.
- Array elements nullability.
However, there is an ongoing discussion which aims to cover them.