A Note on Optionals in Java 8

2016-02-07

The introduction of the Optional class in Java 8 provides a powerful way to avoid null checking. Initially, though, it may be hard to break the habit of checking for null before proceeding with a certain operation.

Suppose we have an object which accepts a flight as a parameter, queries for that flight's status against an external API, and returns a new flight with an updated departure time. We might have a method as follows:

public Flight confirmDepartureTime(Flight flight) {
    // lookup flight against external service

    // return new flight with confirmed departure
    // - OR -
    // return original flight with unconfirmed departure
}

Let's assume further that the object which communicates with the external service returns a Flight object wrapped in an Optional. If for whatever reason the flight isn't found, our flight service returns an empty Optional. From there, if we receive flight data from the external service, we use that data to construct a confirmed flight. Otherwise, we simply return the unconfirmed flight as is.

An initial implementation might take the following shape:

public Flight confirmDepartureTime(Flight unconfirmedFlight) {
    Optional<Flight> maybeFlight =
        flightService.confirmDeparture(unconfirmedFlight);

    if (maybeFlight.isPresent()) {
        return Flight.withConfirmedDeparture(
            maybeFlight.get()
        );
    }

    return unconfirmedFlight;
}

The code above does its job, but in fact looks only slightly different from the usual null checking pattern. More importantly, the code above does not use the Optional object in its proper idiom.

Instead of calling isPresent, we can use two handy functions on the Optional class: 1) map and 2) orElse. Observe:

public Flight confirmDepartureTime(Flight unconfirmedFlight) {
    Optional<Flight> maybeFlight =
        flightService.confirmDeparture(unconfirmedFlight);

    return maybeFlight
            .map(f -> {
                return Flight.withConfirmedDeparture(f);
            })
            .orElse(unconfirmedFlight);
}

The beautiful thing about Optional is that calling map on an empty Optional will be a no-op which immediately falls through to the orElse call. If there is a value inside maybeFlight, the map function will transform that value with the lambda and skip orElse.

Tightening the code further, our function becomes:

public Flight confirmDepartureTime(Flight unconfirmedFlight) {
    return flightService
            .confirmDeparture(unconfirmedFlight)
            .map(Flight::withConfirmedDeparture)
            .orElse(unconfirmedFlight);
}

The result is a striking departure from the usual null checking.