3. Optional

NullPointerExceptions may be the most seen exceptions during Java developers’ daily development. Null reference is considered as The billion dollar mistake by its inventor Tony Hoare. Maybe null should not be introduced into Java language in the first place. But it’s already there, so we have to live with it.

Suppose we have a method with argument val of a non-primitive type, we should check first to make sure the value of val is not null.

Listing 5.1. Check null value
public void myMethod(Object val) {
  if (val == null) {
    throw new IllegalArgumentException("val cannot be null!");
  }
  // Use of val
}

Listing 5.1 shows a common pattern of writing code that takes arguments with possible null values. If a method accepts multiple arguments, all these arguments should be checked. Utility methods like Objects.requireNonNull() can help, but the code is still long and tedious. Another case is for long object references chains, e.g. a.b.c, then all objects in the reference chain need to be checked. Groovy has safe navigation operator ?., but Java doesn’t the same thing.

3.1 What’s Optional

Java 8 introduced a new class Optional<T> to solve the issues with nulls. The idea behind Optional is quite simple. An Optional object is a holder of the actual object which may be null. Client code interacts with the Optional object instead of the actual object. The Optional object can be queried about the existence of the actual object. Although the actual object may be null, there is always an non-null Optional object.

The major benefit of using Optional is to force client code to deal with the situation that the actual object that it wants to use may be null. Instead of using the object reference directly, the Optional object needs to be queried first.

Optional objects are very easy to create. If we are sure the actual object is not null, we can use <T> Optional<T> of(T value) method to create an Optional object, otherwise we should use <T> Optional<T> ofNullable(T value) method. If we just want to create an Optional object which holds nothing, we can use <T> Optional<T> empty().

Listing 5.2. Create Optional objects
Optional<String> opt1 = Optional.of("Hello");

Optional<String> opt2 = Optional.ofNullable(str);

Optional<String> opt3 = Optional.empty();

If you pass a null value to Optional.of() method, it throws a NullPointerException.

3.2 Usage of Optional

3.2.1 Simple usage

The simple usage of Optional is to query it first, then retrieve the actual object hold by it. We can use boolean isPresent() method to check if an Optional object holds a non-null object, then use T get() method to get the actual value.

Listing 5.3. Simple usage of Optional
public void myMethod(Optional<Object> holder) {
  if (!holder.isPresent()) {
    throw new IllegalArgumentException("val cannot be null!");
  }
  Object val = holder.get();
}

Listing 5.3 is similar with Listing 5.1, except it uses Optional. But the code in Listing 5.3 is still long and tedious.

3.2.2 Chained usage

Since Optionals are commonly used with if..else, Optional class has built-in support for this kind of scenarios.

void ifPresent(Consumer<? super T> consumer) method invokes the specified consumer function when value is present, otherwise it does nothing.

Listing 5.4 Example of Optional.ifPresent
public void output(Optional<String> opt) {
  opt.ifPresent(System.out::println);
}

T orElse(T other) returns the value if it’s present, otherwise return the specified other object. This other object acts as the default or fallback value.

Listing 5.5. Example of Optional.orElse
public String getHost(Optional<String> opt) {
  return opt.orElse("localhost");
}

T orElseGet(Supplier<? extends T> other) is similar with orElse(), except a supplier function is invoked to get the value if not present. getPort() method in Listing 5.6 invokes getNextAvailablePort() method to get the port if no value is present.

Listing 5.6 Example of Optional.orElseGet
public int getNextAvailablePort() {
  int min = 49152;
  int max = 65535;
  return new Random().nextInt((max - min) + 1) + min;
}

public int getPort(Optional<Integer> opt) {
  return opt.orElseGet(this::getNextAvailablePort);
}

<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) returns the value if present, otherwise invokes the supplier function to get the exception to throw.

Listing 5.7 Example of Optional.orElseThrow
public void anotherMethod(Optional<Object> opt) {
  Object val = opt.orElseThrow(IllegalArgumentException::new);
}

3.2.3 Functional usage

As Optional objects are the holders of actual objects, it’s inconvenient to use when manipulating the actual objects. Optional provides some methods to use it in a functional way.

Optional<T> filter(Predicate<? super T> predicate) filters an Optional object’s value using the specified predicate. If the Optional object holds a value and the value matches the predicate, an Optional object with the value is returned, otherwise an empty Optional object is returned.

Listing 5.8 only outputs a string when it is not empty.

Listing 5.8 Example of Optional.filter
Optional<String> opt = Optional.of("Hello");
opt.filter((str) -> str.length() > 0).ifPresent(System.out::println);

<U> Optional<U> map(Function<? super T,? extends U> mapper) applies the specified mapping function to the Optional’s value if present. If the mapping result is not null, it returns an Optional object with this mapping result, otherwise returns an empty Optional object.

Listing 5.9 outputs the length of input string.

Listing 5.9 Example of Optional.map
Optional<String> opt = Optional.of("Hello");
opt.map(String::length).ifPresent(System.out::println);

<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) is similar with map(), but the mapping function returns an Optional object. The returned Optional object only contains a value when the value is present in both the current Optional object and the mapped Optional object.

flatMap() is very useful when handling long reference chain. For example, considering a Customer class has a method getAddress() to return an Optional<Address> object. The Address class has a method getZipCode() to return an Optional<ZipCode> object. Listing 5.10 uses two chained flatMap() method invocations to get an Optional<ZipCode> object from an Optional<Customer> object. With the use of flatMap(), it’s very easy to create safe chained object references.

Listing 5.10 Example of Optional.flatMap
Optional<Customer> opt = getCustomer();
Optional<ZipCode> zipCodeOptional = opt.flatMap(Customer::getAddress)
  .flatMap(Address::getZipCode);
zipCodeOptional.ifPresent(System.out::println);

3.3 How-tos

3.3.1 How to interact with legacy library code before Optional?

Not all library code has been updated to use Java 8 Optional, so when we use third-party libraries, we need to wrap an object using Optional.

Listing 5.11 Old legacy library code
public Date getUpdated() {
  //Get date from somewhere
}

public void setUpdated(Date date) {

}
Listing 5.12 Wrap legacy library code with Optional
public Optional<Date> getUpdated() {
  return Optional.ofNullable(obj.getUpdate());
}

3.3.2 How to get value from chained Optional reference path?

Given an object reference path a.getB().getC() with getB() and getC() methods both return Optional objects, we can use flatMap() method to get the actual value. In Listing 5.13, we use flatMap() to get value of reference path a.b.c.value.

Listing 5.13 Get value from chained Optional reference path
package optional;

import java.util.Optional;

public class OptionalReferencePath {

  public static void main(String[] args) {
    new OptionalReferencePath().test();
  }

  public void test() {
    A a = new A();
    String value = a.getB()
        .flatMap(B::getC)
        .flatMap(C::getValue)
        .orElse("Default");
    System.out.println(value);
  }

  static class C {

    private final String value = "Hello";

    public Optional<String> getValue() {
      return Optional.ofNullable(value);
    }
  }

  static class B {

    private final C c = new C();

    public Optional<C> getC() {
      return Optional.ofNullable(c);
    }
  }

  static class A {

    private final B b = new B();

    public Optional<B> getB() {
      return Optional.ofNullable(b);
    }
  }
}

3.3.3 How to get first value of a list of Optionals?

Given a list of Optional objects, we can use code in Listing 5.14 to get the first value.

Listing 5.14 Get first value of a list of Optionals
package optional;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class ListOfOptionals {

  public static void main(String[] args) {
    List<Optional<String>> optionalList = Arrays.asList(
        Optional.empty(),
        Optional.of("hello"),
        Optional.ofNullable(null),
        Optional.of("world")
    );
    String value = optionalList.stream()
        .filter(Optional::isPresent)
        .map(Optional::get)
        .findFirst()
        .orElse(null);
    System.out.println(value);
  }
}

Starting from Java 9, you can use optionalList.stream().flatMap(Optional::stream) to convert an object of type Stream<Optional<T>> to type Stream<T>.

3.3.4 How to chain method invocations with return value of Optional objects in sequence?

Suppose we want to retrieve a value to be used in the client code and we have different types of retrievers to use, the code logic is that we try the first retriever firstly, then the second retriever and so on, until a value is retrieved. In Listing 5.15, ValueRetriever is the interface of retrievers with only one method retrieve() which returns an Optional<Value> object. Classes ValueRetriever1, ValueRetriever2 and ValueRetriever3 are different implementations of the interface ValueRetriever.

Listing 5.15 shows two different ways to chain method invocations. The first method retrieveResultOrElseGet() uses Optional’s orElseGet() method to invoke a Supplier function to retrieve the value. The second method retrieveResultStream() uses Stream.of() to create a stream of Supplier<Optional<Value>> objects, then filters the stream to only include Optionals with values, then gets the first value.

Listing 5.15 Chain method invocations with return value of Optional objects in sequence
package optional;

import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class ChainedOptionals {

  private final ValueRetriever retriever1 = new ValueRetriever1();
  private final ValueRetriever retriever2 = new ValueRetriever2();
  private final ValueRetriever retriever3 = new ValueRetriever3();

  public Value retrieveResultOrElseGet() {
    return retriever1.retrieve()
        .orElseGet(() -> retriever2.retrieve()
            .orElseGet(() -> retriever3.retrieve().orElse(null)));
  }

  public Value retrieveResultStream() {
    return Stream.<Supplier<Optional<Value>>>of(
            retriever1::retrieve,
            retriever2::retrieve,
            retriever3::retrieve)
        .map(Supplier::get)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .findFirst()
        .orElse(null);
  }

  public static void main(String[] args) {
    ChainedOptionals chainedOptionals = new ChainedOptionals();
    Value value = chainedOptionals.retrieveResultOrElseGet();
    System.out.println(value);
    value = chainedOptionals.retrieveResultStream();
    System.out.println(value);
  }

  static class Value {

    private final String value;

    public Value(String value) {
      this.value = value;
    }

    @Override
    public String toString() {
      return String.format("Value -> [%s]", this.value);
    }
  }

  interface ValueRetriever {

    Optional<Value> retrieve();
  }

  static class ValueRetriever1 implements ValueRetriever {

    @Override
    public Optional<Value> retrieve() {
      return Optional.empty();
    }
  }

  static class ValueRetriever2 implements ValueRetriever {

    @Override
    public Optional<Value> retrieve() {
      return Optional.of(new Value("hello"));
    }
  }

  static class ValueRetriever3 implements ValueRetriever {

    @Override
    public Optional<Value> retrieve() {
      return Optional.of(new Value("world"));
    }
  }
}

The output of the program is two lines of Value -> [hello], because ValueRetriever2 is the first retriever that returns a value.

3.3.5 How to convert an Optional object to a stream?

Sometimes we may want to convert an Optional object to a stream to work with flatMap. optionalToStream method in Listing 5.16 converts an Optional object to a stream with a single element or an empty stream.

Listing 5.16 Convert an Optional object to a stream
public <T> Stream<T> optionalToStream(Optional<T> opt) {
  return opt.isPresent() ? Stream.of(opt.get()) : Stream.empty();
}

3.3.6 How to use Optional to check for null and assign default values?

It’s a common case to check if a value is null and assign a default value to it if it’s null. Optional can be used to write elegant code in this case. Listing 5.17 shows the traditional way to check for null which uses four lines of code.

Listing 5.17 Traditional way to check for null
public void test() {
  MyObject myObject = calculate();
  if (myObject == null) {
    myObject = new MyObject();
  }
}

Listing 5.18 shows how to use Optional to do the same thing as Listing 5.17with only one line of code.

Listing 5.18 Use Optional to simplify code
public void test() {
  MyObject myObject = Optional.ofNullable(calculate()).orElse(new MyObject());
}

3.4 Updates after Java 8

After Java 8 was released, new methods have been added to Optional.

isEmpty() method, introduced in Java 11, is the opposite of isPresent().

ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) method, introduced in Java 9, performs the given action when the value is present, otherwise performs the emptyAction.

or(Supplier<? extends Optional<? extends T>> supplier) method, introduced in Java 9, returns the current Optional object if the value is present, otherwise invokes the supplier function to return the Optional object.