Hitchhiker’s guide to Java Optional

Zalán Tóth
5 min readAug 28, 2022

--

In software development the most frequent error is the null reference. Even its inventor, Tony Hoare called it a billion-dollar mistake.
Many modern languages are null safe by nature, but as Java is mature and backward compatibile is maintained, the language does not offer null safety at language level.

Besides this Java offers a workaround called Optional. We can achieve null safety using this wrapper class wisely. In this article we will get to know its capabilities and how to use it properly.

Basic usage

First we define a sample domain model We are going to use it through our examples.

Let’s look at a null reference error. Define a reference as null and call a getter method on that.

As car variable does not refer to any objects, when we call the year method on the variable we’ll get NullPointerException.

Exception in thread “main” java.lang.NullPointerException: Cannot invoke “com.zlrx.article.optional.domain.Car.year()” because “car” is null
at com.zlrx.article.optional.NullPointerExceptionExample.main (NullPointerExceptionExample.java:10)

Java 8 has brought us the Optional wrapper class which tries to handle this problem. It can either contain a value or signs us the lack of it.
Optional is immutable. Once it was instantiated, we cannot change it’s value

In the following example we are creating Optional from null value using the ofNullable factory method. It accepts either a value or null. This is the most used generator for Optional besides the Optional.empty() which produces the same result as null would have been passed to the ofNullable method.

There are multiple functions we can use for retrieving the wrapped value. For example the orElse returns with the value if exists or with the passed default if not. In the example above the result would be the default value as the original string was null .

Of course we can check if the value is missing or not. The isPresent method returns true when the value is not null inside the wrapper.
In case the value presents we can safetely call the get() for retrieving the value. This method throws NoSuchElementException if the value is missing.

We can also use the ifPresent high ordered function which accepts a function as input parameter and executes it when the value presents or skip it. The ifPresentOrElse method accepts an instance of the Runnable interface as second argument and executes it in case of missing value.

When to use it and when not

Use it mainly as the return value of the public methods of classes, when the value goes back can be null. In this way a missing null check by the client won’t ended in NullpointerException. Use null in private scope only and not in public methods.

There are many cases when Optional is not so effective and the overuse of it can cause unclean, overcomplicated source code. Let’s see some examples.

  • Never use Optional as class variable. It isn’t serializable.
  • Never use it as input parameter of a method. There are multiple problems with it. The caller must wrap the value which is inconvinient. The Optional reference can also be null so we have to check it anyway.
  • Never use it for wrapping collections directly (Lists, Sets, Maps..). Use empty collection instead of null and Optional.
  • Don’t use it as the return value of a private method. It increases the complexity and most of the time the caller method should unwrap it anyway.

Some examples for this bullet list. Never use Optional in this way.

Now let’s look at an example where it was used in the proper way.

The database simulator class randomly retrieves a Car object or null. The private methods of the example use the plain Car object only as it would be both performance degradation to wrap in and out the object from Optional constantly for every operations. Also it would make the code less readable. But as the public method returns with Optional, the client of the API gets the result object wrapped so we force them towards null safety.

Constructing Optional and Retrieving its value

There are three factory methods we can use for creating Optional instance.

Create Optional

The ofNullablewas discussed earlier. We can indicate the lack of value using the empty method as well. The of function only accepts values different from null. When we try to pass null it throws NullpointerException.

Retrieve value

We’ve also discussed get() previously. The orElse method returns with the passed default in case of missing value. orElseGet is similar but we can use Supplier instance as input parameter, which produces default value on fly. At last orElseThrow - as its name indicates - throws predefined exception if the value is null.

Transformation

There are several useful methods we can use to ‘manipulate’ the Optional (actually these are creating new instances).

filter accepts Predicate and return with the current object when it meets with the requirements or empty if not.
The value can be modified by the map method. In this example we transform the string value to an integer which represents the length of the aforementioned string.

flatMapaccepts mapper function which has Optional return type. If we used the map function we would get back Optional<Optional<T>>type as the result. flatMap extracts the value so the result will be simply an Optional<T> where T is a generic type. In this example the flatMap method calls the mapper function from another class (Mapper) ,which returns with an Optional value on its public method, as it is the best practices.

The last example shows how we can combine these operations. Filter the value if its length is greater then five, then write it twice after each other and then calculate its length. (=14)

Null check chain

Checking null on object deep inside object chains is painful. We have to test every objects in order to avoid NullpointerException. There are multiple approaches exist for this, like multiple embedded if statements or chaining null tests using &&. In the following example we would like to retrieve the piston of a car but as you may see, we have to do four null tests. These null checks would work just fine but they are complex and ugly. Let’s take a look how we can use the Optional wrapper class for chain null checks in a nice way.

Optional’s map function will return as empty if any objects in the chain or the value itself is null. This prevents us to make mistakes on null check ordering and get NullpointerException. Also it is much more readable and easier to debug.

We can call the stream method on the Optional which converts it to a regular java stream. For detailed explanation please read the following article.

Source code on GitHub

--

--