Java Streams Api - Difference Between Map and flatMap

map()

  1. Returns a stream consisting of the results of applying the given function to the elements of this stream.
  2. It wraps the underlying sequence in a Stream instance.
  3. Input is a stream and output is another (a new) stream.

flatMap()

  1. Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Each mapped stream is closed after its contents have been placed into this stream. (If a mapped stream is null an empty stream is used, instead.).
  2. It allows avoiding nested Stream<Stream<R>> structure.
  3. Input is a stream of stream and output is another (a new) stream.

Generally speaking, a map operation wraps its return value inside its ordinal type, while flatMap does not. For example, in Optional, a map operation would return Optional<String> type, while flatMap would return String type.

So, after using map(), we need to unwrap (read “flatten”) the object to retrieve the value. Whereas after using flatMap(), there is no such need as the object is already flattened.

Both map and flatMap are intermediate stream operations that receive a function and apply this function to all the elements of a stream.

The difference is that for map, this function returns a value, but for flatMap, this function returns a stream. The flatMap operation “flattens” the streams into one.

Here’s an example where we take a map of users’ names and lists of phones and “flatten” it down to a list of phones of all the users using flatMap:

public class MapVsFlatmap {

    public List<List<String>> getPhoneNumberLists(List<Person> people) {
        return people.stream()
                .filter(person -> CollectionUtils.isNotEmpty(person.getPhones()))
                .map(p -> p.getPhones())
                .collect(Collectors.toList());
    }

    public List<String> getAllDistinctPhoneNumbers(List<Person> people) {
        return people.stream()
                .filter(person -> CollectionUtils.isNotEmpty(person.getPhones()))
                .map(p -> p.getPhones())
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    }
}

public class MapVsFlatmapTests {

    private MapVsFlatmap mapVsFlatmap = new MapVsFlatmap();

    @Test
    public void test_getAllPhoneNumbers() throws JsonProcessingException {
        List<Person> people = TestsHelper.getPeople();

        List<List<String>> phoneNumbers = mapVsFlatmap.getPhoneNumberLists(people);

        assertEquals("[ [ \"555-1123\", \"555-3389\" ], [ \"555-2243\", \"555-5264\" ], [ \"555-6654\", \"555-3242\" ] ]",
                (new ObjectMapper()).writerWithDefaultPrettyPrinter().writeValueAsString(phoneNumbers));
    }

    @Test
    public void test_getAllDistinctPhoneNumbers() throws JsonProcessingException {
        List<Person> people = TestsHelper.getPeople();

        List<String> phoneNumbers = mapVsFlatmap.getAllDistinctPhoneNumbers(people);

        assertEquals("[ \"555-1123\", \"555-3389\", \"555-2243\", \"555-5264\", \"555-6654\", \"555-3242\" ]",
                (new ObjectMapper()).writerWithDefaultPrettyPrinter().writeValueAsString(phoneNumbers));
    }

}

Links to this note