Java Optionals
Java Optionals êŽë š
Thereâs an old saying in computer science that null was the âbillion dollar mistake.â Itâs actually a quote from Tony Hoare, the creator of the null reference.
Itâs easy to understand the hate for null
. Weâve all run into null reference exceptions in just about any language weâve used. But as annoying as they can be, itâs easy to wonder how they can be avoided. After all, itâs inevitable that sometimes you have a variable pending assignment. If not null, how would the absence of a value be represented in a way that prevents a developer from creating exceptions when interacting with that non-value?
Optionals
This post is about the Optional type, which is a common way programming languages protect developers from null references. The idea is, the optional type gives you a box that can be empty (null) or have a value in it. Along with some APIs to safely deal with these possibilities.
This is a concept that exists in many languages. Swift has a particularly elegant implementation, which is integrated into various language-level features. But for this post, weâll look at Java, which added an Optional type in version 8 of the language. Along the way weâll run into a few other modern Java features.
The Old Way
Letâs say we have a basic Person
 type:
record Person(String name, int age){}
Records were added to Java 14, and are essentially simplified classes for objects which are mainly carriers of data.
Letâs say we want to declare a variable of type Person
. Normally weâd write:
Person p;
Then carefully check for null before referencing any properties.
if (p != null) {
System.out.println(p.name);
}
If you failed to check, youâd be greeted with something like:
Exception in thread "main" java.lang.NullPointerException: Cannot read field "name" because "p" is null
at Main.main(Main.java:13)
Your First Optional
To use the Optional type, make sure to do the proper import:
import java.util.Optional;
Then declare your variable:
Optional<Person> personMaybe;
If you donât have a value to assign, you can indicate that by assigning Optional.empty()
personMaybe = Optional.empty();
Or, you can assign an actual value with the of
 static method.
personMaybe = Optional.of(new Person("Mike", 30));
If you try to get cute and assign null this way:
personMaybe = Optional.of(null);
Youâll be greeted by an error immediately.
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:209)
at java.base/java.util.Optional.of(Optional.java:113)
at Main.main(Main.java:12)
If you truly have a value that might be null, which you want to safely and correctly assign to an optional, you can use the ofNullable
method:
// where x is of type Person that could be null
personMaybe = Optional.ofNullable(x);
Using Your Optional
Itâs one thing to have an optional type that can hold a value, but how do you use the value? The crudest, most dangerous way to access the value contained in your optional is with the get
 method
System.out.println(personMaybe.get().name);
The get()
 method returns the value thatâs in the optional if there is one, or if the optional is empty, it will promptly error out.
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:143)
at Main.main(Main.java:21)
Thereâs an isPresent()
 you can call, to check for this.
if (personMaybe.isPresent()) {
System.out.println(personMaybe.get().name);
}
Here weâre no better off than we were before. These APIs allow you to (carefully) interact with other APIs that are not coded with Optional types. In general, using get
 should be avoided where possible.
Letâs see some of the better APIs Optional ships with.
Using Optionals Effectively
If we want to use an optional, rather than carefully calling get
 (after verifying thereâs a value) we can use the ifPresent()
 method.
personMaybe.ifPresent(p -> System.out.println(p.name));
We pass in a lambda expression that will be invoked with the person
value. If the optional is empty, nothing will be done. If youâd like to also handle the empty use case, we can use ifPresentOrElse
.
personMaybe.ifPresentOrElse(
p -> System.out.println(p.name),
() -> System.out.println("No person")
);
Itâs the same as before, except we now provide a lambda for when the optional is empty.
Getting Values from an Optional
Letâs say we want to get a value from an Optional. Letâs first expand our Person
 type just a bit and add a bestFriend
 property of type Optional<Person>
.
record Person(String name, int age, Optional<Person> bestFriend){}
Now letâs say we have a Person
 optional:
personMaybe = Optional.of(new Person("Mike", 30, Optional.empty()));
Then letâs say we want to get that personâs name (and we donât want to assume thereâs a value in there). We want to store this as an Optional<String>
. Obviously we could do something ridiculous like this
Optional<String> personsName = personMaybe.isPresent()
? Optional.of(personMaybe.get().name)
: Optional.empty();
It should come as no surprise that thereâs a more direct API: map
. The map
 API takes a lambda expression, from which you return whatever you want from the object. The type system will look at what you return and fit that into an Optional of that type. If thereâs no value present in the Optional, the lambda will not be called, and youâll be safely left with Optional.empty()
Optional<String> personsName = personMaybe.map(p -> p.name);
Since records automatically create getter methods for all properties, the following would also work:
Optional<String> personsName = personMaybe.map(Person::name);
The ::
 syntax is a method reference, which was added in Java 8. As of version 10 Java supports inferred typings, so you could also write:
var personsName = personMaybe.map(Person::name);
The var
 keyword takes the meaning from C#, not JavaScript. It does not represent a dynamically typed value. Rather, itâs merely a shortcut where, instead of typing out your type, you can tell the type system to infer the correct type based on whatâs on the right hand side of the assignment, and pretend you typed that. Needless to sayâŠ
var x;
⊠produces a compiler error of:
java: cannot infer type for local variable x
(cannot use 'var' on variable without initializer)
Optionals of Optionals
So far weâve added the bestFriend
 property to our Person record, which is of type Optional<Person>
. Letâs put it to good use.
Optional<Person> personsBestFriend = personMaybe.map(p -> p.bestFriend);
Rather than use var
, I explicitly typed out the type, so weâd know immediately what was wrong. IntelliJ highlights this line as an error, and when we hover, weâre greeted by this (surprisingly clear) error message.
Required type: Optional<Person>
Provided: Optional<Optional<Person>>
The value we return from the map
 method is placed inside of an optional for us. But, here, the value we return is already an optional, so weâre left with an optional of an optional. If we want to âflattenâ this optional of an optional into just an optional, we use flatMap
(just like we use flatMap
in JavaScript when we want to flatten an array of arrays from Array.map
).
Optional<Person> personsBestFriend = personMaybe.flatMap(p -> p.bestFriend);
We can use this optional now, as we did before.
personsBestFriend.ifPresentOrElse(
s -> System.out.println(s.name),
() -> System.out.println("No person")
);
Chaining Things Together
Rather than pulling the name off of the best friend, letâs clean the code above up a bit by extracting the best friendâs name directly, and then using that. Letâs also start to use method references more, to remove some of the bloat
Optional<String> bestFriendsName = personMaybe.flatMap(Person::bestFriend).map(Person::name);
We can use this as before:
bestFriendsName.ifPresentOrElse(System.out::println, () -> System.out.println("Nothing"));
For one final trick, letâs note that Optionals have an orElse
 method. If you have an Optional<T>
, orElse
takes a value (not an optional) of type T. If the optional had a value, that value is returned. If the optional was empty, the value you provided is returned. Itâs a good way to convert an optional to a real value, while providing a default value if the optional was empty. Letâs see it in action with the code above, grabbing our personâs best friendâs name (if there is one).
String bestFriendsName = personMaybe
.flatMap(Person::bestFriend)
.map(Person::name)
.orElse("No friend found");
and now we can use this string, which is guaranteed to not be null.
System.out.println(bestFriendsName);
Wrapping up
I hope you enjoyed this introduction to Javaâs Optional type. Itâs a great tool to make your code safer and more clear.