Part 2: Avoiding Null-Pointer Exceptions in a Modern Java Application

Jens Christian B. Madsen
Destination AARhus-TechBlog
10 min readAug 21, 2020

There’s got to be a better way — modern null handling

In the previous post - part 1, we saw how in some cases null is a necessary evil and that there are right and wrong ways to use it. In this post, I will cover how you can best work safely with null values and discuss some of the solutions, we use in the Columna project to avoid errors and elegantly handle null.

Return Optional over null

While I hopefully convinced you in the previous post that returning null is correct in some cases. In this post, I am going to tell you that you shouldn’t if you can avoid it. Whenever you add a new method to the code base that may return nothing, you should strive to use the Optional type instead of null. The Optional type was introduced in Java 8 and is a generic container for any object. It was introduced to give developers a way to indicate that a method might return nothing, which you previously couldn’t (at least not without annotations — more on that below). There are two types of Optional objects: Those that hold a value and those that don’t. The former is created with the value to hold, whereas the latter is simply a static singleton. The typical use case has the following structure:

if (checkSomeCondition(someValue)){
return Optional.of(someValue);
} else {
return Optional.empty();
}

To see why an Optional-based method signature is superior, assume a method with the following signature:

public Admission getAdmission(Patient patient)

The signature only tells you that it returns an object of type Admission. As discussed above, null corresponds to every type. Unless we analyze the implementation of the method, we have no way to know if null is returned. In contrast, a method returning an Optional looks like this.

public Optional<Admission> getAdmission(Patient patient)

Since it only makes sense to return an Optional, where an empty value must be returned, the method must return an empty value. Whenever you get an Optional from a method, you know that there’s a possibility of receiving nothing and must handle the value appropriately.

A returned Optional value is handled similarly to null: You check if a value is provided, and if so, you do something with it. A call to a method that returns null typically looks like:

Admission admission = getAdmission(Patient patient);
if (admission != null) {
// do stuff with admission data
}

With an Optional value the corresponding code would look like this:

Optional<Admission> admissionOptional = getAdmission(Patient patient);
if (admissionOptional.isPresent()) {
Admission admission = admissionOptional.get();
// do stuff with admission data
}

While it is easy to forget checking for null in the first example, an Optional type is safer as it makes it obvious that there may or may not be value provided. If you call get() on an empty Optional value, you’ll get an exception, just as you would by calling any method on a null value. You always need to call isPresent() followed by get() in the most basic use case, but the API offers multiple other alternatives, where a default value can be returned from the call if no value is present.

Note that the Optional type only really makes sense to use for return values. For instance, in the case below, assigning to Optional in the loop below has no benefits over null, in fact, using null for the initial assignment is more elegant and your IDE should have no problem detecting if you’re missing a null-check, when the variable is used in the same method in which it is declared.

Optional<Admission> admissionOptional = Optional.empty()for (Admission admission : admissions) {
if (admission.isActive()) {
admissionOptional= admission;
break;
}
}
if (admissionOptional.isPresent()) {
// Do stuff
} else {
// Do something else
}

However, if we instead had a method that returned the active admission, we’d be in Optional territory:

for (Admission admission : admissions) {
if (admission.isActive()) {
return Optional.of(admission);
}
}
return Optional.empty();

What about using Optional for parameters?

SonarLint says no as it is better to provide an actual value than an Optional that may or may not have a value, as we would have to check for both of these inside the method as well as ensuring that somebody didn’t provide null instead. However, you shouldn’t mix null and Optional. It defeats the whole purpose of an Optional value.

Don’t ever think about doing this or providing null as the argument for a parameter with an optional value.

Optional<Object> optional = null; // BAD!

If you do something like this, you’ll also get an NPE.

Optional<Object> optional = Optional.of(null);

If you have some value that possibly holds null and you want to convert it to an Optional, you need to call Optional.ofNullable() instead, and it will return an Optional-wrapped value if the argument is not null; otherwise, it returns an empty Optional. If you need to do the reverse operation, that is, convert a possibly empty optional into null or the value it holds, use orElse(null) that returns the provided argument if no value is present.

Assist the IDE with Nullable/Nonnull annotations

For most codebases not passing null around at all is a utopia. Another way to make both your intent clearer and to aid the type system in helping you is to use annotations for parameters and return values that indicate, where null is and isn’t expected or supposed to be used. Many frameworks enable this (see this thorough summary on SO), but the basic idea is the same: A parameter can be tagged either as Nonnull meaning that it is never expected to have a null value or as a Nullable value, meaning that this variable may hold a null-value. Fields and return values may similarly be tagged.

One example of one such method signature:

@Nullable
public Admission getAdmission(@Nonnull Patient patient) {
...
}

The signature now reveals that the method is not intended to be called with a Patient-parameter with a null-value, and the IDE will give you a warning for doing so. We are also informed that the method might return null, and you will similarly get a warning when using the return value of the method.

While new methods should use Optional over null in return values, extensive refactoring of existing methods is usually outside the scope of feature development. However, just throwing a tag on whenever you examine whether a value ever becomes null as part of writing new code is a helping hand to anybody, who uses the code in the future, and will help to prevent NPEs.

Use Objects.requireNonNull() to prevent null-parameters at run-time

Annotations only matter at compile-time and don’t affect the behaviour of the running program. If you want to enforce at runtime that a method is not called with a null argument, you should use the Objects.requireNonNull(T obj, String message) method. Objects is a static utility class for performing basic operations like equals, hashCode and toString in a null-safe manner.

To defend a method against null arguments, pass the relevant parameter(s) to the requireNonNull() method along with a descriptive error-text. If the parameter is null, it results in an NPE with the error text. Otherwise, the method just returns the parameter. It is typically used in constructors, like below:

public Foo(Bar bar, Baz baz, Qux qux) {
this.bar = Objects.requireNonNull(bar, “bar must not be null”);
this.baz = Objects.requireNonNull(baz, “baz must not be null”);
this.qux = qux; // qux is allowed to be null
}

Now you might ask yourself, if the purpose is to prevent a parameter from being null because it can unintentionally result in an NPE. Is throwing an NPE intentionally before that happens really any better?

It seems like the same thing. And from the users’ point of view, it will look the same — an error happens. But when diagnosing the cause of the problem later, the latter is much preferable. The problem with NPEs is that they don’t tell you what variable was null; they only provide a line number. In a line, where multiple methods are called on objects, it can be hard to diagnose which one is the culprit.

Say you get an NPE in a line of code like below:

computeStuff(bar.getValue(), baz.getAnotherValue(), qux.getSomeObject().getStuff());

Which variable was null? Both bar, baz and qux could have been null. If qux was not null, the object that qux returns could have been. You are going to spend some time investigating which of these scenarios are the most likely culprit.

Throwing your own exception for an empty parameter value will immediately give away in log files where to look for an issue because your own error message gives it away.

Working with strings

I frequently see bugs, where string-generating logic fails to consider null values and as a result, “null” appears in the middle of strings in the user interface. This sneaky behaviour is due to the fact that when concatenating strings in Java, an unexpected null value does not lead to an NPE as long as no methods are explicitly called on it. If we look under the hood of the JRE, it turns out that null-values are often explicitly handled and converted into “null” values. Whenever we’re using the “+” shorthand, a StringBuilder is actually created and used to create the joined value. This means that the right-hand values of the assignments of s3 and s4 are actually equivalent:

String s1 = “hello “;
String s2 = null;
String s3 = s1 + s2;
String s4 = new StringBuilder(String.valueOf(s1)).append(s2).toString();

Both will lead to a “hello null” string. Concatenation can also be performed using s1.concat(s2), which interestingly does result in an NPE whenever s2 is null. However, the concat method seems largely out of favour, even though it performs better than StringBuilder when concatenating only a few strings, so we should generally expect null-strings to fly under the radar until they appear in the UI.

Use Objects class and StringUtils library to simplify boilerplate code

Often errors find a way into code-bases, when we don’t have a good mental model of what some code does or why it does so; maintainability is important. I often experience that string creation code is convoluted. For some reason, people tend to go wild with ternary expressions by putting them inside expressions or nesting ternary expressions in ternary expressions. It’s a very effective way to make simple logic much harder to read than it needs to be.

The ternary operator is often used to return a default value if a variable holds null, as in the example below, straight from the Columna code base

String s = (form != null ? form : "-") + " " + (nameAndStrength != null ? nameAndStrength: "");

Admittedly, it is not the hardest code to analyze, but I think we can do a lot better. In C# you have a so-called null-coalescing operator, related to the ternary operator, that explicitly checks for a null value, as in:

String s = form ?? "-" + " " + nameAndStrength ?? "-";

Where the left-hand-side value of the operator is assigned if it is not null, otherwise the right-hand side gets used. While Java lacks an operator for doing this, we can use the built-in Objects class discussed above for the same purpose. The toString(Object o, String s) method of the Objects class returns the toString() value of the first object if the object isn’t null, otherwise, it returns the provided string value.

String hyphen = "";
String s = Objects.toString(form, hyphen) + " " + Objects.toString(nameAndStrength, hyphen);

Not as nice as the C# example, but in my opinion much more readable than the original example and thus a much smaller chance that errors are introduced when working with this code.

Use StringUtils for null-safe String methods

The StringUtils library simplifies handling of null and the empty string for you — like the API says “Operations on String that are null safe.”. The library contains many common string operations with checks for null built-in. By using it, the code-base will become less cluttered as you don’t need to implement all the null-checking defensive boilerplate code, making your conditional statements easier to read. Your code-base will become more uniform when you use the library to standardize these checks and most importantly, your code will become safer in regard to null as you avoid the risk of making absent-minded mistakes by not implementing the checks yourself. In particular, aggressively guarding concatenation expressions with conditional statements based on the isBlank(s) method ensures that no unwanted “null” values (or empty strings for that matter) sneak into strings.

Final words

To summarize and put it all together:

  • Null has several valid uses; don’t be afraid of using it whenever it makes sense as part of the modelling of a given domain.
  • Minimize usage of null and don’t use it in ways where others don’t expect it — watch out at the interface between methods.
  • Don’t use null to implicitly indicate errors — be explicit and throw an exception.
  • Use method overloading and the Builder pattern to avoid passing null-values in your method calls.
  • Never assign a Collection type variable to null — use Collections.emptyList(), emptySet(), emptyMap() to create empty data structures.
  • Use Optional over null when returning empty values to let callers know to expect empty values and provide proper handling of these.
  • When you can’t use Optional: Make your intention of handling/not handling null parameters and returning/not returning null in your methods explicit with NonNull/Nullable annotations.
  • Use Objects.requireNonNull() to intentionally fail on unwanted null-parameters.
  • Be on the lookup for sneaky null-strings and use the Objects class and StringUtils to handle null elegantly.

By Jens Christian B. Madsen
Systems Developer, Systematic

About me
Jens Christian B. Madsen is a developer on the Columna clinical information system, holds master’s degrees from Aarhus University in Molecular biology and Computer Science and is a certified ethical hacker. He has contributed to books on such topics as Python, Computer Science and Linux. He enjoys heavy books, heavy metal and heavy weights.

--

--