Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Null Safety: Calling Java From Kotlin

DZone's Guide to

Null Safety: Calling Java From Kotlin

Let's dive into Kotlin's baked-in null safety and the steps involved in calling Java code from Kotlin while taking advantage of Kotlin's capabilities.

· Java Zone ·
Free Resource

Java-based (JDBC) data connectivity to SaaS, NoSQL, and Big Data. Download Now.

The JVM already provides a safety net in the form of bytecode verification, buffer overflow, type safety, etc., but 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 a Null Pointer Exception. Nevertheless, we can still encounter NPEs by:

  • Invoking external Java code, which in turn can throw NPEs.
  • Using the !! operator
  • Explicitly throwing NPEs
  • Using an uninitialized this in a constructor (data inconsistency)

In this post, we will focus on how to take advantage of null safety when invoking Java 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 a platform type, we will receive a compilation error. E.g.

Compilation error - trying to explicitly declaring platform type in the program


From the Kotlin compiler perspective, it is a type that can be 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 code from Kotlin, null checks are relaxed due to platform types. The way to leverage the existing null safety mechanism is by representing platform types as an actual Kotlin type (nullable or non-nullable). Digging into the source code of Kotlin's compiler, one can find various flavors of the nullability annotation that aims to achieve this. Let's see an example on how to use them.

We have a class called Message.java

public class Message {
    private final String greetingMessage = "Hello ";
    public String getEchoMessage() {
        return greetingMessage;
    }
}


When we invoke getEchoMessage() from Kotlin, the compiler infers it as a platform type. The infer type String! means the variable message may or may not have a String value. For this reason, the compiler cannot enforce our 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.

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 the default option.
  • When.MAYBE/NEVER: type may be nullable.
  • When.UNKNOWN: type is resolved as a platform type.

E.g.

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 Codebases: 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 behavior 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 annotations such as @NonNullableApi, it is mandatory to annotate it with both @TypeQualifierDefault and a JSR-305 annotation such as @Nonull, @Nullable, @CheckForNull, etc.

E.g.

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 fields.
  • ElementType.METHOD: for methods return types.
  • ElementType.PARAMETER: for parameters values.

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

@NonNullableApi
package api;

import annotation.NonNullableApi;


After that, we may override the default behavior (if needed) for a particular class/method parameter.

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 a specified file for different build tool).

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 mismatchYou can find the complete source for this post here.

Spring Framework 5 + Null Safety

Spring Framework 5 introduced null safety in their codebase (see this and this). A 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 element nullability.

However, there is an ongoing discussion that aims to cover them.

Connect any Java based application to your SaaS data.  Over 100+ Java-based data source connectors.

Topics:
kotlin ,java ,spring 5 ,null safety ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}