Modern Java

48. Streams — Gatherers (Java 22+)

Stream.gather(Gatherer) (Java 22+) adds a powerful stateful intermediate operation API — enabling sliding windows, prefix-while, deduplication by key, and other patterns that filter/map cannot express.

Java 22+ (standard in Java 24)

Explanation

The Stream intermediate operation API (filter, map, flatMap) is stateless — each element is processed independently. Some useful operations require state across elements: a sliding window needs to remember previous elements; takeWhile is already there but custom prefix predicates are awkward.

Gatherer<T,A,R> (JEP 461, standard Java 24) is the generalisation: it defines an optional initialiser (create state), an integrator (process each element, emit zero or more downstream), an optional combiner (for parallel use), and a finisher (emit anything left in state). Built-in gatherers are in java.util.stream.Gatherers: windowFixed(n), windowSliding(n), scan(identity, fn) (running accumulation), fold(identity, fn), mapConcurrent(n, fn).

Example

import java.util.List;
import java.util.stream.Gatherers;

public class StreamsGatherers {
    public static void main(String[] args) {
        // windowFixed: non-overlapping windows of size 3
        List<List<Integer>> fixed = java.util.stream.Stream.iterate(1, n -> n + 1)
            .limit(9)
            .gather(Gatherers.windowFixed(3))
            .toList();
        System.out.println("Fixed windows: " + fixed);
        // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

        // windowSliding: overlapping windows of size 3
        List<List<Integer>> sliding = java.util.stream.Stream.of(1, 2, 3, 4, 5)
            .gather(Gatherers.windowSliding(3))
            .toList();
        System.out.println("Sliding windows: " + sliding);
        // [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

        // scan: running sum
        List<Integer> runningSum = java.util.stream.Stream.of(1, 2, 3, 4, 5)
            .gather(Gatherers.scan(() -> 0, Integer::sum))
            .toList();
        System.out.println("Running sum: " + runningSum);
        // [1, 3, 6, 10, 15]

        // fold: like reduce but emits final value (not every intermediate)
        var total = java.util.stream.Stream.of(1, 2, 3, 4, 5)
            .gather(Gatherers.fold(() -> 0, Integer::sum))
            .findFirst();
        System.out.println("Fold total: " + total); // Optional[15]
    }
}

Sources