Stream gatherers (2nd preview)

Java 23 Course · lesson 3 of 15 · 5 min read

The missing piece of the Streams API: your own intermediate operation.

Open this lesson in the learning hub

Key points

  • You could always write a custom terminal op with Collector, but never a custom intermediate one.
  • So sliding windows, fixed batches and running totals meant dropping out of the stream entirely.
  • A Gatherer plugs into stream.gather(...) and can hold state, emit many, or stop early.
  • Built-ins cover the common cases: windowFixed, windowSliding, fold, scan.
  • Preview in 22 and 23; it went final in Java 24, so on 24+ no flag is needed.

Example

// Java 23 preview API (final in 24) - shown for reference, needs --enable-preview.
//
//   import java.util.stream.Gatherers;
//
//   List<List<Integer>> batches = Stream.of(1,2,3,4,5,6,7)
//       .gather(Gatherers.windowFixed(3))
//       .toList();                      // [[1,2,3],[4,5,6],[7]]
//
//   List<Integer> running = Stream.of(1,2,3,4)
//       .gather(Gatherers.scan(() -> 0, Integer::sum))
//       .toList();                      // [1,3,6,10]
//
// What you had to write before gatherers, which still runs on Java 21:
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> src = List.of(1, 2, 3, 4, 5, 6, 7);

        // Fixed windows, the pre-gatherer way: leave the stream and count manually
        List<List<Integer>> batches = new ArrayList<>();
        for (int i = 0; i < src.size(); i += 3) {
            batches.add(new ArrayList<>(src.subList(i, Math.min(i + 3, src.size()))));
        }
        System.out.println("windowFixed(3) by hand : " + batches);

        // Running total, the pre-gatherer way: a mutable accumulator outside the pipeline
        int[] acc = { 0 };
        List<Integer> running = src.stream().map(n -> acc[0] += n).collect(Collectors.toList());
        System.out.println("scan by hand           : " + running);
        System.out.println("(gatherers make both of these one call)");
    }
}

Gatherers are to intermediate operations what collectors are to terminal ones.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Java 23 Course course, and every lesson in it is listed on the Java 23 Course contents page.