- 7 minutes read

Most Java developers believe that Oracle introduced Optional to protect you from running into NullPointerExceptions.

That's wrong on almost all levels. Let's have a look why Optional has been invented, and why so many programmers believe it solves the NPE problem, and why that's wrong. It's just a misconception you can't evade when you're coming from a language like Haskell.

Is it wrong to use Optionals?

First of all, this article isn't about flame wars. When we're talking about using if vs. Optional, there's no right or wrong. Both approaches work for the CPU, and while most developers cope better with the traditional if statement, there are also many developers who consider chaining more readable.

The only thing that's not debatable is performance. CPUs have been designed with traditional if statements in mind (or something similar), so checking conditions and branching to a new line is a use case they are optimized for. My in-depth performance analysis shows that there's a factor 10 performance penalty of using Optional. When the code runs very often - something like ten thousand times - the JIT compiler does an excellent job optimizing the code. Even then, there's a noticeable 20% performance disadvantage.

If other words, if statements are good for your carbon footprint. From this perspective, replacing if statements by Optional is evil. But it's not wrong. It just consumes more energy and time.

Run the test yourself!

This article is about when and when not to use Optional. Conditional code execution is only a small part of it. But because it's such a popular trend to replace if statements by Optional, let's spend one final paragraph on it.

Ask your team to write an algorithm without using if statements. If they write it fluently, go for Optional.orElseGet(), Optional.filter() and Optional.map(). But I bet interesting discussions will pop up. What the difference between flatMap() and map()? Can we use orElse(), or does it have to be orElseGet()?

During my research I've found an interesting example to make you think:

String name = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");

That's two lines using flatMap(). Why is the third line different? Why does a simple map() do the trick in the third line, but not in the first two lines? And why do we need three times a lambda expression (or a method handle), but the last line is happy with a simple String?

I suspect using Optionals for such simple tasks adds a lot to the mental effort required to read the code. Mind you, human language embraces if and else, too: "If the weather is fine, I'm hiking in the mountains. Otherwise, I'm going to the cinema".

Introducing the Maybe datatype

Do you know Haskell? That's a functional programming language with a unique but powerful programming style. Among other things, it has two data types called Maybe and Just.

Imagine your corporate address list. Everybody has a first name. That's Just String. But not everybody is willing to tell you how old they are. So their birthdate is Maybe DateTime. It may have a value, but maybe it's just Nothing.

Does that sound familiar? Yes, so far it's exactly the same as Optional. The difference is that Maybe is part of Haskell's type system. Optional is a class in a library.

This means that even if the syntax is more or less the same, the semantics are wildly different. The Haskell compiler knows the difference between Maybe and Just. The Java compiler does not. So it misses many mistakes the Haskell compiler detects.

Optional in Java

Optional has been invented to take the sting out of dealing with null values.

But that doesn't mean declaring a variable as Optional eliminates the dreaded NullPointerException. Quite the contrary:

Optional firstName = null; Optional lastName = Optional.ofNullable(null); System.out.println(lastName.get());

You still have to deal with null values. It's just a convention that Optionals never are null. That's a powerful convention, one that works most of the time because the Optional keyword makes you aware of the problem. But still, it can be broken. Every Optional is an object, so null is a legal value.

Plus, even if everybody recognizes the convention, you've just replaced the NullPointerException with a NoSuchElementException. That gets you nowhere.

It's the same old story. Inventing a library to solve a problem that needs to be addressed in the language only makes things worse.

What Oracle says

Here's the catch. While it's true that Optional has been invented to take the sting out of dealing with null values, it has not been invented to get rid of the NullPointerException. Stuart Marks, member of the Java language design team, clearly says they've invented Optional to deal gracefully with null values in streams. He also say they were very surprised when developers started using Optional in many other scenarios.

According to Oracle,

Optional is primarily intended for use as a method return type where there is a clear need to represent "no result," and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.

But then, Oracle is a big company. Big enough to send contradictory signals. They're also trying to convince us it's a good idea to get rid of the if statement.

Beauty is in the eye of the beholder.

When and how to use Optional

I haven't answered the question yet, have I? I've found two well-thought articles I try to distill. I'm talking about A look at the Optional datatype in Java and some anti-patterns when using it and Nothing is better than Optional.

  • Generally speaking, it's a good idea to use Optional in streams. That's a lot better than using null value.
  • But it's even better to avoid using empty values in streams altogether. Just filter them away. The nice thing is that filter() copes with null values.
  • Wrapping primitive data types in Optionals is tremendously inefficient. Don't do that, it just ruins your carbon footprint. Primitive data types can't be null, anyways.
  • The real problem is that the average hurried programmer always forgets to add the necessary checks. Optional reminds them. But an annotation @NonNullable does that, too, at a lower price. It's even supported by your IDE. I've coverd the nullness annotations in depth in my previous article.
  • If you're using an Optional for a method parameter, think twice. The method must check whether the optional parameter is there. Maybe it's better to write two different methods. Not always, but optional parameters are a code smell.

Wrapping it up

There's nothing wrong with Optional. Use it. But use it wisely. If you feel obliged to use it, think twice. Do you want to use it because it's fancy? Because your peers told you so? Because it's fashionable? Because someone told you it's faster?

These are the wrong reasons to use Optional. They tend to convert your clean code into a messy ball of mud.

You must find your programming style. Every once in a while, jot down an algorithm using Optional, and the same algorithm using a traditional coding style. Return a week later. Or ask a friend. Which version reads more naturally?

Optional is a powerful tool, so you'll find many use cases. It's just not as powerful as Haskell's Maybe data type. In Haskell, using Maybe isn't optional. Java operates at a lower abstraction layer. It's closer to the CPU, so using Optional usually is optional.


Comments