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.
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
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
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.
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
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.
There are several useful methods we can use to ‘manipulate’ the Optional (actually these are creating new instances).
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
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.
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