flatMap()

Chain steps that each return an Optional, such as one lookup feeding the next, without ending up with Optional<Optional<T>>.

Signature
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$mapperFunction<? 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 null from the function. Unlike map, flatMap does not turn null into empty; it throws NullPointerException. The function must return Optional.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, use stream.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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · java
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 Unauthorized

Returning 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.

Example · java
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.empty

Combining 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.

Example · java
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

  • Be the first to comment on this lesson.