Optional.get() is code smell

I sometimes see java.util.Optional being used in the following way:

if (emailOpt.isPresent()) {
    String email = emailOpt.get();
    sendEmail(email);
}

Although I do advocate for usage of Optionals, this is little more than a glorified null check. The power of Optional really shines when using its various methods that allow you to filter, chain and transform optional values.

My rule of thumb is that if you find yourself using Optional.get(), pause and think if there’s another way to do it. There almost always is, and is its probably cleaner. Also, unlike languages like Haskell, the Java compiler doesn’t force you to check if the optional is present before calling .get(), so you run the risk of a runtime exception if you forget to manually check it.

In this post, I’ll discuss ways in which you can minimize usage of Optional.get() or hopefully avoid it altogether.

Conditional execution

In the example above, we could use Optional.ifPresent() to conditionally execute the if block:

emailOpt.ifPresent(email -> sendEmail(email));

Much cleaner isn’t it? The cool thing is since ifPresent() takes a lambda, you can make it even more compact with a method reference:

emailOpt.ifPresent(this::sendEmail);

Default values

This one’s easy. Instead of something like:

final String country;
if (countryOpt.isPresent()) {
    country = countryOpt.get();
} else {
    country = "US";
}

Just use .orElse():

final String country = countryOpt.orElse("US");

Be careful, though! If you use the return value of a method call as the default value, and the method has side-effects, you might get undesired behavior. For example:

private String getDefaultCountry() {
    LOG.info("No country specified, choosing default");
    return "US";
}

// This will log "No country specified.." even when the Optional is present!
final String country = countryOpt.orElse(getDefaultCountry());

// Use .orElseGet() instead to avoid eager evaluation:
final String country = countryOpt.orElseGet(() -> getDefaultCountry());

Throwing exceptions

Often, you need to check if a required value is present, and if not, throw an exception. One might write code like this:

final String phoneNumber;
if (phoneNumberOpt.isPresent()) {
    phoneNumber = phoneNumber.get();
} else {
    throw new IllegalStateException("Phone number is required!");
}

...

Again (you guessed it), there is a much better way:

final String phoneNumber = phoneNumberOpt.getOrElseThrow(() -> 
    new IllegalStateException("Phone number is required!"));

Transformations

In many cases, you might want to transform an optional value to another, more useful, optional value. For example, if your application optionally reports metrics depending on configuration:

// Naive way
private Optional<MetricsReporter> getMetricsReporter(
    Optional<MetricsConfiguration> configuration) {
    if (configuration.isPresent()) {
        return Optional.of(new MetricsReporter(configuration.get());
    }
    return Optional.empty();
}

// The correct (TM) way using `.map()`
private Optional<MetricsReporter> getMetricsReporter(
    Optional<MetricsConfiguration> configuration) {
    // Bonus use of method reference, yay!
    return configuration.map(MetricsReporter::new);

Exceptions to the rule

Does that mean you should never use Optional.get()? Not really; a limitation of the Optional API (as it was first introduced) is that there is no obvious way to execute a statement if the optional is not present using any of the available methods. So, going back to the first example, if you want to log something in case the user has not provided an email, you’re forced to write something like:

if (emailOpt.isPresent()) {
    sendEmail(emailOpt.get());
} else {
    LOG.info("Not sending email because no email specified");
}

// You _could_ do some mind-bending to achieve this without using .get(),
// but really, don't.
emailOpt.map(email -> {
    sendEmail(email);
    return null;
}).orElseGet(() -> {
    LOG.info("Not sending email because no email specified");
    return null;
});

Luckily, the Java language overlords saw the error of their ways and introduced a new method, .ifPresentOrElse() in Java 9. Now, you can rewrite the above:

emailOpt.ifPresentOrElse(
    this::sendEmail,
    () -> LOG.info("Not sending email because no email specified")
);

Another situation where you might need to use Optional.get() is when you’re working with a collection of Optionals and need to filter and operate on present Optionals. As an example, if you have a list of users and need to send emails, but (obviously) only to the users who actually have email addresses, you might write:

List<Email> emailAddrs = userList.stream()
    .map(User::getEmail)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

Enter Java 9 to the rescue again! A .stream() method was introduced which converts a present Optional to a stream of one, and an empty Optional to an empty stream. With this convenience, we can now rewrite the above with:

List<Email> emailAddrs = userList.stream()
    .map(User::getEmail)
    .flatMap(Optional::stream)
    .collect(Collectors.toList());

Summary

You most likely won’t need to use Optional.get() in Java 8, and Java 9 adds even more goodies to help you avoid it. In fact, the creator of the Optional API, Brian Goetz, has expressed that Optional.get() might be deprecated altogether.

Want more content about writing modern and lean Java?