map()

Transform the value inside an Optional without unwrapping it, so a missing value flows through the chain as empty instead of null.

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

Parameters

ParameterTypeDescription
$mapperFunction<? super T, ? extends U>The function applied to the value, if present. Not called when the Optional is empty.

Return value

Optional<U>An Optional holding the result of applying mapper to the value if a value is present and the result is non-null; otherwise an empty Optional.

Errors

NullPointerException if mapper is null.

map applies a function to the value inside an Optional and wraps the result in a new Optional. If the Optional is empty, the function is not called and you get an empty Optional back. That lets you write the steps for the present case and handle the missing case once, at the end.

String city = findCustomer(id)
        .map(Customer::address)
        .map(Address::city)
        .orElse("unknown");

A null result becomes empty

If the mapping function returns null, map returns Optional.empty() rather than throwing. This makes it a safe way to walk a chain of getters where any link may be null: above, a customer with no address simply produces "unknown". The same code with plain getters would need a null check at each step.

When to use flatMap instead

If the function already returns an Optional, such as another repository lookup, map wraps it again and you get Optional<Optional<Team>>. That is the signal to switch to flatMap, which takes a function returning Optional and does not add a second layer.

Mistakes people make

  • Unwrapping too early: if (opt.isPresent()) { dto = toDto(opt.get()); }. opt.map(this::toDto) does the same and keeps the result an Optional for the next step.
  • Doing side effects inside map, such as saving or logging. Use ifPresent for actions; map is for computing a new value.
  • Expecting map to catch exceptions. If the function throws, for example Integer.parseInt on bad input, the exception propagates. Validate first, or write a helper that returns Optional.empty() on failure and use flatMap.

Back-end angle

The typical use is converting an entity to a response DTO: repo.findById(id).map(UserResponse::from). Spring MVC goes one step further with ResponseEntity.of(optional), which returns 200 with the body or 404 when empty, so map is often the last thing a controller does before returning.

Example

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

When to use it

  • Converting a loaded entity to a response DTO before returning it from a GET handler.
  • Reading a nested property such as customer.address.city where any level may be null.
  • Trimming, parsing and clamping an optional query parameter in one chain with a default at the end.

Frequently asked questions

What happens if the function in Optional.map returns null?

map returns an empty Optional. It never produces an Optional containing null, which is what makes it useful for chains of getters that may return null.

What is the difference between Optional.map and Optional.flatMap?

map wraps whatever the function returns in an Optional. flatMap expects the function to return an Optional itself and returns it as is, so you avoid Optional<Optional<T>>.

More examples

Entity to response DTO

map converts the entity to a DTO that leaves out the password hash, then into a response line. The missing user skips both steps and falls through to the 404.

Example · java
record User(long id, String name, String passwordHash) {}
record UserResponse(long id, String name) {
    static UserResponse from(User u) {
        return new UserResponse(u.id(), u.name());
    }
}

final Map<Long, User> users = Map.of(1L, new User(1L, "Ada", "$2a$10$fakehash"));

String getUser(long id) {
    return Optional.ofNullable(users.get(id))
            .map(UserResponse::from)
            .map(r -> "200 " + r)
            .orElse("404 user " + id + " not found");
}

void main() {
    IO.println(getUser(1));
    IO.println(getUser(2));
}
// Output:
// 200 UserResponse[id=1, name=Ada]
// 404 user 2 not found

A null from the mapper becomes empty

Map.get returns null for the missing header, and map turns that null into an empty Optional instead of Optional[null] or an exception.

Example · java
void main() {
    Map<String, String> headers = Map.of("Accept", "application/json");
    Optional<Map<String, String>> request = Optional.of(headers);

    IO.println(request.map(h -> h.get("Accept")));
    IO.println(request.map(h -> h.get("X-Trace-Id")));
    IO.println(request.map(h -> h.get("X-Trace-Id")).orElse("no trace id"));
}
// Output:
// Optional[application/json]
// Optional.empty
// no trace id

Parsing and clamping a query parameter

Each map step does one small job: trim, parse, clamp to the range 1 to 100 with Math.clamp (Java 21). A client asking for 500 rows gets 100, a negative limit becomes 1, and no parameter means the default of 20; non-numeric input would still throw from parseInt.

Example · java
int limit(Map<String, String> query) {
    return Optional.ofNullable(query.get("limit"))
            .map(String::strip)
            .map(Integer::parseInt)
            .map(n -> Math.clamp(n, 1, 100))
            .orElse(20);
}

void main() {
    IO.println(limit(Map.of("limit", "500")));
    IO.println(limit(Map.of("limit", " 15 ")));
    IO.println(limit(Map.of("limit", "-5")));
    IO.println(limit(Map.of()));
}
// Output:
// 100
// 15
// 1
// 20

Discussion

  • Be the first to comment on this lesson.