Magnus' blog


Eliminating NullPointerExceptions in Java

In C#, nullable reference types must be explicitly declared. For example, a string is not nullable, but a string? is. By introducing this concept, the developer is forced to consider null for any nullable reference. This concept also exists in languages like Kotlin, a modern JVM language that is fully interoperable with Java. Unfortunately, in Java, all reference types are nullable by default. This post is meant to be practical advice for getting rid of NullPointerException in existing codebases.

The nuisance of NullPointerExceptions

Anyone acquainted with an unfamiliar codebase and trying to debug NullPointerExceptions knows why they’re so annoying. The NullPointerException will point to a specific source-code line number where the exception occurred, but it is rarely the origin of the null value and almost never obvious where the null came from. If the problem occurs in a running environment it can be even harder to debug, since it is not always easy to recreate the circumstances. This makes NullPointerException especially hard to deal with.

@NotNull annotations

A common approach to enforce non-null constraints is to use a @NotNull annotation on fields that cannot be null. This can be a useful way to specify nullability and helps with internal null checks where the compiler can give warnings. However, this annotation is usually provided by tooling and runtime NullPointerExceptions can still occur. They can still be useful in an exposed model, as they are typically enforced on deserialization, but internally in a codebase they are not enforced.

A useful tool here is to switch to a stricter version of these annotations which is @lombok.NonNull. Lombok’s version of the annotation will actually generate the source code to throw an exception at the exact place a null value occurs. This makes Lombok’s version of the NullPointerException much easier to debug, because it occurs in the exact place an unexpected null value occurred. For example:

public class User {
    @NonNull
    String name;
}

Optional values

Recognising this problem, Java has already introduced an Optional value, which has special JDK-level guarantees. This is highly recommended to be used in places where null is expected. It forces the developer to account for null values. With static analyser tools it can even recognise an unchecked call to get() which could throw an exception if empty.

Deserialization frameworks like jackson supports Optional wrapped values, which makes it easier to specify a null value is expected from an external client. For example:

public class UserRequest {
    Optional<String> name;
}

Conclusion

Wrapping these approaches together, a good strategy for eliminating NullPointerExceptions is to wrap nullable values in Optional and annotate non-null values in Lombok’s @NonNull. This way any occurrence of an unexpected null will be caught early and be easily debuggable. It can be painful when introduced, as bugs surface faster than normal, but over time the investment will drastically help improve the stability of a solution.

View next or previous post: