Back to Stream: flatMap
Q2Question 2 of 7

flatMap use cases: flatten nested collections, split values, Optional::stream, nested data

Key Concepts
  • Use case 1: flatten nested collections (List<List<Trade>> to Stream<Trade>).
  • Use case 2: split one value into many (CSV line to individual tokens via Arrays.stream(split)).
  • Use case 3: extract Optional values (flatMap(Optional::stream) removes empties and extracts present values).
  • Use case 4: merge multiple stream sources into one (Stream.of(stream1, stream2).flatMap(s -> s)).
  • Use case 5: multi-level drill-down (customer to accounts to transactions to tags with chained flatMap calls).
  • flatMap can produce zero, one, or many elements per input, making it strictly more powerful than map.
  • Without flatMap, nested structures require nested for-loops; with flatMap, one declarative pipeline suffices.

Definition

flatMap has five core use cases: flatten nested collections, split values, extract Optional values, merge streams, and multi-level drill-down through nested data structures.
DIAGRAM
- **Flatten nested**: `List<List<T>>` to `Stream<T>` using `flatMap(Collection::stream)`.
- **Split values**: CSV line to tokens using `flatMap(line -> Arrays.stream(line.split(",")))`.
- **Optional extraction**: `flatMap(Optional::stream)` converts present Optionals to values and removes empties.
- **Merge sources**: `Stream.of(stream1, stream2).flatMap(s -> s)` merges multiple streams.
- **Multi-level**: chained flatMap calls drill through customer -> accounts -> transactions.

flatMap is the envelope opener at National Bank of India. A bag of envelopes each containing multiple cheques: flatMap opens each and puts all cheques on one belt. A corporate cheque listing multiple employees: flatMap splits it into individual cheques. Some envelopes are empty: flatMap discards them. Three departments each with their own stack: flatMap merges all stacks into one.

Understanding the Concepts

What are the five use cases of flatMap?

Use case 1: Flatten nested collections

JAVA
package com.bank.service;

import java.util.Collection;
import java.util.List;

public class FlattenDemo {
    record Branch(String name, List<String> accountIds) {}

    public static void main(String[] args) {
        List<Branch> branches = List.of(
            new Branch("Mumbai", List.of("SAV2001", "SAV2002")),
            new Branch("Delhi", List.of("CUR3001")),
            new Branch("Chennai", List.of("SAV2003", "FD4001", "CUR3002"))
        );

        List<String> allAccounts = branches.stream()
            .flatMap(b -> b.accountIds().stream())
            .toList();

        System.out.println("All accounts: " + allAccounts);
    }
}
TEXT
All accounts: [SAV2001, SAV2002, CUR3001, SAV2003, FD4001, CUR3002]

Use case 2: Split one value into many

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

Use case 3: Extract Optional values (Java 9)

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

List<String> emails = List.of(
    Optional.of("rajesh@bank.com"), Optional.empty(), Optional.of("amit@bank.com")
).stream()
    .flatMap(Optional::stream)  // present -> value, empty -> gone
    .toList();
// [rajesh@bank.com, amit@bank.com]

Use case 4: Merge multiple stream sources

JAVA
import java.util.stream.Stream;

Stream<String> merged = Stream.of(
    List.of("SAV2001", "SAV2002").stream(),
    List.of("CUR3001", "CUR3002").stream()
).flatMap(s -> s);
// Stream<String> with all 4 accounts

Use case 5: Multi-level drill-down

JAVA
package com.bank.service;

import java.util.List;

public class MultiLevelDrillDown {
    record Customer(String name, List<Account> accounts) {}
    record Account(String id, List<Transaction> transactions) {}
    record Transaction(String txnId, String tag) {}

    public static void main(String[] args) {
        List<Customer> customers = List.of(
            new Customer("Rajesh", List.of(
                new Account("SAV2001", List.of(
                    new Transaction("T1", "TRANSFER"),
                    new Transaction("T2", "DEPOSIT")
                ))
            )),
            new Customer("Priya", List.of(
                new Account("SAV2002", List.of(
                    new Transaction("T3", "WITHDRAWAL")
                ))
            ))
        );

        // 3-level drill-down with chained flatMap
        List<String> allTags = customers.stream()
            .flatMap(c -> c.accounts().stream())
            .flatMap(a -> a.transactions().stream())
            .map(Transaction::tag)
            .distinct()
            .toList();

        System.out.println("Tags: " + allTags);
    }
}
TEXT
Tags: [TRANSFER, DEPOSIT, WITHDRAWAL]

What should a senior engineer know?

flatMap is the monadic bind. It applies to both Stream and Optional. Whenever a mapping function returns a container type (Stream, Optional, List), flatMap unwraps and merges. Without flatMap, the equivalent code is nested for-loops or filter+map+isPresent chains.

Visual Overview

DIAGRAM
  Five flatMap use cases:
  ───────────────────────
  1. Nested:    [[A,B], [C], [D,E]] → [A,B,C,D,E]
  2. Split:     "A,B,C" → [A, B, C]
  3. Optional:  [Opt(A), empty, Opt(C)] → [A, C]
  4. Merge:     stream1 + stream2 → merged stream
  5. Drill:     Customer → Account → Transaction → Tag

Key Takeaway

flatMap has five primary use cases: flattening nested collections, splitting values, extracting Optional values, merging stream sources, and multi-level drill-down. Each converts a nested structure into a flat one-dimensional stream.

The most powerful use case is multi-level drill-down: chaining flatMap calls to traverse customer to accounts to transactions to tags in a single declarative pipeline, replacing three nested for-loops.

Remember one sentence: flatMap opens, expands, and flattens: nested collections become flat, CSVs become tokens, and empty Optionals disappear.

---

Share
Use ← → arrow keys to navigate