Week 5 Day 5 — Optional
Goal
Today I want Optional as a return type for “maybe one value”, with a hard list of places it does not belong.
Main questions:
- What is
Optionalfor? ofvsofNullablevsempty?orElsevsorElseGetvsorElseThrow?- When must I not use
Optional? - How does Spring Data use it?
1. A return type, not a magic null
Optional<T> is a container that is either empty or holds one non-null T. It makes “not found” part of the signature.
public Optional<Order> findById(long id) {
return Optional.ofNullable(store.get(id));
}
Order order = findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
The caller must choose: default, throw, or map. They cannot ignore the empty case as easily as a null return.
Optional is a value wrapper. It is not a substitute for exceptions when the use case required the order to exist (Week 4: get throws; find returns Optional).
Memory sentence:
Optionalis a return type that makes “not found” explicit. Empty collections stay collections.
2. Creating Optionals
| Factory | Use |
|---|---|
Optional.empty() | known empty |
Optional.of(x) | x must be non-null; null → NPE |
Optional.ofNullable(x) | empty if x is null |
Never Optional.of(null). Never return null instead of Optional.empty() — that is two empty channels.
Do not wrap a collection: Optional<List<Order>>. A missing list is List.of(). A missing order is Optional<Order>.
3. Unwrapping
optional.orElse(defaultOrder); // default always evaluated
optional.orElseGet(this::loadDefault); // default only if empty
optional.orElseThrow(() -> new OrderNotFoundException(id));
optional.map(Order::id); // Optional<Long>
optional.flatMap(this::findInvoice); // when the function already returns Optional
optional.filter(Order::isOpen);
optional.ifPresent(this::audit);
optional.ifPresentOrElse(this::audit, this::warnMissing);
orElse vs orElseGet: orElse always builds the default, even when the optional is present. If the default is cheap (orElse(0)), that is fine. If it hits the database, use orElseGet.
get(): throws NoSuchElementException when empty. I treat get() as a code smell unless I just checked isPresent() in the previous line — and even then orElseThrow is clearer.
4. Where Optional does not belong
| Place | Why not | What instead |
|---|---|---|
| Field | extra allocation, unclear persistence, not for identity | nullable field, or don’t store absence |
| Method parameter | forces wrapping at every call; unclear “empty vs omit” | overload, or a dedicated type |
| JPA entity attribute | Hibernate does not treat it as a first-class mapping | nullable column, Optional only on accessors if the team likes that |
| JSON DTO | Jackson optional support is awkward; APIs use null or omit | record with a nullable component, or skip the field |
| Collection elements | List<Optional<Order>> is noise | List<Order> |
Joshua Bloch’s rule, which interviews expect: Optional is for return types.
5. Spring connection
public interface OrderRepository extends JpaRepository<Order, Long> {
Optional<Order> findById(Long id); // already on CrudRepository
Optional<Order> findByOrderNumber(String n);
}
Service:
public Order get(long id) {
return orders.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
}
public Optional<Order> findByNumber(String n) {
return orders.findByOrderNumber(n);
}
REST get uses get(id) and Week 4’s advice → 404. I do not put Optional<Order> in the JSON body.
@RequestParam Optional<String> q exists in Spring MVC as “parameter may be absent.” It is a framework trick. I still do not use Optional on my own service parameters.
6. Common traps
Trap 1: optional.get() as the normal path.
Trap 2: orElse(loadFromDb()) when loadFromDb is expensive — use orElseGet.
Trap 3: Optional of a List. Empty list is enough.
Trap 4: Optional fields on entities and records that Jackson serializes as { "present": true, "empty": false }.
Trap 5: Using Optional to hide a thrown exception: catch, return empty(). That deletes the failure (Week 4).
Practice Questions and Answers
Question 1
When do I use Optional? When do I not?
Answer:
I use it as a return type when a lookup may find nothing. I do not use it as a field, a JPA attribute, a DTO property, or a service parameter. I do not wrap collections. I do not call get() blindly.
Question 2
orElse vs orElseGet?
Answer:
orElse always evaluates the default argument. orElseGet runs the supplier only when empty. Cheap constants: orElse. Database or construction: orElseGet.
Question 3
How does this meet REST 404?
Answer:
The repository returns Optional<Order>. The service get uses orElseThrow(OrderNotFoundException::new) (or a lambda with the id). @ControllerAdvice maps that to 404. The JSON body is an OrderResponse, not an Optional.
Question 4
Why not Optional as a method parameter?
Answer:
Callers must wrap every argument. null vs empty becomes two absences. Overloads or a required type are clearer. (Spring MVC’s @RequestParam Optional is an exception at the HTTP adapter.)
Question 5
Optional.of(null)?
Answer:
Throws NullPointerException. Use ofNullable when the value may be null, empty() when I already know it is missing.
Memory sentences
Optionalis for return values; empty collections stay collections.
orElsealways runs the default;orElseGetruns it only when empty.
Repository
findreturnsOptional. Servicegetthrows. REST maps the exception.
Next: Week 5 Review