Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

map() and flatMap() APIs stem from functional languages. In Java 8, we can find them in Optional, Stream and in CompletableFuture (although under a slightly different name).

Streams represent a sequence of objects whereas optionals are classes that represent a value that can be present or absent. We have the map() and flatMap() methods among other aggregate operations.

Even though both have the same return types, they are quite different. Let’s explain these differences by analyzing some examples of streams and optionals.

Further reading:

Iterate Over a Map in Java

Learn different ways of iterating through the entries of a Map in Java.

Map Serialization and Deserialization with Jackson

A quick and practical guide to serializing and deserializing Java Maps using Jackson.

How to Store Duplicate Keys in a Map in Java?

A quick and practical guide to handling duplicate keys by using multimaps in Java.

2. Map and Flatmap in Optionals

The map() method works well with Optional — if the function returns the exact type we need:

Optional<String> s = Optional.of("test");
assertEquals(Optional.of("TEST"), s.map(String::toUpperCase));

However, in more complex cases we might be given a function that returns an Optional too. In such cases using map() would lead to a nested structure, as the map() implementation does an additional wrapping internally.

Let’s see another example to get a better understanding of this situation:

assertEquals(Optional.of(Optional.of("STRING")), 
  Optional
  .of("string")
  .map(s -> Optional.of("STRING")));

As we can see, we end up with the nested structure Optional<Optional<String>>. Although it works, it’s pretty cumbersome to use and does not provide any additional null safety, so it is better to keep a flat structure.

That’s exactly what flatMap() helps us to do:

assertEquals(Optional.of("STRING"), Optional
  .of("string")
  .flatMap(s -> Optional.of("STRING")));

3. Map and Flatmap in Streams

Both methods work similarly for Optional.

The map() method wraps the underlying sequence in a Stream instance, whereas the flatMap() method allows avoiding nested Stream<Stream<R>> structure.

Here, map() produces a Stream consisting of the results of applying the toUpperCase() method to the elements of the input Stream:

List<String> myList = Stream.of("a", "b")
  .map(String::toUpperCase)
  .collect(Collectors.toList());
assertEquals(asList("A", "B"), myList);

map() works pretty well in such a simple case. But what if we have something more complex, such as a list of lists as an input?

Let’s see how it works:

List<List<String>> list = Arrays.asList(
  Arrays.asList("a"),
  Arrays.asList("b"));
System.out.println(list);

This snippet prints a list of lists [[a], [b]].

Now let’s use a flatMap():

System.out.println(list
  .stream()
  .flatMap(Collection::stream)
  .collect(Collectors.toList()));

The result of such a snippet will be flattened to [a, b].

The flatMap() method first flattens the input Stream of Streams to a Stream of Strings (for more about flattening, see this article). Thereafter, it works similarly to the map() method.

4. Conclusion

Java 8 gives us the opportunity to use the map() and flatMap() methods that originally were used in functional languages.

We can invoke them on Streams and Optionals. These methods help us to get mapped objects by applying the provided mapping function.

As always, the examples in this article are available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.