Using Optional — Nowhere, Somewhere, or Everywhere?

Nicolai Parlog
97 Things
Published in
3 min readJul 19, 2019

--

Java 8 introduced Optional for a very precise use case: to allow the continuation of Stream pipelines without triggering a NullPointerException. This is NPE-safe:

Stream.of(2, 4, 6)
.filter(this::isOdd) // resulting stream is empty
.findFirst() // returns empty Optional instead of null
.ifPresent(this::handleFirstOddNumber) // not executed

Nonetheless its use has been hotly debated. Why? And what conclusions should you draw from the discussion? To answer that, let’s look at the three-and-a-half camps of Where to Use Optional.

Camp #1: Never use Optional!

Some consider Optional’s API verbose (all that wrapping!), inviting misuse (null is an Optional), and not beneficial over explicit null-handling. It’s not serializable and various frameworks still don’t support it. It also makes stack traces harder to debug, hampers performance with additional dereferencing, and the many new instances increase memory consumption.

In summary, it’s a train wreck and you should never use it unless forced to. If that happens, unwrap quickly and move on.

Camp #2: Use Optional as a return value (in limited cases)

Indeed, Optional is not serializable, long-lived instances increase memory consumption, and (un)boxing it when passed as a method argument is verbose. That’s not its use case, though! Optional was designed as a return value and, if used conscientiously, its disadvantages all but disappear: serializability doesn’t matter; instances are short-lived so they rarely make it to the heap; its functional API makes operating on missing values very comfortable.

So never use it for instance variables or method parameters, and only return it where null is particularly error-prone.

Camp #2½: Use Optional as a return value (always)

This is the half camp. Its argument is very similar to the former with the addition that returning null is always error-prone, so always return Optional instead of null.

Camp #3: Use Optional everywhere!

Optional’s API isn’t perfect, but it’s pretty good and it beats out explicit null-handling with ease. Framework support has improved and where it’s lacking it can usually be plugged in manually — same goes for serializability. The performance argument applies only after benchmarks were missed and profiling showed Optional-using code to be the culprit.

So there’s no strong argument against using Optional, but a good one for it: The problem with null isn’t NPEs, it’s that null encodes two disparate cases. It ends up representing both purposeful and accidental absence, and debugging NPEs is mostly figuring out which case you’re in. Optional fixes that by using the type system to express a value’s expected absence. If used consistently in all those cases, every null is clearly a bug.

Hence the recommendation: Design carefully to avoid optionality. Where it remains, always use Optional, whether as return value, method argument, or field. This simplifies your system by prohibiting null as a legal state.

Where to pitch your tent?

In the end, it’s up to every team to decide how to use Optional. I’m decidedly in the everywhere-camp, but if you’re undecided, limited return — following the opinion of the JDK developers behind Optional — is a good default. Also keep in mind that it’s easier to relax such a guideline than to make it stricter. Whatever you decide, though, every team member should follow the same rule or you end up with the worst of all worlds, plus edit wars and frustration.

--

--

Nicolai Parlog
97 Things

Nicolai is a #Java enthusiast with a passion for learning and sharing — in posts & books; in videos & streams; at conferences & in courses. https://nipafx.dev