This is the first in a multipart series that I am tentatively calling Making Your Java Code Not Suck.
A big mistake
The null reference was (arguably) invented (for statically typed languages, at least) in 1965 by the computer scientist Tony Hoare (of Quicksort fame) for the ALGOL W programming language. Since then, this invention has been replicated in many other languages, including Java.
For his part, Dr. Hoare refers to his invention as his "billion dollar mistake". He does not mean this as hyperbole or exaggeration. He literally means that he believes that a billion dollars is a pretty good ballpark figure for the amount of real damage that has resulted from errors caused by null references in the past half a century.
Why null
is bad for you
The main problem with null references is that they undermine the type system. Any given reference can either point to an object of the specified type or nothing at all, and it’s entirely up to the developer to keep track of whether any given value might be null. Runtime errors result if the developer neglects to check for null when it is necessary, and unnecessary null checks result in useless and confusing code.
So what’s the solution?
Some languages, such as Kotlin and Ceylon, attempt to wrangle null references by adding special syntax to indicate what variables are "nullable". Other languages provide special types, such as Haskell’s Maybe
and Scala’s Option
, that act like immutable containers of zero or one element. Both approaches force developers to deal with missing values explicitly, under penalty of a compile-time error if you forget (rather than having to wait for a NullPointerException
at runtime). Java 8 takes the latter approach, providing the generic java.util.Optional
class.
You obtain an empty instance by calling Optional.empty()
, and you obtain an instance that has a value by calling Optional.of(value)
. When interacting with code that uses null
instead of Optional
, you can also convert a nullable value to a (not null) Optional
by calling Optional.ofNullable(valueOrNull)
, and you can convert it back to a possibly-null value by calling its orElse
method with an argument of null
. When receiving an instance of Optional
, you can either use an if
conditional to call its isPresent()
method to check whether it has a value (which can be obtained using its get
method), or you can lambdas with other methods to specify actions to be performed if and only if the value is present.
For earlier versions of Java, the Guava library provides a similar class, also named Optional
. Its methods are named slightly differently, but it works in more or less the same way as the Java 8 Optional
.
And why is this any better?
Using the Optional
class for method and constructor parameters makes it obvious which values are, well, optional, without the need for the caller to consult documentation or examine the source code. For return values, it makes it clear that a particular method might not return a value. Take the following query service interface as an example.
public interface UserLookupService { Optional<User> findUserByName(UserName username); List<User> findActiveUsers(Optional<Duration> maxIdleTime); }
Even without any documentation or an implementation to inspect, it is immediately obvious that findUserByName
will return an empty Optional
(rather than null
or throwing an exception) if it cannot find the specified user, and it is also clear that the maxIdleTime
parameter of findActionUsers
is not a required value. Futhermore, if you are diligent about always using Optional
and not null
throughout your codebase, you can be reasonability certain that null
is not a valid argument to pass to either method, and that neither method will ever return null
(and doing so would be considered a bug).
The bottom line
I strongly encourage all Java developers to banish null
completely from their projects. Always use Optional
to represent potentially missing values. Wherever you must interact with code that deals in nulls, always immediately wrap the nullable value in an Optional
, and convert back to a nullable value only precisely when needed, such as in the following example.
final Map<Key, Value> keyValuePairs = getKeyValuePairs(); final Optional<Value> valueForKey = Optional.ofNullable(keyValuePairs.get(key)); if (valueForKey.isPresent()) { // do stuff with value } else { throw new MissingValueException(); }
Do this even if you are going to just pass the value along to other code that accepts nullable values. Packing a value in an Optional
and then immediately unpacking it may seem like a waste of effort. However, it makes it immediately obvious to anyone reading your code (including you, coming back to the code a month later after having forgotten all the details) that the value in question might not exist, and that you’re passing it between two pieces of code that deal in null
. Don’t worry about the performance cost of creating an instance of Optional
and then throwing it away. Constructing very small, short-lived objects on the JVM is very cheap, and in many cases the compiler can optimize them away altogether.
Caveat emptor
The only other place where null
may legitimately appear is in initialization logic, where it may show up as a temporary state of a field that has not been initialized yet (e.g., because the constructor of the enclosing object hasn’t finished executing yet). If a null
appears at any other time, it should be treated as if the containing scope is in an illegal state, and therefore a likely bug.
Resist temptation!
You may be tempted to forgo the ceremony associated with wrapping a nullable value in an Optional
when that value is only going to be immediately checked for "null-ness" (or "non-null-ness") and then forgotten, such as in the following case:
if (foo.bar() != null) { // do something }
However, letting any null creep into your code yields a slippery slope towards chaos and confusion, since it then becomes unclear if a given piece of code that lacks a null check is really not nullable, or if the null check was simply forgotten. The above code should therefore be written as:
if (Optional.ofNullable(foo.bar()).isPresent()) { // do something }
It’s a bit wordy, but it clearly flags the foo.bar()
call as returning a possibly-null value, and it instills anyone reading your code with confidence that you’ve been diligently managing any possible sources of null
. A few extra keystrokes is a small price to pay for the piece of mind and code clarity that is attained by ensuring that all optional values are diligently and consistently captured as instances of Optional
.
Precondition checks
To avoid letting null
creep into your code inadvertantly in a way that violates your code invariants, it is important to include precondition checks at the top of any public method or constructor that works with non-primitive parameters. This is especially important for parameters that get stored as fields, since a null
that slips in via one of those might hang around until a much later method call, violating that method’s invariant to never return null
.
Java 8 introduced the java.util.Objects
class, which provides static methods for performing common tasks on objects. These include a method named requireNonNull
, which throws a customizable NullPointerException
if its argument is null
. For earlier versions of Java, Guava’s Preconditions.checkNotNull
method works in the same way. Examples of appropriate use of each are shown below.
public final class BigPoint implements Serializable { private final BigDecimal x; private final BigDecimal y; private final BigDecimal z; // Java 8 public BigPoint(final BigDecimal x, final BigDecimal y, final BigDecimal z) { this.x = Objects.requireNonNull(x, "x"); this.y = Objects.requireNonNull(y, "y"); this.z = Objects.requireNonNull(z, "z"); } // Java 6, Java 7 public BigPoint(final BigDecimal x, final BigDecimal y, final BigDecimal z) { this.x = Preconditions.checkNotNull(x, "x"); this.y = Preconditions.checkNotNull(y, "y"); this.z = Preconditions.checkNotNull(z, "z"); } public BigDecimal getX() { return x; // guaranteed non-null } // more methods....
Note that some people find more verbose error messages than simply the parameter name. However, I find that the combination of NullPointerException
with the name of the thing that was null
is sufficient information to understand exactly what the problem is without getting too wordy.
Also, you will likely find it useful to use static import
so that you can just call the null-check methods without qualifying them with their class names, but I left the class names in the above example for the sake of clarity about which class was being used in each case.
Epilogue
To reiterate, all active Java projects should migrate to banning null
from their codebases as soon as possible, replacing them with the java.util.Optional
class (falling back on Guava’s Optional
for projects using Java 6 or 7). Doing so will improve the reliability of the code, make it easier to debug (since any appearance of null
indicates a bug), as well as make it for anyone attempting to maintain the code or use its interfaces to understand which values are required and which are optional. Half a century later, we should all be doing our best to limit the damage caused by null references to just a billion dollars.