Skip to main content

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:

  1. What is Optional for?
  2. of vs ofNullable vs empty?
  3. orElse vs orElseGet vs orElseThrow?
  4. When must I not use Optional?
  5. 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:

Optional is a return type that makes “not found” explicit. Empty collections stay collections.


2. Creating Optionals

FactoryUse
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

PlaceWhy notWhat instead
Fieldextra allocation, unclear persistence, not for identitynullable field, or don’t store absence
Method parameterforces wrapping at every call; unclear “empty vs omit”overload, or a dedicated type
JPA entity attributeHibernate does not treat it as a first-class mappingnullable column, Optional only on accessors if the team likes that
JSON DTOJackson optional support is awkward; APIs use null or omitrecord with a nullable component, or skip the field
Collection elementsList<Optional<Order>> is noiseList<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

Optional is for return values; empty collections stay collections.

orElse always runs the default; orElseGet runs it only when empty.

Repository find returns Optional. Service get throws. REST maps the exception.

Next: Week 5 Review