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
Camp #1: Never use
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)
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
Camp #3: Use
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.