Using the Java Optional right, finally

Norbert Spiess
3 min readMay 26, 2020
Filled? Or empty? Why not handle both well?

The Java Optional. Many have tried to conquer this strange beast. Many went awry on their quest.

Yet it is my favourite underdog that got introduced with Java 8, six years ago.

The problem to solve

What is the purpose of the Optional?

First: to provide a representation for a possible value. Following functional paradigms that got introduced with the Stream API.

Second: to avoid these nasty null checks everyone complains about:

String value = getPossibleValue();
if (value != null) {
// do something
} else {
throw new IllegalArgumentException();
}

The solution to null checks?

To my surprise, Optionals still get used by many developers in a really similar way to the null check. Instead of checking for null, the presence gets evaluated:

Optional<String> optional = getPossibleValue();
if (optional.isPresent()) {
String value = optional.get()
// do something
} else {
throw new IllegalArgumentException();
}

The benefit? Not existing.

Even worse, you now have a line of code more to unpack the content that was not necessary before.

The “better” solution

How would I use the Optional in this case? Simple. By leveraging the methods it provides:

String value = getPossibleValue()
.orElseThrow(() -> new IllegalArgumentException())
// do something

Short, concise, does what it’s supposed to do. And the best: it’s readable.

So… what can you do with it?

The Optional class comes with many useful methods. More get added over time with new Java versions. Here is an overview for the current state (as of Java 14) with basic examples:

since 8

.orElse(“x”) → provide a default value in case the Optional is empty.

int val = Optional.of(24)
.orElse(42);

.orElseGet(() → “x”) → provide a function for a more complex default value. Like objects with special field values.

String val = Optional.of("x")
.orElseGet(() -> getPrefix() + "x");

.map(v → v) → transform the content of the optional.

Optional<String> val = Optional.of("x")
.map(v -> v + "y");

Caution: providing a null value inside with the lambda will change the Optional to an empty state. This might wreak havoc on your operation chain.

.flatMap(v → Optional.empty()) → replace the Optional with a different one. E.g. to change from a filled to an empty state.

Optional<Integer> val = Optional.of("x")
.flatMap(v -> Optional.of(3));

.filter(v → true) → check the content on the condition and switch to an empty Optional when it’s not met.

Optional<String> val = Optional.of("x")
.filter(s -> s.empty());

.ifPresent(v → {…}) -> execute this function if the Optional is filled.

Optional.of("x")
.ifPresent(v -> System.out.println("value: " + v));

since 9

.ifPresentOrElse(v → {…}, () → {…}) → provide functions for both cases (filled and empty).

Optional.of("x")
.ifPresentOrElse(
v -> System.out.println("value: " + v),
() -> System.out.println("empty"));

.or(() → Optional.empty()) → Replace an empty Optional with a different one.

Optional<String> val = Optional.of("x")
.or(() -> Optional.of("y"));

.stream() → Create a stream out of the content, an empty stream in case of an empty Optional.

Stream<String> val = Optional.of("x")
.stream();

since 10

.orElseThrow() → throw a NoSuchElementException in case of an empty Optional.

String val = Optional.of("x")
.orElseThrow();

since 11

.isEmpty() → counter part to isPresent().

boolean val = Optional.of("x")
.isEmpty();

Many of these functions can get chained. Some unpack the optional directly. Making it easy to write small stepped operations while avoiding complex if statements:

String name = getPossiblePerson()
.map(p -> p.getName())
.orElse("anonymous");

in contrast to

Person p = getPossiblePerson();
String name;
if (p != null) {
name = p.getName();
} else {
name = "anonymous";
}

I personally like to provide Optionals as return value of a function. Especially if I want to handle the empty case somewhere else. That way unnecessary Exception paths get avoided while having a well defined API.

Play around with the methods. See how they fit your use cases.

Avoid the get() method like a disease.

And who knows, maybe you end up with slightly cleaner code than before.

--

--

Norbert Spiess

Software engineer, craftsman apprentice, coffee geek, board gamer by heart