Transforming Optionals
Transforming Optionals
In the previous lesson you learned to extract a value from an Optional safely with orElse, orElseGet, and orElseThrow. But a very common pattern is not wanting to unwrap the value at all — you want to apply some logic to it and keep the result wrapped, so the absence case continues to propagate automatically. That is what map, flatMap, and filter are for.
Optional like a single-element Stream. The transformation methods let you build a pipeline of operations. If the Optional is empty at any step, the rest of the pipeline is skipped — no null checks, no if-statements.
map — applying a function to the wrapped value
map(Function<T, R> mapper) applies mapper if a value is present and returns an Optional<R>. If the original is empty, it returns Optional.empty() without calling mapper at all.
Compare this to the imperative version:
With map you describe the transformation; the empty-propagation is automatic.
flatMap — when the mapper itself returns an Optional
Suppose your mapper function already returns an Optional. Using plain map would give you a nested Optional<Optional<T>>, which is almost never what you want. flatMap flattens that one level.
map when your function returns a plain value. Use flatMap when your function already returns an Optional. If you see Optional<Optional<...>> in your IDE, reach for flatMap.
filter — conditionally emptying an Optional
filter(Predicate<T> predicate) keeps the value if the predicate returns true; otherwise it returns Optional.empty(). It never throws — if the Optional is already empty the predicate is not called.
Building a real pipeline
The three methods compose cleanly. Here is a realistic example: reading a user preference from a config map, converting it to a validated integer, and clamping it to an acceptable range.
Notice there is not a single if (x != null) in that code. The entire absent/invalid path is handled structurally.
The difference from Stream operations
Optional.map behaves like Stream.map on a stream of at most one element. One practical difference: if your map function returns null, Optional.map silently turns the result into Optional.empty() rather than wrapping a null. This is intentional — it prevents re-introducing the nulls you are trying to avoid.
optional.map(x -> { doSomething(x); return x; }) to run a side effect, use ifPresent or ifPresentOrElse instead. The transformation methods signal intent: "produce a new value." Smuggling side effects into them confuses readers and can introduce subtle bugs.
or() — providing a fallback Optional (Java 9+)
Sometimes you want to try another source when the first Optional is empty, and that second source is also Optional. or(Supplier<Optional<T>>) fills that gap cleanly without flatMap gymnastics:
This is safer than primary.orElseGet(() -> secondary.orElse(null)), which unwraps prematurely and loses the Optional context.
Summary
Use map to transform a present value and stay inside Optional. Use flatMap when the transformation itself returns an Optional, to avoid nesting. Use filter to discard values that do not satisfy a condition. Chain them together to write readable, null-safe transformation pipelines. In the next lesson you will see common patterns where these methods shine — and the anti-patterns where they are misused.