Definition
- Gatherers are CUSTOM INTERMEDIATE OPERATIONS, the intermediate counterpart to Collectors (which are terminal).
- stream.gather(Gatherer) inserts a custom stateful transformation into the middle of a pipeline.
- Built-in gatherers:
Gatherers.windowFixed(n)creates fixed-size batches,windowSliding(n)creates overlapping windows,foldaccumulates state emitting intermediate results. - Why needed: map, filter, and flatMap are stateless. They cannot express "group every 3 elements into a sub-list" or "emit running totals."
- A Gatherer has four parts: initializer (state), integrator (process element and maybe emit), combiner (parallel), finisher (emit leftovers).
- Gatherers complement Collectors: Collectors customize HOW you produce the final result, Gatherers customize HOW you process elements in the pipeline.
At National Bank of India, Collectors are like the final packaging at the end of a transaction processing assembly line. Gatherers are like intermediate processing stations: "batch every 3 transactions for bulk settlement" (windowFixed), "look at each 3-transaction window for fraud patterns" (windowSliding), "compute running balance after each transaction" (fold).
Understanding the Concepts
What problem do Gatherers solve?
Before Gatherers, custom stateful intermediate logic (sliding windows, fixed-size batching, running de-duplication) had no clean API. You had to fight with reduce, external mutable state, or collect-then-restream patterns.
How does windowFixed work?
package com.bank.service;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Gatherers;
public class GathererDemo {
record Transaction(String id, BigDecimal amount) {}
public static void main(String[] args) {
List<Transaction> txns = List.of(
new Transaction("T001", new BigDecimal("50000")),
new Transaction("T002", new BigDecimal("75000")),
new Transaction("T003", new BigDecimal("30000")),
new Transaction("T004", new BigDecimal("90000")),
new Transaction("T005", new BigDecimal("45000"))
);
// Batch transactions in groups of 2 for bulk settlement
txns.stream()
.gather(Gatherers.windowFixed(2))
.forEach(batch -> System.out.println("Batch: " + batch));
}
}
Batch: [Transaction[id=T001, amount=50000], Transaction[id=T002, amount=75000]]
Batch: [Transaction[id=T003, amount=30000], Transaction[id=T004, amount=90000]]
Batch: [Transaction[id=T005, amount=45000]]
The last batch has only 1 element because 5 is not evenly divisible by 2.
How does windowSliding work?
// Sliding window of 3: detect fraud patterns
txns.stream()
.gather(Gatherers.windowSliding(3))
.forEach(window -> System.out.println("Window: " + window));
Window: [T001, T002, T003]
Window: [T002, T003, T004]
Window: [T003, T004, T005]
Each window overlaps with the previous by n-1 elements.
How does fold work for running totals?
// Running balance after each transaction
txns.stream()
.gather(Gatherers.fold(
() -> BigDecimal.ZERO,
(balance, txn) -> balance.add(txn.amount())))
.forEach(balance -> System.out.println("Running total: Rs." + balance));
Running total: Rs.50000
Running total: Rs.125000
Running total: Rs.155000
Running total: Rs.245000
Running total: Rs.290000
Unlike reduce which only returns the final value, fold emits the accumulated value after each element.
How are Gatherers structured?
A custom Gatherer has four parts:
| Part | Purpose | Required |
|------|---------|----------|
| Initializer | Creates mutable state | Yes |
| Integrator | Processes one element, may emit results | Yes |
| Combiner | Merges state for parallel streams | Optional |
| Finisher | Emits remaining state when stream ends | Optional |
How are Gatherers different from Collectors?
| Feature | Gatherers | Collectors |
|---------|-----------|------------|
| Pipeline position | Intermediate | Terminal |
| Input/Output | Stream in, Stream out | Stream in, single result |
| Stateful | Yes | Yes |
| Added in | Java 24 | Java 8 |
| Use case | Transform elements mid-pipeline | Produce final result |
What are common mistakes?
WRONG: Using Gatherers on Java versions before 24.
// COMPILE ERROR on Java 21 or earlier:
txns.stream().gather(Gatherers.windowFixed(2));
// Gatherers are not available before Java 24
What should a senior engineer know?
- Gatherers fill the gap between map/filter (stateless intermediates) and Collectors (stateful terminal). They enable custom stateful intermediate processing.
- Before Gatherers, sliding windows required
IntStream.range(0, list.size() - n + 1).mapToObj(i -> list.subList(i, i + n)), which is clunky and only works on RandomAccess lists. - The
scangatherer emits prefix-accumulation at each step, similar to functional programming'sscanl. - If your interview targets Java 21 or earlier, mention Gatherers as "coming in Java 24" to show awareness of the latest API evolution.
Visual Overview
windowFixed(2):
[T001, T002, T003, T004, T005]
│ │ │ │ │
└────┘ └────┘ │
Batch 1 Batch 2 Batch 3
[T001,T002] [T003,T004] [T005]
windowSliding(3):
[T001, T002, T003, T004, T005]
└────────────┘ Window 1
└────────────┘ Window 2
└────────────┘ Window 3
fold (running total):
T001(50K) → 50K
T002(75K) → 125K
T003(30K) → 155K
T004(90K) → 245K
T005(45K) → 290K
Key Takeaway
Stream Gatherers (JEP 485, Java 24) fill the gap between map/filter (stateless intermediates) and Collectors (terminal). They enable custom stateful intermediate operations: windowFixed for batching, windowSliding for pattern detection, and fold for running accumulations.
Before Gatherers, these patterns required ugly workarounds with IntStream.range or external libraries. Gatherers complement Collectors: Collectors customize HOW you produce the final result (terminal), Gatherers customize HOW you process elements IN the pipeline (intermediate).
At National Bank of India, batching transactions for bulk settlement uses gather(Gatherers.windowFixed(2)), and computing running balances uses gather(Gatherers.fold(...)), both in a clean pipeline.
Remember one sentence: Gatherers are to intermediate operations what Collectors are to terminal operations, enabling stateful windowing, batching, and accumulation mid-pipeline.
---