DEV Community

Sadiul Hakim
Sadiul Hakim

Posted on

Java Stream Gatherer Tutorial

1. What is a Gatherer?

A Gatherer is a new stream collector-like abstraction introduced in Java.
It allows you to transform a stream in ways that require maintaining state or looking at multiple elements together, beyond what map, filter, and flatMap can easily do.

Think of it as a customizable intermediate stream operation, similar in power to writing your own collector, but for intermediate operations instead of terminal ones.

2. When to Use It (and When to Avoid It)

Use Gatherer when:

  • You need stateful transformations (like "group consecutive elements", "batch into windows", "emit only changes").
  • Standard map / filter / flatMap aren’t enough.
  • You want reusable and composable transformations without resorting to imperative loops.

Avoid Gatherer when:

  • A simple map, filter, or flatMap does the job.
  • You just need a terminal reduction (collect, reduce) → then use a Collector.
  • Performance is critical, and you don’t need advanced stateful behavior → Gatherer adds some abstraction overhead.

3. How to Use a Gatherer

The API revolves around Gatherer.of(...).
A Gatherer defines how to process elements and emit zero, one, or many outputs for each input.

A Gatherer has:

  • Initializer → creates mutable state (optional).
  • Integrator → processes each input element with state.
  • Finisher → final step when the stream ends (optional).

Signature (simplified):

static <T, R, S> Gatherer<T, S, R> of(
    Supplier<S> initializer,
    Integrator<T, S, R> integrator,
    BiConsumer<S, Downstream<? super R>> finisher
)
Enter fullscreen mode Exit fullscreen mode

4. Examples

Example 1: Removing Consecutive Duplicates

import java.util.stream.*;

public class GathererExample1 {
    public static void main(String[] args) {
        var result = Stream.of("A", "A", "B", "B", "C", "A", "A")
            .gather(Gatherers.distinctAdjacent()) // built-in gatherer
            .toList();

        System.out.println(result); // [A, B, C, A]
    }
}
Enter fullscreen mode Exit fullscreen mode

Gatherers.distinctAdjacent() is a built-in gatherer that removes only consecutive duplicates.

Example 2: Batching (Windowing Elements)

import java.util.stream.*;
import java.util.*;

public class GathererExample2 {
    public static void main(String[] args) {
        var result = Stream.iterate(1, n -> n + 1).limit(10)
            .gather(Gatherers.windowFixed(3)) // windows of size 3
            .toList();

        System.out.println(result);
        // [[1,2,3], [4,5,6], [7,8,9], [10]]
    }
}
Enter fullscreen mode Exit fullscreen mode

Gatherers.windowFixed(3) groups elements into lists of size 3.

Example 3: Custom Gatherer — Emit Only Increasing Values

import java.util.stream.*;
import java.util.function.*;

public class GathererExample3 {
    public static void main(String[] args) {
        var increasingGatherer = Gatherer.of(
            () -> new int[]{Integer.MIN_VALUE}, // state holder
            (state, elem, downstream) -> {
                if (elem > state[0]) {
                    state[0] = elem;
                    downstream.push(elem); // emit only if greater
                }
                return true; // keep going
            }
        );

        var result = Stream.of(1, 2, 2, 5, 3, 7, 6, 8)
            .gather(increasingGatherer)
            .toList();

        System.out.println(result); // [1, 2, 5, 7, 8]
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, we wrote a custom Gatherer that emits only when the sequence increases.

5. Key Built-in Gatherers in Java

Java ships with a few ready-made gatherers under java.util.stream.Gatherers:

  • distinctAdjacent() → removes consecutive duplicates.
  • scanLeft(initial, op) → cumulative scan (like reduce but keeps intermediates).
  • windowFixed(size) → groups elements into fixed-size windows.
  • windowSliding(size) → sliding windows.
  • mapConcurrent(...) → concurrent mapping.

6. Summary

  • Gatherer = Custom intermediate transformation for streams.
  • Use when you need stateful or advanced element processing.
  • Provides built-in gatherers (windowFixed, distinctAdjacent, etc.).
  • Can define custom gatherers for domain-specific needs.

Top comments (0)