Nothing is better than the Optional type

(和訳, Japanese translation)

JDK 8 introduced the Optional class, a container that is either empty or contains a non-null value.

Optional has numerous problems without countervailing benefits. Optional does not make your code more correct or robust. There is a real problem that Optional tries to solve, and this article shows a better way to solve it. Therefore, you are better off using a regular possibly-null Java reference, rather than using Optional.

The Web and blogosphere are full of claims that the Optional class solves the problem of null pointer exceptions. This is not true. Changing your code to use the Optional class has these effects:

When your code throws a NullPointerException or NoSuchElementException, the underlying logic bug is that you forgot to check all possibilities when processing data. It's best to use a tool that guarantees you don't forget. That helps you to understand and fix the underlying problem.

(These criticisms aren't specific to Java's implementation of Optional. Other languages that claim to have solved the problem have also merely transformed it into a different manifestation.)

The Optional class isn't all bad: if you have to use Optional, it defines methods for reducing code clutter when dealing with possibly-present data. However, you should still avoid the Optional class.

The rest of this article expands on the above points. Here is the outline of this article:

Changing the exception that is thrown doesn't fix the defect or improve the code

Consider a Cartesian point class with fields x and y (the discussion is equally applicable to getter methods):

    class Point { int x; int y; }

Because a Java reference may be null, there is a danger of a NullPointerException whenever you dereference it, as in myPoint.x below:

    Point myPoint;
    ...
    ... myPoint.x ...

If myPoint is null and does not refer to a real point, then myPoint.x throws a NullPointerException and the program crashes.

Here is a way to write this code using Optional:

    Point myPoint;
    Optional<Point> myOPoint = Optional.ofNullable(myPoint);
    ...
    ... myOPoint.get().x ...

If myOPoint does not contain a real point, then myOPoint.get().x throws a NoSuchElementException and the program crashes. This isn't any better than the original code, because the programmer's goal is to prevent all crashes, not just NullPointerException crashes!

It is possible to prevent the exception and crash by doing a check first:

    if (myPoint != null) {
      ... myPoint.x ...
    }

or

    if (myOPoint.isPresent()) {
      ... myOPoint.get().x ...
    }

Again, the code is very similar and Optional is not superior to using a regular Java reference.

Optional is prone to misuse

Optional is a Java class, and therefore the variable myOPoint of type Optional<Point> might be null. Thus, the expression myOPoint.get() might throw a NullPointerException or a NoSuchElementException! You really need to write

    if (myOPoint != null && myOPoint.isPresent()) {
      ... myOPoint.get().x ...
    }

You can express complex data using the distinction between a null Optional value, a non-null Optional with data absent, and a non-null Optional with data present, but this is complex and confusing. Alternately, you could decide to forgo those possibilities, and to be careful and disciplined about not letting variables of type Optional be null. However, if you trusted yourself about that you wouldn't have had any null pointer exceptions in the first place and you wouldn't be using Optional.

Optional is a wrapper, so uses of value-sensitive operations are error-prone, including reference equality (==), identity hash codes, and synchronization. You need to remember not to use these.

Stuart Marks gives a longer list of rules to avoid mistakes in the use of Optional.

Optional clutters your code

With the Optional library, your code is more verbose:

No one of these is a deal-breaker, but overall it is cumbersome and ugly to use.

For some concrete examples, see http://www.codeproject.com/Articles/787668/Why-We-Should-Love-null and search for the word “cumbersome”.

Optional introduces overhead

Optional introduces space overhead: an Optional is a separate object that consumes extra memory.

Optional introduces time overhead: Its data must be accessed via an extra indirection, and calling methods on it is more expensive than Java's efficient test for null.

Optional introduces coding overhead: You have to deal with its incompatibility with existing interfaces that use null, with the fact that it is not serializable, etc.

The real problem: remembering to perform checks

A NullPointerException or NoSuchElementException occurs because the programmer forgot to perform a check that data is present, via != null or .isPresent(), before trying to use the data.

Some people say that the main benefit of Optional is that with Optional, you are less likely to forget to perform the check. If true, that is good! Nonetheless, it's not enough to make a problem somewhat less likely, in a few places where Optional was written. It is better to have a guarantee that eliminates the problem everywhere.

One way would be to force the programmer to always perform the check before accessing the data. (This is what some programming languages do, by offering a destructuring or pattern-match operator.) This would result in many redundant checks in places where the check has already been performed or it is not really needed. (Think about how some programmers react to checked exceptions, which force the programmer to do a check whether the programmer wants to do it or not.)

A better approach is to have a tool that guarantees that you do not forget to check, but that also doesn't require redundant checks. Luckily, such a tool exists! It is the Nullness Checker of the Checker Framework.

The Nullness Checker works at compile time. It examines every dereference in your program and requires that the receiver is known to be non-null. That could be because you have already checked it, or because it was generated by a source that never produces null. The Nullness Checker has a powerful analysis that keeps track of whether a reference might be null. By comparison to use of Optional, this reduces the number of warnings and the number of redundant checks that are needed. By default, the Nullness Checker assumes that references are non-null, but you can specify possibly-missing data by writing @Nullable Point.

Writing @Nullable Point is analogous to Optional<Point>, but with significant benefits:

The Nullness Checker achieves the goal of guaranteeing that you never forget to check for the presence of data, anywhere in your code, in a way that is less disruptive than use of Optional.

Since every programmer error related to null references is possible with Optional, and Optional makes new types of errors possible, someone should write a compile-time Optional Checker to prevent those errors. It could share much of its logic with the Nullness Checker. The Checker Framework manual has a chapter about how to create a new checker. In the meanwhile, save yourself some pain and just use nullable references.

Optional's handy methods

Although Optional tends to clutter your code, if you have to use Optional, it provides methods that reduce the clutter. Here are two examples:

You could easily create methods of your own that do the same things for regular Java references. However, there are some conveniences to using the versions in Optional:

Other methods such as filter and flatMap are described in the API documentation for Optional. These eliminate much of the need for calling Optional.isPresent() and Optional.get(), which is a great benefit. However, they don't eliminate all of it, and the other disadvantages of Optional remain.

Counterarguments

Not everyone is foolish enough to claim that Optional solves the problem underlying null pointer exceptions. For instance, Oracle's JDK team does not claim this.

A general programming rule is to avoid, as much as possible, the situation that data is not present. This reduces the need to write a type such as Optional<Point> or @Nullable Point. All the arguments of this article continue to hold, wherever in your program data might not be present.

Some people suggest that programmers should use Optional sparingly, such as only on method return types and never on fields. If you use Optional less, then there is less clutter, overhead, and potential for misuse. However, the only way to eliminate Optional's problems is to not use it. Furthermore, if you use Optional less, you obtain fewer of its benefits. Null pointer exceptions are important no matter what their source or syntactic form, so the best solution is one that handles every reference in your program, not just some of them.

The main argument for Optional on return types is, "It's too easy for clients to forget to handle the possibility of a null return value. Optional is ugly and in your face, so a client is less likely to forget to handle the possibility of an empty return value. Everywhere else, programmers should continue to use normal references, which might be null." By contrast, my suggestion is that programmers should continue to use normal references everywhere in their program, but use a tool to ensure that at every possible location — not just at method calls — the program checks against null when needed. The Nullness Checker is a tool that serves this purpose, without the downsides of Optional.

If you find a style of using Optional that solves part of your problems, at acceptable cost to you, then good for you — use it.

Summary

In conclusion, using regular Java references is better than special libraries such as Optional.


Thanks to Stuart Marks for comments on an earlier draft of this article.


Back to Advice compiled by Michael Ernst.

Michael Ernst