flatMap()
Chain steps that each return an Optional, such as one lookup feeding the next, without ending up with Optional<Optional<T>>.
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$mapper | Function<? super T, ? extends Optional<? extends U>> | The function applied to the value, if present. It must return an Optional, never null. |
Return value
Optional<U>The Optional returned by mapper when a value is present; otherwise an empty Optional.
Errors
NullPointerException if mapper is null or returns a null result.
Changelog
- PHP Java 9The mapper parameter type was widened from Function<? super T, Optional<U>> to Function<? super T, ? extends Optional<? extends U>>, so functions returning an Optional of a subtype are accepted.
flatMap is map for functions that already return an Optional. If the Optional has a value, it calls the function and returns that function's Optional as it is. If it is empty, the function is not called and the result is empty.
Why map is not enough
Say you load a user, then load the user's team. Both lookups return Optional. With map:
Optional<Optional<Team>> nested = findUser(id).map(u -> findTeam(u.teamId()));A nested Optional is awkward to use and almost never what you want. flatMap gives Optional<Team> directly, and it is empty if either the user or the team is missing:
String teamName = findUser(id)
.flatMap(u -> findTeam(u.teamId()))
.map(Team::name)
.orElse("no team");The rule of thumb: function returns a plain value, use map; function returns an Optional, use flatMap.
Mistakes people make
- Returning
nullfrom the function. Unlikemap,flatMapdoes not turnnullinto empty; it throwsNullPointerException. The function must returnOptional.empty(). - Nesting
isPresent()checks for each step instead of chaining. Each extra level makes the missing cases harder to see. - Confusing it with
Stream.flatMap. They share the idea (flatten one level), but on a stream you flatten many results; on Optional you flatten at most one. To drop empty Optionals from a stream, usestream.flatMap(Optional::stream).
Back-end angle
Request handling is full of steps that may each fail softly: read a header, extract a token, look up the session, load the user. Written as helpers that return Optional, they chain with flatMap and the handler ends with a single orElseThrow for the 401. You can also combine two independent Optionals: a.flatMap(x -> b.map(y -> combine(x, y))) is present only when both are.
Example
When to use it
- Loading a user and then the user's team, account or tenant, where each lookup returns Optional.
- Chaining header parsing, token extraction and session lookup in an authentication filter.
- Building a value only when two optional request fields are both present.
Frequently asked questions
When should I use flatMap instead of map on an Optional?
Use flatMap when the function you pass already returns an Optional. map would wrap it again and give you Optional<Optional<T>>; flatMap returns the inner Optional directly.
Why does Optional.flatMap throw a NullPointerException?
Either the function itself was null or it returned null. flatMap requires the function to return a non-null Optional, so return Optional.empty() for the missing case.
How do I flatten Optional<Optional<T>>?
Call flatMap(Function.identity()) or, better, avoid creating it by using flatMap instead of map in the step that returns an Optional.
More examples
From Authorization header to user
Each helper returns an Optional, so flatMap links them without nesting. Any failing step, whether no header, wrong scheme or unknown token, ends in the same 401.
final Map<String, String> sessions = Map.of("tok-42", "ada");
Optional<String> bearerToken(String header) {
if (header == null || !header.startsWith("Bearer ")) {
return Optional.empty();
}
return Optional.of(header.substring(7));
}
Optional<String> userForToken(String token) {
return Optional.ofNullable(sessions.get(token));
}
String whoAmI(Map<String, String> headers) {
return Optional.ofNullable(headers.get("Authorization"))
.flatMap(h -> bearerToken(h))
.flatMap(t -> userForToken(t))
.map(user -> "200 " + user)
.orElse("401 Unauthorized");
}
void main() {
IO.println(whoAmI(Map.of("Authorization", "Bearer tok-42")));
IO.println(whoAmI(Map.of("Authorization", "Bearer tok-00")));
IO.println(whoAmI(Map.of("Authorization", "Basic abc")));
IO.println(whoAmI(Map.of()));
}
// Output:
// 200 ada
// 401 Unauthorized
// 401 Unauthorized
// 401 UnauthorizedReturning null from flatMap throws
map treats a null result as empty, but flatMap insists on a real Optional and throws when given null. Return Optional.empty() from the function instead.
void main() {
Optional<String> code = Optional.of("GB");
try {
code.flatMap(c -> null);
} catch (NullPointerException e) {
IO.println("flatMap: NullPointerException");
}
IO.println(code.map(c -> null));
IO.println(code.flatMap(c -> Optional.empty()));
}
// Output:
// flatMap: NullPointerException
// Optional.empty
// Optional.emptyCombining two Optionals
The outer flatMap and inner map produce a Name only when both parts are present. This pattern works for any two independent optional inputs, such as two optional form fields.
record Name(String first, String last) {}
Optional<Name> fullName(Optional<String> first, Optional<String> last) {
return first.flatMap(f -> last.map(l -> new Name(f, l)));
}
void main() {
IO.println(fullName(Optional.of("Ada"), Optional.of("Lovelace")));
IO.println(fullName(Optional.of("Ada"), Optional.empty()));
IO.println(fullName(Optional.empty(), Optional.of("Lovelace")));
}
// Output:
// Optional[Name[first=Ada, last=Lovelace]]
// Optional.empty
// Optional.empty
Discussion