Null Safety – Calling Java From Kotlin?

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.

Screen Shot 2017-10-26 at 09.28.52.png

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:

notation.png

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

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.

Screen Shot 2017-11-01 at 06.06.17.png

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.

Now the Kotlin compiler will no longer infer it as a platform type.

Screen Shot 2017-11-01 at 06.13.07.png

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.

Screen Shot 2017-11-01 at 08.11.24.png

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.

The following values of ElementType can be used with @TypeQualifierDefault(...) :

  • ElementType.FIELD – for fields
  • ElementType.METHOD – for methods return types
  • ElementType.PARAMETER – for parameters value

Next we need to apply our annotation for a particular package by placing it inside package-info.java. E.g.

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).

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

Screen Shot 2017-11-01 at 05.33.26.png

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

Screen Shot 2017-11-01 at 05.32.10.png

Build output -Xjsr305=warn results in successful build (with warning)

E.g.(-Xjsr305=strict )

Screen Shot 2017-11-01 at 05.46.25.png

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

Screen Shot 2017-11-01 at 04.51.17

Build output:-Xjsr305=strict results in build failure

 

Screen Shot 2017-11-01 at 05.54.44

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.

 

Advertisements