Optional

Java · Practical · 8 min

Optional makes "might be missing" visible in method signatures and cuts down on NullPointerExceptions.

Optional<T> is a container that either holds a value or is empty. Its main job is as a return type for methods that may legitimately find nothing, such as findUserByEmail. The signature tells callers they must deal with absence, which a nullable return never does.

Create one with Optional.of(value) (value must be non-null), Optional.ofNullable(maybeNull), or Optional.empty(). Streams return them from findFirst, max, min and reduce.

Consume it without unwrapping by hand:

  • map(f) / flatMap(f) — transform the value if present.
  • filter(pred) — keep the value only if it matches.
  • orElse(default) — fallback value (always evaluated).
  • orElseGet(() -> compute()) — fallback computed lazily, only when empty.
  • orElseThrow() — get the value or throw NoSuchElementException; orElseThrow(() -> new NotFound(id)) for your own exception.
  • ifPresent(consumer) and ifPresentOrElse(consumer, runnable).

Calling get() after checking isPresent() works but is just null-checking with extra steps; the methods above are more expressive.

Guidelines the JDK designers recommend:

  • Use Optional for return values, not for fields, method parameters or collection elements.
  • Never return null from a method declared to return Optional.
  • Return an empty collection rather than Optional<List<T>>.
  • For primitives there are OptionalInt, OptionalLong and OptionalDouble.

Prefer orElseGet over orElse when the fallback is expensive, such as a database call — orElse's argument is evaluated even when the value is present.

Example

import java.util.Map;
import java.util.Optional;

public class Optionals {
    record User(String email, String nickname) { }

    static final Map<String, User> USERS = Map.of(
            "ana@x.io", new User("ana@x.io", "ana"),
            "bo@x.io", new User("bo@x.io", null));

    static Optional<User> findByEmail(String email) {
        return Optional.ofNullable(USERS.get(email));
    }

    public static void main(String[] args) {
        for (String email : new String[]{"ana@x.io", "bo@x.io", "zed@x.io"}) {
            String nick = findByEmail(email).map(User::nickname).orElse("anonymous");
            System.out.print(nick + " ");             // ana anonymous anonymous
        }
        findByEmail("ana@x.io").ifPresent(u -> System.out.println("\nfound " + u.email()));

        User u = findByEmail("zed@x.io")
                .orElseThrow(() -> new IllegalStateException("no such user")); // throws
    }
}

Remember

  • Use Optional as a return type for "may be absent" results
  • Chain map/filter and finish with orElse, orElseGet or orElseThrow
  • Don't use Optional for fields or parameters, and never return null instead of one

Watch out for

  • Writing if (opt.isPresent()) return opt.get(); everywhere — it's a null check in disguise.

Check yourself

  1. Why prefer `orElseGet(() -> loadDefault())` over `orElse(loadDefault())`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html

Builds on

Unlocks

Nothing builds on this yet.

Optional · Java · Topolearn