Null Safe(r) Java with NullAway
After working on Kotlin projects for over seven years, I recently started on a Java project again.
One of the things I missed most was the fact that Kotlin explicitly supports nullability as part of its type system. Within the first day I ran into a NullPointerException.
Luckily, we can now add compile-time null checks to Java as well, making Java null safe(r)!
How does it work?
NullAway is a plugin for the Java compiler which checks that method return values and method parameters that are not explicitly declared nullable, are in fact not null.
For example, given this code snippet:
package com.jdriven.blog.nullaway;
public class NullTest {
public static String foo() {
return null;
}
public static void printMessage(String msg) {
IO.println(msg.toString());
}
static void main() {
printMessage(foo());
}
}
This will compile just fine, but will throw an NPE when run. Ideally we want the compiler to verify that foo() returns null, and printMessage expects the msg String to be non-null. Using NullAway we can do just that!
Adding the NullAway compiler plugin to your project
In this example I’ll use Maven. Let’s add NullAway to our pom.xml:
<project>
<properties>
<jspecify.version>1.0.1</jspecify.version>
<nullability.plugin.version>0.5.2</nullability.plugin.version>
</properties>
<dependencies>
<!-- provides annotations for used by NullAway -->
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- Configures the ErrorProne and NullAway plugins -->
<groupId>am.ik.maven</groupId>
<artifactId>nullability-maven-plugin</artifactId>
<version>${nullability.plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>configure</goal>
<goal>generate-package-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
NullAway needs the ErrorProne compiler plugin and about 40 lines of configuration in your POM file. Luckily, there is the nullability-maven-plugin which does this for you.
Now we can start using NullAway in our project!
Add JSpecify annotations to the code
Of course, the NullAway plugin needs to know if a method is allowed to return or receive a null value.
To do this, we first need to tell NullAway that the code in our package is annotated with JSpecify annotations. This can be done by either annotating a class with @NullMarked, or by added the @NullMarked annotation to the package-info.java file:
@NullMarked
package com.jdriven.blog.nullaway;
import org.jspecify.annotations.NullMarked;
Next, we can start annotating our example code:
package com.jdriven.blog.nullaway;
import org.jspecify.annotations.Nullable;
public class NullTest {
public static @Nullable String foo() {
return null;
}
public static void printMessage(@Nullable String msg) {
IO.println(msg.toString());
}
static void main() {
printMessage(foo());
}
}
When we now try to compile the project, compilation will fail with the following error:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.15.0:compile (default-compile) on project blog-nullaway: Compilation failure
[ERROR] src/main/java/com/jdriven/blog/nullaway/NullTest.java:[11,22] [NullAway] dereferenced expression 'msg' is @Nullable
Now, when we fix the bug in our application, compilation will succeed! The compiler just saved us from an NPE on production!🎉
public static void printMessage(@Nullable String msg) {
if (msg != null) {
IO.println(msg.toString());
}
}
Nullable entries in collections
NullAway even supports null checks in Java collections! For example, if we want a method to return a List of `String`s, where some
of the values might be null, we can annotate the type argument of a list:
public static List<@Nullable String> getStrings() {
return List.of("foo", null, "bar");
}
public static void processStrings(List<@Nullable String> strings) {
strings.stream()
.filter(Objects::nonNull)
.forEach(e -> IO.println(e.trim()));
}
If we would remove (or forget 😬) the @Nullable annotation on the String type argument in processStrings(), compilation would fail with the following error:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.15.0:compile (default-compile) on project blog-nullaway: Compilation failure
[ERROR] /src/main/java/com/jdriven/blog/nullaway/Lists.java:[27,45] [NullAway] incompatible types: List<String> cannot be converted to List<@Nullable String>
The compiler now makes sure that we cannot pass a list with possible null values into a method which does not support this!
Framework support
Spring Boot 4 and most sub-projects have their API’s fully annotated with JSpecify annotations!, so you’ll never have to guess if a value might be null or not. Since Kotlin also uses JSpecify annotations for interopability with their type system, more and more projects are annotated with JSpecify.
Conclusion
NullAway is a great tool to reduce bugs in your code that could have easily been prevented. I think it’s a no-brainer for all (new) projects where Kotlin isn’t an option.
The source code for the examples is available on Github.