Back to Stream: flatMap
Q1Question 1 of 7

map() vs flatMap(): one-to-one vs one-to-many + flatten

Key Concepts
  • map(Function<T,R>) transforms each element one-to-one: each input T produces exactly one output R.
  • flatMap(Function<T, Stream<R>>) transforms each element one-to-many and flattens: each T produces a Stream of R, merged into one flat Stream.
  • If map produces Stream<List<T>> (nested), flatMap produces Stream<T> (flat).
  • Use flatMap whenever you have nested structures: accounts with transactions, orders with line items, lines with comma-separated values.
  • flatMap with Optional.stream() (Java 9) extracts present values and removes empty Optionals in one step.
  • flatMap can produce zero elements (Stream.empty()), one element, or many, making it more flexible than map.
  • The rule: if you see Stream<List<T>>, Stream<Optional<T>>, or Stream<Stream<T>>, you need flatMap.

Definition

map transforms one-to-one (wraps), flatMap transforms one-to-many and flattens (unwraps): use flatMap when map gives you Stream<List<T>> but you want Stream<T>.
  • map(Function): transforms each element independently. N elements in, N elements out.
  • flatMap(Function>): transforms each element into a Stream and merges all resulting streams into one flat Stream.
  • Signature difference: map takes T -> R, flatMap takes T -> Stream.
  • Nesting: map wraps (produces Stream>), flatMap unwraps (produces Stream).
  • Zero elements: flatMap can return Stream.empty() for an element, effectively removing it.
  • Optional.stream(): Java 9 feature; flatMap(Optional::stream) extracts present values and removes empties.

map is like the currency exchange counter at National Bank of India: each dollar cheque becomes a rupee cheque. One cheque in, one cheque out. flatMap is like splitting a corporate salary cheque into individual employee cheques: one corporate cheque in, 50 individual cheques out, all on the same conveyor belt.

Understanding the Concepts

What is the difference between map and flatMap?

Visual comparison

JAVA
package com.bank.service;

import java.util.List;
import java.util.stream.Stream;

public class MapVsFlatMap {
    record Customer(String name, List<String> accountIds) {}

    public static void main(String[] args) {
        List<Customer> customers = List.of(
            new Customer("Rajesh", List.of("SAV2001", "CUR3001")),
            new Customer("Priya", List.of("SAV2002")),
            new Customer("Amit", List.of("SAV2003", "FD4001", "CUR3002"))
        );

        // map: Customer -> List<String> (NESTED!)
        List<List<String>> nested = customers.stream()
            .map(Customer::accountIds)
            .toList();
        System.out.println("map: " + nested);
        // [[SAV2001, CUR3001], [SAV2002], [SAV2003, FD4001, CUR3002]]

        // flatMap: Customer -> Stream<String> -> FLAT!
        List<String> flat = customers.stream()
            .flatMap(c -> c.accountIds().stream())
            .toList();
        System.out.println("flatMap: " + flat);
        // [SAV2001, CUR3001, SAV2002, SAV2003, FD4001, CUR3002]
    }
}
TEXT
map: [[SAV2001, CUR3001], [SAV2002], [SAV2003, FD4001, CUR3002]]
flatMap: [SAV2001, CUR3001, SAV2002, SAV2003, FD4001, CUR3002]

Comparison table

DIAGRAM
| Aspect | map | flatMap |
|--------|-----|--------|
| Mapping | T -> R | T -> Stream<R> |
| Cardinality | 1-to-1 | 1-to-many |
| Result | Stream<R> (may be nested) | Stream<R> (always flat) |
| Use case | Transform type | Flatten nested structures |

flatMap with Optional (Java 9)

JAVA
package com.bank.service;

import java.util.List;
import java.util.Optional;

public class FlatMapOptional {
    record Account(String id, Optional<String> email) {}

    public static void main(String[] args) {
        List<Account> accounts = List.of(
            new Account("SAV2001", Optional.of("rajesh@bank.com")),
            new Account("SAV2002", Optional.empty()),
            new Account("CUR3001", Optional.of("amit@bank.com"))
        );

        // flatMap(Optional::stream) extracts present values, removes empties
        List<String> emails = accounts.stream()
            .map(Account::email)
            .flatMap(Optional::stream)
            .toList();

        System.out.println("Emails: " + emails);
    }
}
TEXT
Emails: [rajesh@bank.com, amit@bank.com]

flatMap with String splitting

JAVA
import java.util.Arrays;
import java.util.List;

List<String> allSymbols = List.of("RELIANCE,TCS,HDFC", "INFY,WIPRO").stream()
    .flatMap(csv -> Arrays.stream(csv.split(",")))
    .distinct()
    .toList();
// [RELIANCE, TCS, HDFC, INFY, WIPRO]

What should a senior engineer know?

flatMap is the monadic bind operation. It parallels Optional.flatMap exactly: if a function returns an Optional, use flatMap to avoid nested Optional>. In streams, if a function returns a Stream, use flatMap to avoid nested Stream>. The rule: if you see a nested container, flatMap unwraps it.

Visual Overview

DIAGRAM
  map (one-to-one, wraps):
  ────────────────────────
  Customer A → [SAV2001, CUR3001]     ← List (wrapped)
  Customer B → [SAV2002]              ← List (wrapped)
  Customer C → [SAV2003, FD4001]      ← List (wrapped)
  Result: Stream<List<String>>  (3 elements, nested)

  flatMap (one-to-many, flattens):
  ────────────────────────────────
  Customer A → SAV2001, CUR3001       ← individual elements
  Customer B → SAV2002                ← individual element
  Customer C → SAV2003, FD4001        ← individual elements
  Result: Stream<String>  (5 elements, flat)

Key Takeaway

map transforms one-to-one: each element produces exactly one output. flatMap transforms one-to-many and flattens: each element produces a Stream that is merged into the main Stream. If map gives you Stream> (nested), flatMap gives you Stream (flat).

Use flatMap for nested structures (customers with accounts), string splitting (CSV to tokens), Optional extraction (flatMap(Optional::stream)), and merging multiple sources.

Remember one sentence: map wraps each result, flatMap unwraps and flattens, so use flatMap whenever you see nested Stream>.

---

Share
Use ← → arrow keys to navigate