Java Optional Tutorial with Examples
Java 12 min read
Creating, testing and unwrapping Optional — plus the four things not to do with it: calling get(), passing it as a parameter, putting it in a field, and using orElse where you meant orElseGet.
Optional<T> is a container that holds either one value or nothing. Its purpose is narrow and worth
stating precisely: it makes “this might not return anything” part of a method’s return type,
where the compiler and the reader both see it, instead of a fact you learn from a
NullPointerException.
It is not a null-safety feature for the language. Used in the wrong places it adds allocation and noise without removing a single bug.
Written against Java 17.
Creating one
Optional<String> present = Optional.of("java"); // throws if the argument is null
Optional<String> maybe = Optional.ofNullable(lookup()); // null becomes empty
Optional<String> empty = Optional.empty();
Optional.of(null) throws NullPointerException immediately, which is deliberate. It is for values
you know are present. ofNullable is the one for a reference that might be null, and the usual way
to wrap a legacy API at the boundary.
Testing without unwrapping
if (maybe.isPresent()) { ... }
if (maybe.isEmpty()) { ... } // Java 11+
maybe.ifPresent(v -> log.info("got {}", v));
maybe.ifPresentOrElse(
v -> log.info("got {}", v),
() -> log.warn("nothing found")); // Java 9+
ifPresentOrElse replaces the isPresent/else pair that people write by reflex and reads better
than either branch alone.
Getting the value out
Five ways, and only one of them is usually right:
String a = maybe.get(); // avoid
String b = maybe.orElse("default");
String c = maybe.orElseGet(() -> expensiveDefault());
String d = maybe.orElseThrow(); // NoSuchElementException, Java 10+
String e = maybe.orElseThrow(() -> new NoteNotFoundException(id));
get() defeats the point. It throws NoSuchElementException when empty, so you have swapped a
NullPointerException for a differently-named unchecked exception and added a wrapper object. If
you find yourself writing if (o.isPresent()) return o.get();, the whole expression is
o.orElse(...) or o.orElseThrow(...).
orElse versus orElseGet is not a style choice. orElse takes a value, so its argument is
evaluated every time, present or not:
// countAllNotes() runs even when the optional has a value
long total = maybe.map(Note::count).orElse(countAllNotes());
// countAllNotes() runs only when the optional is empty
long total = maybe.map(Note::count).orElseGet(this::countAllNotes);
With a literal default, orElse is fine and clearer. With anything that queries, allocates or
calls a service, orElseGet: otherwise you pay for the fallback on the happy path, silently.
Transforming
map applies a function if a value is present and stays empty otherwise:
Optional<String> title = noteRepository.findById(id)
.map(Note::title);
Optional<Integer> length = noteRepository.findById(id)
.map(Note::title)
.map(String::length);
No null checks between the steps, and the chain short-circuits at the first empty.
flatMap is for a function that itself returns an Optional. Without it you get nesting:
// map gives Optional<Optional<Address>>
Optional<Optional<Address>> nested = findUser(id).map(User::address);
// flatMap flattens it
Optional<Address> address = findUser(id).flatMap(User::address);
Optional<String> postcode = findUser(id)
.flatMap(User::address)
.map(Address::postcode);
Optional<Optional<T>> is always a sign that map should have been flatMap.
filter turns a present-but-unwanted value into empty:
Optional<Note> publishedNote = noteRepository.findById(id)
.filter(Note::isPublished);
That reads as “find it, and treat an unpublished one as not found”, which is often exactly the domain rule.
or supplies an alternative Optional rather than a value:
Optional<Config> config = fromEnvironment()
.or(this::fromPropertiesFile)
.or(this::fromDefaults); // Java 9+
A fallback chain, evaluated lazily, with no nesting.
Bridging to streams
List<String> titles = ids.stream()
.map(noteRepository::findById) // Stream<Optional<Note>>
.flatMap(Optional::stream) // Stream<Note>, empties dropped — Java 9+
.map(Note::title)
.toList();
Optional::stream yields a stream of zero or one element, so flatMap drops the empties. Before
Java 9 this was .filter(Optional::isPresent).map(Optional::get), which you still see and no longer
need.
Going the other way, terminal operations that may find nothing already return Optional:
Optional<Note> newest = notes.stream()
.max(Comparator.comparing(Note::updatedAt));
Where Optional does not belong
This is the part that decides whether Optional improves a codebase.
Not as a method parameter.
// don't
public List<Note> search(String q, Optional<String> category) { ... }
// do
public List<Note> search(String q, String category) { ... } // document that null means "any"
public List<Note> search(String q) { ... } // or overload
A caller must now write Optional.of(x) at every call site, and can still pass null for the
Optional itself, so you have added ceremony without removing a failure mode.
Not as a field.
// don't
public class Note {
private Optional<String> summary;
}
Optional is not Serializable. It costs an extra object per instance, and JPA and Jackson both
need help to handle it. Store the nullable field and return Optional from the getter.
Not as a collection. An empty list already means “nothing”; Optional<List<T>> forces callers to
unwrap twice for no information. Return an empty collection.
Not for a value you then immediately get(). That is a null check with more syntax.
The one place it clearly belongs: a return type for a lookup that can legitimately find nothing.
findById, findByEmail, firstMatching. Spring Data returns Optional from those methods for
exactly this reason.
Equality, and a caveat
Optional.of("a").equals(Optional.of("a")); // true — delegates to the value
Optional.empty().equals(Optional.empty()); // true
Do not synchronize on an Optional or use it as a lock, and do not rely on reference identity —
it is a value-based class, and identity-sensitive operations on it are explicitly unspecified. In
practice this only bites if you were doing something you should not.
Frequently asked questions
What is Optional actually for?
Making “may return nothing” visible in a method’s return type. It is a documentation and API-design tool, not a null-safety mechanism for the language.
Why should I avoid get()?
It throws NoSuchElementException when empty, so it converts one
unchecked exception into another while adding a wrapper. Use orElse, orElseGet or orElseThrow.
What is the difference between orElse and orElseGet?
orElse takes a value and evaluates its
argument every time, even when a value is present. orElseGet takes a supplier and evaluates it only
when empty. Use orElseGet for anything expensive.
When do I need flatMap instead of map?
When the mapping function itself returns an Optional.
Using map there produces Optional<Optional<T>>.
Can I use Optional as a method parameter?
You can, and it is widely considered wrong: callers must wrap at every call site, and the parameter itself can still be null. Use an overload, or a nullable parameter that is documented.
Should an entity field be Optional?
No. It is not Serializable, costs an allocation per
instance, and confuses JPA and Jackson. Keep the field nullable and return Optional from the
getter.
Should I return Optional<List>?
No. An empty list already expresses “nothing found”, and the wrapper forces two unwrappings for zero extra information.
How do I get rid of empties in a stream of Optionals?
.flatMap(Optional::stream) since Java 9.
The older .filter(Optional::isPresent).map(Optional::get) is equivalent and unnecessary.
Is Optional serializable?
No, deliberately. That is one of the reasons it is unsuitable as a field.
Does Optional prevent NullPointerException?
Only where you use it as a return type and callers
respect it. An Optional reference can itself be null, which is why Optional.of(null) throwing
early is the correct design.
Where should I go next?
CompletableFuture composes
asynchronous results the same way Optional composes possibly-absent ones, and the Java
guides cover the rest of the standard library.