Optional and null handling
What Optional is for, the get() and Optional.of(null) traps, and why orElse runs its fallback even when a value is present.
After this lesson you can
- Return Optional from a method whose result may be absent, and consume it without get()
- Choose between of and ofNullable, and between orElse and orElseGet
- Chain map, flatMap and filter instead of nested null checks
- Say where Optional does not belong
Optional<T> is a container that holds either one non-null value or
nothing. Its purpose is narrow: a return type that tells the caller
"there may be no result, and you have to decide what to do then" — the
thing a null return hides. Optional.of(v) wraps a value that must
not be null; Optional.ofNullable(v) turns a possible null into an
empty Optional; Optional.empty() is the empty one.
import java.util.*; public class Solution { public static List<String> describe() { List<String> out = new ArrayList<>(); Map<String, String> owners = Map.of("printer", "ana"); out.add("ofNullable(missing): " + Optional.ofNullable(owners.get("scanner"))); out.add("ofNullable(present): " + Optional.ofNullable(owners.get("printer"))); try { Optional.of(owners.get("scanner")); } catch (NullPointerException e) { out.add("of(null): NullPointerException"); } try { Optional.empty().get(); } catch (NoSuchElementException e) { out.add("empty().get(): " + e.getMessage()); } return out; }}Getting the value out
get() (and its clearer twin orElseThrow()) throws
NoSuchElementException on an empty Optional, so calling it without
checking just moves the NullPointerException somewhere else. Prefer
the methods that say what absence means:
orElse(fallback)— usefallbackwhen empty;orElseGet(() -> compute())— compute the fallback only when empty;orElseThrow(() -> new NotFoundException(id))— absence is an error;ifPresent(v -> ...)/ifPresentOrElse(...)— act on it.
The difference between the first two matters. orElse's argument is an
ordinary method argument, so it is evaluated before the call,
whether or not the Optional has a value. If the fallback is
expensive — a database query, a remote call — or has side effects,
orElse pays for it every time.
import java.util.*; public class Solution { static int lookups = 0; static String loadFromDatabase() { lookups++; // stands in for an expensive call return "from-db"; } public static List<String> describe() { lookups = 0; Optional<String> cached = Optional.of("cached"); String a = cached.orElse(loadFromDatabase()); int afterOrElse = lookups; String b = cached.orElseGet(Solution::loadFromDatabase); int afterOrElseGet = lookups; return List.of( "orElse -> " + a + ", lookups=" + afterOrElse, "orElseGet -> " + b + ", lookups=" + afterOrElseGet); }}Chaining instead of nesting
map transforms the value if there is one (and a function returning
null produces an empty Optional); filter empties it when a
condition fails; flatMap is for a function that itself returns an
Optional, so you do not end up with Optional<Optional<T>>.
String city = findUser(id) // Optional<User>
.flatMap(User::address) // address() returns Optional<Address>
.map(Address::city)
.filter(c -> !c.isBlank())
.orElse("unknown");
Where Optional does not belong
As a field, a method parameter, or inside a collection
(List<Optional<T>>): it adds an allocation and a third state (null
Optional) without making anything clearer. An empty collection already
means "nothing". For plain null-safety elsewhere there are
Objects.requireNonNull(x, "x") at the boundary,
Objects.equals(a, b), Map.getOrDefault, and helpful
NullPointerException messages (on by default since Java 15) that name
exactly what was null.
Common mistakes
Optional.of(map.get(key))— throws precisely when the key is missing; that is what is for.
Exercise · Pro
Load only on a cache miss
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade