Optional in Java
Representing absence: map, flatMap, orElse vs orElseGet, orElseThrow; not for fields or parameters.
findUser(42) returns null, nobody checks, and production goes down with a NullPointerException. What if the return type itself shouted "this might be empty!"?A box that might be empty
**Optional<T> either holds a value or is empty. It's designed as a return type** for "there might be no result", forcing callers to deal with absence. Create one with Optional.of(v), Optional.ofNullable(v) or Optional.empty(). **Optional.of(null) throws NullPointerException.**
Optional<User> findUser(int id) { ... }
Optional.of("ada"); // has a value
Optional.ofNullable(null); // empty, safely
Optional.empty(); // emptymap, then unwrap
**map transforms the value only if present; on an empty Optional it does nothing. Then unwrap: orElse(x)** gives a fallback. Avoid bare get(): prefer **orElseThrow()**, which makes the "must exist" intent explicit.
String name = findUser(42)
.map(User::name)
.orElse("guest");Your turn
What does this print?
var some = Optional.of("java");
var none = Optional.<String>empty();
IO.println(some.map(String::length).orElse(0));
IO.println(none.map(String::length).orElse(0));4 04 nulljava 0
Show the answer
some holds "java", so map gives Optional[4] → 4. On none, map does nothing and orElse supplies 0.
flatMap for Optional-returning functions
If the function already returns an Optional, map would wrap it again: Optional<Optional<String>>. **flatMap** avoids the nesting.
// User.email() returns Optional<String>
Optional<String> email =
findUser(id).flatMap(User::email);
// .map(User::email) would give
// Optional<Optional<String>>Empty, then demanding
What happens?
String s = null;
Optional<String> o = Optional.ofNullable(s);
System.out.println(o.isPresent());
System.out.println(o.orElseThrow());Prints false, then throws NoSuchElementExceptionPrints false, then nullThrows NullPointerException at ofNullable
Show the answer
ofNullable(null) safely makes an empty Optional, so isPresent() is false. Then **orElseThrow() on an empty Optional throws NoSuchElementException**.
orElse vs orElseGet
The Optional has a value. What does this print?
static String fallback() {
System.out.println("computing");
return "anon";
}
void main() {
var name = Optional.of("ada");
IO.println(name.orElse(fallback()));
IO.println(name.orElseGet(() -> fallback()));
}ada adacomputing ada adacomputing ada computing ada
Show the answer
orElse(fallback()) is a normal argument, so **fallback() runs first, even though the value is present**. orElseGet takes a Supplier and only calls it when empty. So "computing" prints once.
Where Optional belongs
class User {
Optional<String> nickname; // field
}
void greet(Optional<String> name) { }Fields and parameters: extra allocation, not Serializable, and an Optional parameter can itself be null.
Optional<String> findNickname(User u) {
return Optional.ofNullable(u.nick);
}Return values that might be absent. For fields and parameters, use null checks or overloads.
On the job
Spring Data repositories return Optional<T> from findById, and many teams require it for any lookup that can miss. The orElse vs orElseGet trap is a real performance bug: an expensive database call or object creation running on every lookup, even on hits.
Key takeaways
- Create: Optional.of(v), Optional.ofNullable(v), Optional.empty()
- map transforms the value; flatMap for functions that return Optional
- orElse(x) always evaluates x; orElseGet(supplier) only when empty
- Avoid get() — prefer orElseThrow() to make the intent explicit
💡 Optional is a gift box that might be empty — you check before you promise someone a present.
Optional arrived in Java 8, but the handy isEmpty() method only came in Java 11. Before that, you had to write !opt.isPresent().
Practice questions
What does this print?
Function<String, Integer> len = String::length;
var some = Optional.of("ada");
var none = Optional.<String>empty();
System.out.println(some.map(len).orElse(-1));
System.out.println(none.map(len).orElse(-1));- 3 -1
- 3 0
- ada -1
- 3 null
Check your answer
3 -1. map applies the function only when a value is present: Optional[3] → 3. On an empty Optional, map does nothing and orElse supplies -1.
findUser returns Optional<User>, and User.email() returns Optional<String>. Which method gives an Optional<String>?
Optional<String> email =
findUser(id).___(User::email);- map
- flatMap
- filter
- or
Check your answer
flatMap. flatMap is for functions that already return an Optional — it avoids nesting. map would produce an Optional<Optional<String>>.