Back to Stream Creation & Utilities
Q4Question 4 of 18

Stream.concat(a,b) vs Stream.of(a,b).flatMap(identity): trade-offs for 2+ streams

Key Concepts
  • Stream.concat(a, b) is the idiomatic way to merge exactly two streams lazily
  • For 3+ streams, Stream.of(s1, s2, s3).flatMap(Function.identity()) avoids deeply nested concat calls
  • Nested concat builds a left-skewed pipeline tree that can cause deep recursion for many streams
  • Both approaches are lazy: neither stream is consumed until a terminal operation triggers
  • Both preserve encounter order: elements from the first stream, then the second
  • concat propagates close() to both inputs, important for resource-backed streams like Files.lines
  • flatMap has slightly more per-element overhead but scales cleanly to N streams

Definition

Use Stream.concat for two streams and Stream.of(streams).flatMap(identity) for three or more to avoid deeply nested pipelines.
  • concat(a, b) – lazily concatenates exactly two streams; elements from a first, then b.
  • flatMap(identity) – Stream.of(s1, s2, s3).flatMap(Function.identity()) flattens a stream of streams; handles any number cleanly.
  • Nested concat problem – concat(concat(a, b), c) builds a left-skewed tree; for many streams this causes deep pipeline nesting.
  • Both lazy – neither stream is consumed until a terminal operation triggers.
  • Close propagation – concat closes both input streams; flatMap closes each sub-stream.

Connecting two conveyor belts: concat clips them end-to-end (simple, efficient). Connecting 10 belts: concat means clipping belt1 to belt2, then that to belt3, and so on. flatMap puts all belts in a box and lays them end-to-end in one step.

Understanding the Concepts

When do you use concat vs flatMap?

JAVA
import java.util.stream.Stream;
import java.util.function.Function;

// Two streams: concat is cleaner
Stream<Trade> two = Stream.concat(
    exchangeTrades.stream(),
    internalTrades.stream());

// Three or more streams: flatMap is cleaner
Stream<Trade> three = Stream.of(
    exchangeTrades.stream(),
    internalTrades.stream(),
    manualTrades.stream()
).flatMap(Function.identity());

Why is nested concat a problem?

JAVA
// WRONG: deeply nested concat for many streams
Stream<Trade> nested = Stream.concat(
    Stream.concat(
        Stream.concat(exchangeTrades.stream(), internalTrades.stream()),
        manualTrades.stream()),
    otcTrades.stream());
// Deep nesting, hard to read, potential stack issues for many streams

// CORRECT: flatMap is flat and readable for N streams
Stream<Trade> flat = Stream.of(
    exchangeTrades.stream(),
    internalTrades.stream(),
    manualTrades.stream(),
    otcTrades.stream()
).flatMap(Function.identity());

Nested concat builds a left-skewed pipeline tree that can cause deep recursion for many streams.

What are the trade-offs?

| Aspect | concat(a, b) | flatMap(identity) |
|---|---|---|
| Best for | 2 streams | 3+ streams |
| Nesting | Clean for 2 | Clean for N |
| Overhead | Minimal | Slightly more per element |
| Close | Propagates to both | Propagates to each sub-stream |
| Lazy | Yes | Yes |
| Order | Preserved | Preserved |

How does concat handle resource streams?

concat propagates close() to both input streams, which is important for resource-backed streams like Files.lines.

JAVA
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.Stream;

// concat closes both file-backed streams
try (Stream<String> merged = Stream.concat(
        Files.lines(Path.of("/bank/settled.csv")),
        Files.lines(Path.of("/bank/pending.csv")))) {
    List<String> all = merged.toList();
} // both file handles closed

How do you merge collections (not streams)?

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

// Use Collection::stream when merging collections
List<String> all = Stream.of(listA, listB, listC)
    .flatMap(Collection::stream)
    .toList();
// Cleaner than: Stream.of(listA.stream(), listB.stream(), listC.stream()).flatMap(identity)

What about parallel stream behavior with concat?

JAVA
// concat: the result stream's parallel mode follows the first stream
Stream<String> parallel = Stream.concat(
    list1.parallelStream(),  // parallel
    list2.stream());          // sequential
// Result is parallel (follows first)

// flatMap: follows the outer stream's mode
Stream<String> parallel = Stream.of(list1, list2)
    .parallel()
    .flatMap(Collection::stream);

What are common mistakes?

JAVA
// WRONG: nesting concat for 4+ streams
Stream.concat(Stream.concat(Stream.concat(a, b), c), d); // unreadable!

// CORRECT: flatMap for 3+ streams
Stream.of(a, b, c, d).flatMap(Function.identity());

// WRONG: forgetting to close resource streams after concat
Stream<String> merged = Stream.concat(Files.lines(path1), Files.lines(path2));
merged.toList(); // handles leaked!

// CORRECT: try-with-resources
try (Stream<String> merged = Stream.concat(Files.lines(path1), Files.lines(path2))) {
    merged.toList();
}

What should a senior engineer know?

  • For very many streams (100+), flatMap(identity) is significantly better than nested concat.
  • Both work with parallel streams, but concat creates a balanced split only if both inputs are splittable.
  • Use Stream.of(list1, list2, list3).flatMap(Collection::stream) when merging collections, not streams.
  • concat supports exactly two streams. For more, use flatMap or a loop building a stream.
  • Both approaches are lazy: neither source stream is consumed until a terminal operation triggers.

Visual Overview

DIAGRAM
  Two streams → concat:
  Stream A: [1, 2, 3]
  Stream B: [4, 5, 6]
  concat:   [1, 2, 3, 4, 5, 6]

  Three+ streams → flatMap:
  Stream.of(A, B, C).flatMap(identity):
  [1, 2, 3, 4, 5, 6, 7, 8, 9]
  Flat, clean, no nesting.

Key Takeaway

For two streams, Stream.concat(a, b) is clean and efficient. For three or more, Stream.of(s1, s2, s3).flatMap(Function.identity()) avoids deeply nested concat calls that build an unbalanced pipeline tree.

Both approaches are lazy and preserve encounter order. concat propagates close() to both inputs, important for resource-backed streams. flatMap has slightly more per-element overhead but scales cleanly to any number of sources.

Remember one sentence: use concat for two streams and flatMap(identity) for three or more to keep the pipeline flat and readable.

---

Share
Use ← → arrow keys to navigate