Java 8 Optional — A practical use case

Raluca Diaconescu
METRO SYSTEMS Romania
4 min readJul 24, 2017

How many times in your developer life have you encountered a NullPointerException? I would bet your answer to this question is “too many to count”.

In Java, null values are a convenient way to model “the absence of a value”. But it was also called “a billion-dollar mistake” by its own inventor, Tony Hore, when making an estimation of the cost incurred by fixing the null reference errors. Although intended to “to ensure that all use of references could be absolutely safe, with checking performed automatically by the compiler”, it instead lets us examine an object’s properties only to find out at run time that we are not examining an actual object but a null pointer that will throw the famous NullPointerException. Trying to handle this in code increases and complicates the code (as we will see next).

Moreover, from a theoretical point of view, it creates a hole in an object oriented system: null values can be assigned to any type reference, so when it is propagated from one part of the application to another, it gives you no hint on what it was initially.

Let’s assume in a bank we have the following data model:

What can happen if we execute the following code for a customer who only has a credit card?

As you probably guessed, our friend java.lang.NullPointerException will be thrown. The same would happen if the person would no longer have an account.

A quick solution would be to insert if statements in this code:

This code inserts multiple if blocks, attempting to catch all possible null values but this is an error-prone strategy.

Trying to fix this type of problems, Java8 creators inspired themselves from functional languages and introduced a new class (java.util.Optional<T>) for modeling an optional value.

In the above example, if we know a BankCustomer might or might not have an account, then this account should be modeled as an Optional<Account>. When an account is present, the Optional class is just a wrapper over it. If it missing, the Optional class exists, but it contains nothing. From a business point of view, using the Optional signals the developers that a missing value is allowed here.

The reworked classes would look like the following:

In the code above you can see the Optional objects are initiated using the Optional.empty() function which returns an empty Optional instance on which we can work without the NullPointerException problem. Actual account/card objects are added using the Optional.of() method which returns an Optional with the specified present non-null value. If a null object is given to the Optional.of(), a NullPointerException is immediately raised, at the moment of the initialization. This helps track the incorrect behavior at the source instead of waiting for the code to actually access a null value.

There is also the method Optional.ofNullable() which allows us to put a null object in an Optional and not raise an error at the initialization time. But if we call the get() method on such a value, we will still have an error, so this only reproduce the null usage problem which we are trying to solve.

The card id and amount are kept as normal objects, because they are mandatory from a business point of view (a card without an id or an amount has no meaning) and a missing value means the data is wrong and something should be done about it.

It is important to understand that Optional class is not a universal solution to all null values, but it aims at helping the developer create more self-explained code that communicates quickly which values are mandatory and which are not.

Out checking function can now be rewritten as following:

We are using here the flatMap and map constructs available on streams in Java 8. Because Optional is also a collection with one or no element, we can use the stream methods on it.

The map function lets you convert an object to something else using a transformation function. The result is an Optional object around the expected result type.

In our case, we transform an Optional<Account> to an Optional<Optional<Card>> and then the Optional<Card> to an <Optional<Double>> using each object’s getter functions.

The flatMap function does the same as map but also flattens multiple layers of Optional into only one: from Optional<Optional<Card>> to <Optional<Card>>.

The orElse() function allows as to generate a default value if any object from the entire flow is missing (we have no account, we have no credit card).

Comparing the second version of our ownsMoney function to the first one, we can see how the Optional objects help us handle possible missing values. We re-wrote the function in an easy to understand statement instead of increasing the complexity with if statements.

--

--

Raluca Diaconescu
METRO SYSTEMS Romania

Senior Java developer at METRO Systems Romania, passionate about solving problems and finding innovative solutions.