How to properly use Optional to cleanse your Java code of NullPointerExceptions

Justin Harjanto
7 min readFeb 12, 2020

--

Since the creation of null in the 1960s by Tony Hoare, null or some form of it whether it be nilor None, has crept its way into many languages today as a convenient way to signify a non-existent object. In doing so, it has caused problems from making developers pull their hair out trying to track down a null reference, to even taking systems in production down.

What’s the problem with null?

Take for example the following snippet:

Excluding the DAO being null, there are three points of failure just from null in this snippet:

  • Since Long is not a primitive, homeListingId is allowed to be null. A call to getExpensiveHome(null) will successfully type check in Java which results in homeListingDAO.get(null) to either throw an exception or return null.
  • homeListing could now be null if the id isn’t in the database resulting in homeListing.getPrice() throwing another NullPointerException.
  • homeListing could also not have a price yet, in which case listingPrice is now null which results in listingPrice > EXPENSIVE_HOME_THRESHOLD to throw a NullPointerException.

In fact, this method doesn’t make the client’s life any easier either. From just looking at the method signature, the client doesn’t know if the returned Home is null or not. Even in the event that the given home price surpasses the EXPENSIVE_HOME_THRESHOLD, homeListing.getHome() can still return null . That’s… a lot of places it can fail… how do we fix it?

We can nullify these problems with rewriting this a little with some simple null checks!

Ta-da! By switching from the wrapper Long class to the primitive long, we’ve made it so that isExpensiveHome(null) won’t type check with the compiler before this code ever runs. Moreover, we fixed all the other null issues that were pointed out above.

While it works, this isn’t the most elegant solution. The actual business logic that the code is doing is straightforward. But, by introducing all these null checks, we’re fighting Java’s inability to give us compile time guarantees about the types and are forced to deal with them at run time instead. As a programmer using a statically typed language, the presence of null subverts the advantages that a type checker is supposed to give us as any Object in Java could just be a null reference.

Save me! What is Optional and how can we use it?

From the Oracle Java docs:

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true andget() will return the value.

Another way to reason about Optional is that Optional is like a List but instead of having an indefinite number of elements, it can only contain at most one element. It can be either empty represented by Optional.empty() or filled with an Object.

Sweet, let’s take a stab using Optional then!

This… doesn’t look much better. It just looks like we’re wrapping the result of things we know are going to return null in Optional. This just adds unnecessary complexity to the code! Optional sucks! I’m going back to my null checks!

Don’t go just quite yet! Like Java 8 Streams you can also use your favorite higher order functions,map and filter.

How does this help us?

To see how these operations can be useful in the context of Optional, let’s take a look at their implementations. Let’s look at map first:

public Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return Optional.empty();
} else {
return Optional.ofNullable(mapper.apply(value));
}
}

This is great! This snippet here is effectively doing the null check for us, and if the value is not null, then the function will be applied and a new Optional will be returned which may or may not be empty. If it is empty, any subsequent map operations will keep on returning Optional.empty() and not throw any errors.

Now let’s look at filter:

public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) {
return Optional.empty();
} else {
return predicate.test(value) ? this : empty();
}
}

It’s the same thing here! If the value isn’t present, then the function passed in will never be applied. Otherwise, the test predicate is applied on the value inside of the Optional and if it succeeds, it will return itself, otherwise the empty optional.

Using these functions and what we know about Optional, we can rewrite the method neatly and null-safe as such:

This above snippet accomplishes a lot in such few lines! Not only is the business logic more clear from the absence of nullchecks, but now as a client of this method, it is easy to see that getExpensiveHome has the possibility to return no Home . This now forces the client to deal with the scenario where no Home exists which is far better than having an NullPointerException (NPE)!

Psh this seems like a contrived example. Show me more code!

The snippet below demonstrates retrieving a list of HomePhotos from a given propertyId. In this example, there can be multiple HomeListing s per property and it will choose the first one to get the photos.

Yuck! Four null checks makes this really difficult to read. Moreover, the filtering out of low quality photos makes this a great candidate for using the Stream API. Let’s see how Optional and Streams are used in conjunction with one another:

Is this really doing the same thing as the original snippet?

You bet! First, the propertyId is ensured to not be null. Then, propertyRoDAO::getHomeListings fetches the List<HomeListing> and is converted to a Stream<HomeListing>. The findFirst method is called on the Stream.

Notice that instead of calling the standard map, we’re calling flatMap instead. In this context, findFirst returns an Optional<HomeListing>. If we used map here, we’d be left with an Optional<Optional<HomeListing>> . To extract the HomeListing out of the inner Optional, we’d have to do an isPresent check with a filter to ensure that the HomeListingis there and then map Optional::get to get the HomeListing so that we’re back to our original type of Optional<HomeListing>. flatMap does this all in one step.

After a HomeListing is fetched, the List<HomePhoto> is now retrieved, filtered for non null and for only high quality photos. The Stream is collected back up into a List and now we’re left with the type Optional<List<HomePhoto>>.

As a general rule, it’s good practice to not wrap container types such as Streams and Collections in Optional but instead to return the empty collection instead to remove the unnecessary burden for the client to unwrap the Optional to get to the container. With that in mind, rather than returning an Optional<List<HomePhoto>>, we’ll go a step further and use the Optional::orElse method which will return the underlyingList<HomePhoto>, or return the default value passed in if not present, in this case, the empty ArrayList<HomePhoto>.

Hurray for no null!

All of this sounds great… but can’t Optional be null too?

Unfortunately because Optional is not actually a primitive, it has the ability to be null as well effectively defeating all of the advantages of using Optional in the first place. It’s ultimately up to the developers and perhaps even static analysis tools to ensure that null doesn’t get returned from a method that has a return type of Optional. Even though there might be no guarantee on whether a given method that returnsOptional will be null or not, it’s still safe to construct an Optional locally to take advantage of map , filter , and flatMap to reduce the amount of manual null checking that has be done.

Optional is great, but how do I deal with null in other languages?

Today, programming languages vary in their ways of dealing with the billion dollar mistake.

Scala and F# have taken a similar approach to Java and have their own form of Optional not much different than the API that Java provides. In Kotlin, the type system actually makes a distinction between references that can hold null values and those that can’t denoted using a ? that’s appended at the end of the type (ex: String? denotes a String that can be null where as String with no ? cannot be) giving developers compile time guarantees about the scope of their null references. Both C# and Kotlin have a variant of the null propagation operator which enables safe chaining of calling methods on objects, e.g:

obj?.getObjTwo()?.doSomething();

is the same as:

if (obj != null) {
if (obj.getObjTwo() != null) {
obj.getObjTwo().doSomething();
}
}

Unfortunately, for many dynamically typed languages, since the type checker runs at run time rather than compile time, when code is executed, there are no guarantees about the types of all the references, rendering a solution like Optional to be ineffective. Ruby has taken steps to include the null propagation operator to avoid null checks and even Python has a draft in the works to introduce safe navigation as well.

For some languages, null isn’t even a problem. In a handful of functional programming languages such as Haskell, there isn’t even the concept of a null value, developers are forced to use the equivalent to Optional to denote that there’s a possibility for the given value to not be there.

Closing Thoughts

null has become a staple in many languages, and it’s not going away anytime soon. Despite the fact that null still causes major issues in production systems today, it’s reassuring to see that developers are actively trying to find ways to introduce features to better mitigate the holes that these type systems have.

So remember, the next time you have to fix an NPE in production, remember there are solutions out there rather than doing the manual null checks!

--

--

No responses yet