Working with Optional API
1. Creating Optional
| Factory | Behavior |
Optional.empty() | Empty singleton |
Optional.of(v) | Throws NPE if null |
Optional.ofNullable(v) | Empty if null |
OptionalInt/Long/Double | Primitive specializations |
2. Checking Presence
| Method | Returns | Use |
isPresent() | boolean | Avoid — prefer functional API |
isEmpty() Java 11+ | boolean | Inverse of isPresent |
ifPresent(c) | void | Side effect when present |
3. Retrieving Values
| Method | Behavior |
get() | Throws NoSuchElementException if empty |
orElse(default) | Always evaluates default |
orElseGet(supplier) | Lazy default |
orElseThrow() Java 10+ | Throws NoSuchElementException |
orElseThrow(supplier) | Custom exception |
Warning: Prefer orElseGet over orElse when default is expensive (e.g., DB lookup) — orElse always evaluates its argument.
4. Throwing Exceptions (orElseThrow)
| Form | Use |
orElseThrow() | Default NoSuchElementException |
orElseThrow(NotFoundException::new) | Domain exception |
orElseThrow(() -> new X("id=" + id)) | Contextual message |
| Method | Signature | Behavior |
map(Function) | Optional<U> | 1:1 transform; empty stays empty |
flatMap(Function) | Optional<U> | Function returns Optional |
filter(Predicate) | Optional<T> | Empty if predicate false |
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");
6. Filtering Optional (filter)
| Input | filter(p) Result |
| empty | empty |
| present, p true | same Optional |
| present, p false | empty |
7. Chaining Optional Operations
| Pattern | Result |
map.map.map | Nested transforms; empty propagates |
flatMap | Avoids Optional<Optional<T>> |
filter.map | Conditional transform |
8. Using ifPresent (Consumer)
| Method | Action |
ifPresent(c) | Run consumer if value |
ifPresentOrElse(c, runnable) | Both branches Java 9+ |
9. Using ifPresentOrElse (Java 9+)
Example: Branching
repository.findById(id).ifPresentOrElse(
user -> log.info("Found {}", user),
() -> log.warn("Missing id={}", id)
);
| Param | Type | When |
| action | Consumer<? super T> | Value present |
| emptyAction | Runnable | Empty |
10. Using or() for Alternative Optional (Java 9+)
| Method | Returns |
or(Supplier<Optional>) | This if present, else supplier's Optional |
orElse | Raw value |
orElseGet | Raw value, lazy |
Example: Cascading lookups
User u = cache.find(id)
.or(() -> database.find(id))
.or(() -> remote.find(id))
.orElseThrow(() -> new NotFoundException(id));
11. Avoiding Common Pitfalls
| Anti-pattern | Fix |
| Optional fields/params | Only return type — never field, param, or collection element |
opt.get() without check | Use orElseThrow or functional ops |
isPresent + get | Use ifPresent / map |
Optional.of(possiblyNull) | Use ofNullable |
| Serializing Optional | Not Serializable — use null in DTOs |
orElse(new Heavy()) | Use orElseGet |
12. Using Optional with Streams (stream() method)
| Method | Use |
opt.stream() Java 9+ | 0 or 1 element stream |
flatMap(Optional::stream) | Filter + unwrap pattern |
Example: Filter present values
List<User> found = ids.stream()
.map(repo::findById) // Stream<Optional<User>>
.flatMap(Optional::stream) // Stream<User> (drop empties)
.toList();