Better Null-Handling With Java Optionals

 · 7 min
Photo by Alisa Anton on Unsplash

Until Java 8, there was no better way to handle null references than checking your variables at every turn. The new class java.util.Optional<T> changed that significantly.

But first, some history and comparison with other languages.


A Billion-Dollar Mistake

The inventor of the null reference apologized in 2009 for its creation:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965.
At that time, I was designing the first comprehensive type system for references in an object-oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement.
This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
– Sir Charles Antony Richard Hoare at QCon London in 2009

Programming languages are dealing very differently with the mistake. Many of them have a proper way to handle null, directly integrated into the language itself.

Groovy, Swift, Ruby, Kotlin, C#, and more support a safe navigation operator.?, or &. in the case of Ruby.

Other languages, like Objective-C, or Clojure, decided to ingrain better null handling. Time for some different approaches to null handling.


How do other languages handle null?

Swift

In Swift, the null reference is called nil. The language has excellent support for optional types and nil handling. With its help, we can safely call properties, and even chain them fluently together:

swift
let name: String? = articles?.first?.author?.name

In combination with the null coalescing operator, we can make it even better and don’t have to deal with an optional type:

swift
let name: String = articles?.first?.author?.name ?? "n/a"

Clojure

This article is about Java, so let us look at an example from another JVM language. Clojure also prefers nil for the null reference.

But instead of providing a safe navigation operator to work around the problem, Clojure decided to try to get rid of the NullPointerException entirely.

Even though nil is kind of Java’s null, it’s just another value type. Only one instance of nil exists, and it tests to false.

It’s still possible to encounter a NullPointerException, but it’s way harder if you use Clojure the way it’s intended. With nil being a value representing nothingness instead of being nothing, it’s way easier to handle and incorporate into your code.

Objective-C

Being a Smalltalk-inspired language, Objective-C doesn’t call methods or fields, it sends messages to objects. And sending a message to nil will not raise an exception, the message will be discarded silently.

This can be great, you don’t have to null-check everything. But it’s also bad because you might not realize that a message wasn’t answered.


Java Optionals

With the release of Java 8, a straightforward way of handling null references was provided in the form of a new class: java.util.Optional<T>.

It’s a thin wrapper around other objects, so you can interact fluently with the Optional instead of a null reference directly if the contained object is not present.

Creating Optionals

Optionals are created in different ways:

java
// CASE 1: We definitely know there's a value
Optional<String> withValue = Optional.of("definitly a string");

// CASE 2: We 're not sure
Optional<String> maybeValue = Optional.ofNullable(null);

// CASE 3: We're sure there's no value
Optional<String> noValue = Optional.empty();

Every use-case has its own reason for existence:

Case 1: We know/need a value
Even though Optionals are a great way to deal with NullPointerExceptions, what if we actually need one? Just because the API design uses Optionals, there might be a need to throw an exception in specific cases. That’s what Optional#of(...) is for, which doesn’t accept null-values.

Case 2: We’re not sure
The most used form of creation. No null check is performed.

Case 3: We’re sure there’s no value
Optional.empty() returns a static final field. No need to create a new Optional every time it’s empty, and we know about it for sure.

Checking for values

The easiest way to check is Optional#isPresent():

java
Optional<String> withValue = Optional.of("definitly a string");

if (withValue.isPresent()) {
    System.out.println("There's definitly something there!");
}

Optional<String> noValue = Optional.empty();
if (noValue.isPresent() == false) {
    System.out.println("No, nothing...");
}

To not check against false all the time, Java 11 introduced Optional#isEmpty().

Get the actual value

Only checking if a value is present or not is not much of a help, we want to use it! The simplest way is Optional#get():

java
Optional<String> withValue = Optional.of("definitly a string");
String value = withValue.get();

Optional<String> noValue = Optional.empty();
String willCrash = noValue.get();
//                 ^^^^^^^^^^^^^
// this throws NoSuchElementException!

Congratulations, we’ve just thrown an exception! Even though it wasn’t a NullPointerException, there must be a better way than checking isPresent() every time we use Optionals.


A Better Way

In real code, we don’t just want to get the inner value. Instead, we want to work with it. Thanks to lambda expressions, we can do this easily and lazily.

Before we got Optionals, the usual way to handle values was something like this:

java
String result = this.buildResult();
if (result == null) {
    return "n/a";
}

return result.toUpperCase();

With Optionals, we can compact this code into an easily-readable single-method call chain:

java
return Optional.ofNullable(buildResult())
               .map(String::toUpperCase)
               .orElse("n/a");

Optional#map(...) will only be called if the Optional actually contains a value, if not, Optional#orElse(...) is called.

Null Coalescing

Java doesn’t support a null-coalescing operator like other languages:

kotlin
val value = doSomething() ?: "default value"

With Optionals, we can use the same pattern by using Optional#orElse(...) without any additional actions in between:

java
String value = Optional.ofNullable(doSomething())
                       .orElse("default value");

Actions and Alternatives

The methods of java.util.Optional can roughly be separated into two groups: actions and alternatives.

Actions

Alternatives

Methods providing an alternative in the case of no value starting with or:


Primitive Data Types

With java.util.Optional<T> being a generic type, how should we handle primitive data types like int or long?

Of course, primitives can never be null, but that’s not necessarily a good thing. Being able to represent nothingness, compared to not-initialized/default value, can be an advantage.

One way to deal with primitives is not using them and relying on their corresponding object wrapper class, e.g., int -> Integer. But this will only produce overhead from auto-boxing/unboxing.

Until Project Valhalla, with its improved value type handling, is available to us, we can use specialized Optional classes to use the power of Optionals with primitive data types:

There are still some primitive data types missing, but it’s a start. Also, they lack all action methods, except ifPresent(LongConsumer consumer).

Caveat

Optionals are just Java objects like any other object, so they might be null themselves. If we design an API and decide to use Optionals, we must not return null for an Optional, always use Optional.empty().

This has to be enforced by convention, the compiler can’t help us out, except with additional tooling and @NonNull annotations.

Other standard Java features that we shouldn’t use directly with Optionals are about equality: equals(...) and hashCode() are first about comparing the Optional, and only second about the value.

The documentation warns us that the results might be unpredictable.


Conclusion

Optionals are a great way to handle null references in a more concise way. We can replace many if-else constructs and even incorporate lambda expressions.

Instead of using null, we should design our APIs with Optionals, if possible and applicable. Even primitives are usable to a certain extent, so there are no more excuses for using Optionals in our next project.


A Functional Approach to Java Cover Image
Interested in using functional concepts and techniques in your Java code?
Check out my book!
Available in English, Polish, and soon, Chinese.

Resources