Definition
- stream() – present Optional becomes a one-element Stream; empty becomes an empty Stream. Used with
flatMap(Optional::stream)to drop empties. - or(Supplier
>) – provides a lazy fallback Optional if this one is empty; enables chaining multiple lookup sources. - ifPresentOrElse(action, emptyAction) – executes one action if present, another if empty; replaces
if (opt.isPresent()) { ... } else { ... }.
stream() is like opening envelopes on a conveyor belt: envelopes with cheques (present) put the cheque on the belt, empty envelopes put nothing. or() is like trying multiple bank branches: check savings, not found? Try current, not found? Try NRI. ifPresentOrElse is the teller saying: if the cheque exists, deposit it; if not, log a missing-cheque alert.
Understanding the Concepts
What did Java 9 add to Optional?
Three methods improve Optional's integration with streams and fallback handling.
How does Optional.stream() work?
package com.bank.service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
public class AmountParser {
// BEFORE Java 9: verbose
List<BigDecimal> parseAll(List<String> inputs) {
return inputs.stream()
.map(this::tryParse)
.filter(Optional::isPresent)
.map(Optional::get) // unsafe .get()!
.toList();
}
// AFTER Java 9: clean
List<BigDecimal> parseAllClean(List<String> inputs) {
return inputs.stream()
.map(this::tryParse)
.flatMap(Optional::stream) // present → value, empty → gone
.toList();
}
private Optional<BigDecimal> tryParse(String input) {
try {
return Optional.of(new BigDecimal(input));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
}
How does Optional.or() work?
// Try primary source. If empty, try secondary. If empty, try tertiary.
Optional<Account> account = findInSavings(accountId)
.or(() -> findInCurrent(accountId)) // lazy: only called if savings is empty
.or(() -> findInNRI(accountId)); // lazy: only called if current is also empty
// Key: or() returns Optional, enabling further chaining
// orElse() returns the VALUE, ending the chain
Account value = findInSavings(id).orElse(defaultAccount); // returns Account
Optional<Account> opt = findInSavings(id).or(() -> findInCurrent(id)); // returns Optional
How does Optional.ifPresentOrElse() work?
// BEFORE: if-else
Optional<Account> account = findAccount(id);
if (account.isPresent()) {
processAccount(account.get());
} else {
log.warn("Account {} not found", id);
}
// AFTER: one method call
findAccount(id).ifPresentOrElse(
this::processAccount,
() -> log.warn("Account {} not found", id));
What is the difference between or, orElse, and orElseGet?
| Method | Returns | Evaluation | Purpose |
|---|---|---|---|
| orElse(value) | T | Eager (always evaluated) | Default value |
| orElseGet(supplier) | T | Lazy (only if empty) | Computed default |
| or(supplier) | Optional
What should a senior engineer know?
- stream() bridges Optional to Stream: the pattern
flatMap(Optional::stream)is the idiomatic way to drop empties. - or() suppliers should be side-effect-free; they may or may not execute depending on whether earlier Optionals are present.
- ifPresentOrElse returns void; it is for side effects only, not for computing values.
What are common interview follow-up questions?
- Why is flatMap(Optional::stream) better than filter+get? (No unsafe .get() call. One method reference instead of two lines.)
- Can you chain or() multiple times? (Yes. Each supplier is tried in order until one returns a present Optional.)
- Does ifPresentOrElse return a value? (No. It returns void. For computed values, use map or flatMap.)
Visual Overview
Optional.stream():
Optional.of("SAV2001") → Stream.of("SAV2001") (1 element)
Optional.empty() → Stream.empty() (0 elements)
flatMap(Optional::stream) pipeline:
Stream: Optional.of(100) → Optional.empty() → Optional.of(300)
│ │ │
▼ ▼ ▼
flatMap: Stream(100) Stream.empty() Stream(300)
Result: [100, 300] (empties dropped)
Optional.or() chain:
findInSavings(id) → empty
.or(() -> findInCurrent(id)) → empty
.or(() -> findInNRI(id)) → present(Account)
Result: Account from NRI
Key Takeaway
Java 9 added three methods to Optional that improve stream integration and fallback handling. Optional.stream() converts a present Optional to a one-element Stream and an empty Optional to an empty Stream. Used with flatMap(Optional::stream), it cleanly drops empty Optionals from a pipeline, replacing the verbose filter(Optional::isPresent).map(Optional::get) pattern.
Optional.or(Supplier) provides a lazy fallback Optional instead of a fallback value. Each supplier is only called if the previous Optional was empty. Unlike orElse which returns a value, or() returns another Optional, enabling further chaining with map, filter, or another or.
ifPresentOrElse(action, emptyAction) handles both present and empty cases in one method call. The present action receives the value; the empty action is a Runnable. Together, these three methods make Optional a complete monadic container for stream integration.
Remember one sentence: Optional.stream() bridges Optional to Stream via flatMap, or() enables lazy fallback chains, and ifPresentOrElse handles both cases in one call.
---