Consistent null values handling in Java

Sergiy Yevtushenko - Oct 12 '19 - - Dev Community

Handling of null values is often considered a weak spot in Java. There are several reasons for it.

Most frequently mentioned issue is famous NullPointerException, although there is no clear justification why this is an issue. After all, this is just a sign of the problem, but not the problem itself.

Actual problem is deeper. Those who have experience writing code with C or C++ know this issue much better. While coding in C/C++ engineer should always keep in mind countless nuances related to access safety, undefined behaviors, memory allocation, object copying/moving, and so on and so forth. All these numerous details create constant "pressure", consume precious engineer brain resources and result to slower development speed. Even hardcore C/C++ proponents can't deny this fact.

Similar problem exists with null values in Java.

Java often considered verbose language. That's true. This is back side of its explicitness. Almost every intent in Java can be (and usually is) explicitly expressed in code. By adding some extra text we in turn get immediately accessible context, which don't need to be carried in head while reading Java code. The null values break this rule. They don't manifest themselves in the code.

The null values may appear for various reasons. It can be default initialization, value returned from some method or even explicitly assigned null value. Keeping in mind that sometimes null's may appear, checking method documentation or code for possibility to return such value - all these creates "pressure" very similar to described above for C/C++.

What can be done to solve this issue? How remove that constant "pressure" which affects our productivity?

Of course, there are more than one way to solve it.
We can:

  • Add @NotNull (or alike) annotations. Does, basically nothing, except adding some (false) feeling that null values are somehow handled.
  • Establish strict rules and check every value which potentially be null. It's working solution, but in expense of polluting code with countless null checks. And, well, nobody is perfect, we can make mistakes and compiler will not help us.
  • Invent own language. Authors of Kotlin done just that. Again, this is working solution, but it's overkill, as for me. Nevertheless it has one important thing: nullable and non-nullable types are distinct and clearly expressed in the code.
  • Use functional programming approaches - Maybe monad in some form.

Last point is what I'm going to discuss in more details.

Optional: good, bad, ugly

Introduced in Java 8 Optional class is targeted to handle missing values instead of null. To some extent it is a Java implementation of Maybe monad (but not completely, see below).

While some can consider this as just fancy way to work with null values, it actually has far more important benefit: explicit expression of intent. Once you wrap field, parameter or variable into Optional you immediately making your intent explicit and clear to the reader. In other words - you're adding that missing context. In the same time compiler starts seeing difference, you're not alone anymore.

So, by using Optional engineer kills three birds with one stone:

  • Makes potentially missing value explicit in code and removes "pressure" from reader.
  • Enables compiler to control access to the value.
  • Makes handling of null values convenient.

Unfortunately Optional has its own issues.

As mentioned above, Optional "to some extent" implementation of Maybe monad. It makes reverences to imperative world and has externally accessible value. By calling get() method you can try to retrieve it. But if you do, it may throw NullPointerException. Which defies whole purpose of the Optional - be safe container for potential null (i.e. missing) values.

Perhaps this is why some smart heads suggests that "using Optional for fields and parameters is bad practice" and static analysis tools show warnings for such usage of Optional.

The solution might be to write your own version of Maybe and use it instead. There are several different implementations around. I'm using one from my Reactive Toolbox Core library.

Summary

The Optional/Option/Maybe enables Java engineers to use following convention:

  • Every variable/field/parameter with plain type is not null and never can be.
  • Every variable/field/parameter whose type is wrapped into Optional/Option/Maybe - potentially can be empty.
  • If some external library/call can return null value, result should be immediately wrapped into Optional/Option/Maybe.

Without any additional efforts strict following the convention above enables writing Java code which:

  • never throws NullPointerException and have all null values handled properly.
  • clearly and explicitly distinguishes nullable and non-nullable values in code and lets compiler enforce this distinction at compile time.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .