Introduction to Java 24 Stream Gatherers

Durlabh SharmaDurlabh Sharma
5 min read

Introduction

With Java 24, we’re seeing quite a handful of meaningful upgrades to the language. One such addition to Java tool suite is the Stream Gatherers. Java 24 introduces a powerful new feature to the Stream API: Stream Gatherers. This addition greatly enhances the expressiveness, flexibility, and performance of stream operations in Java. In this article, we'll delve into what Stream Gatherers are, why they're important, and how you can leverage them to write more elegant and efficient code.

What are Stream Gatherers?

Stream Gatherers are a new abstraction in Java 24 that extend the capabilities of the Stream API by introducing a mechanism for custom intermediate operations. Traditionally, the Stream API has been limited to a fixed set of intermediate operations like map, filter, flatMap, and terminal operations like collect, reduce, and forEach.

With Stream Gatherers, you can now define custom operations that transform a stream in complex ways, allowing for advanced aggregation, filtering, windowing, batching, and more. These operations still maintain the laziness and composability of streams.

Under the hood, a Stream Gatherer resembles a combination of a Collector and a Spliterator. It processes elements in a controlled fashion and allows the creation of advanced processing pipelines that were previously difficult or verbose to express using standard stream constructs.


But why do we even need Stream Gatherers?

Before Java 24, implementing custom intermediate behaviour often required falling back to imperative code or chaining awkward combinations of existing operations. Stream Gatherers solve this by providing:

  • Expressive Power: Implement complex behaviours like batching, deduplication, or time-based grouping in a clean, reusable way.

  • Performance Optimization: Avoid unnecessary memory overhead and multiple passes over data.

  • Modularity: Encapsulate custom logic into reusable components that fit seamlessly into a stream pipeline.


Okay, so how do I use Stream Gatherers in my code?

Java 24 has given the flexibility to the developers to create their own custom gatherers as well provided some basic utility gatherers for a quick start. Before we dive into those, lets first try using our first gatherer.

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

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

        List<List<Integer>> batched = arr.stream()
                .gather(Gatherers.windowFixed(2)) // Stream.gather() is where the magic happens
                .toList();

        System.out.println(batched); // Output: [[1, 2], [3, 4], [5, 6]]
    }
}

In this example, we’re using the Gatherer API to utilize one of the in-built gatherers, windowFixed. It creates the chunks of a fixed size that is provided in the gatherer which, in our case, is 2.

Now lets take a look at few gatherers provided by Java.

Built-in Stream Gatherers

Java 24 provides several useful gatherers out of the box in the java.util.stream.Gatherers utility class:

  • fold : Accumulates stream elements into a single result, similar to reduce(), but as an intermediate operation.

  • scan : Performs incremental accumulation, emitting each intermediate result, akin to a running total.

  • windowFixed : Groups elements into fixed-size, non-overlapping windows. For example, applying windowFixed(3) to a stream of [1,2,3,4,5,6,7,8] yields [[1,2,3], [4,5,6], [7,8]]

  • windowSliding : Creates overlapping windows by sliding over the stream. For instance, windowSliding(2) on [1,2,3,4,5,6,7,8] produces [[1,2], [2,3], [3,4], [4,5], [5,6], [6,7], [7,8]]

  • mapConcurrent : Applies a mapping function to each element, potentially in parallel, leveraging virtual threads for concurrent processing.

These gatherers significantly simplify many common data processing tasks.

Custom Stream Gatherers

What if the above mentioned gatherers don’t cater to your need. Maybe you need something more customised to your business needs. In that case, Java also has provided us the capability to generate custom gatherers.

Here’s an example how we can achieve that :

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Gatherer;

public class CustomGathererExample {

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

        List<Integer> result = arr.stream()
                .gather(squareEvens())
                .toList();

        System.out.println(result);
    }

    private static Gatherer<Integer, List<Integer>, Integer> squareEvens() {
        return Gatherer.ofSequential(
                ArrayList::new,
                (state, element, downstream) -> {
                    if(element % 2 == 0) {
                        downstream.push(element*element);
                    }
                    return true;
                }
        );
    }
}

With this example, now you don’t need to write the logic of generating square of even numbers out of a stream multiple times. This acts as your single reusable piece of code that can be used all across the code.

One good way to use custom gatherers can be a pattern similar to DB repository methods by creating a repository of all gatherers the codebase often needs.


Benefits of using Stream Gatherers

  1. Custom Intermediate Operations
    Stream Gatherers empower developers to create custom intermediate operations beyond the built-in capabilities of the Stream API. This opens the door to advanced stream manipulation without the need for external libraries or verbose imperative code.

  2. Improved Readability and Maintainability
    With gatherers, complex logic such as batching, windowing, and conditional grouping can be encapsulated into reusable and descriptive components. This leads to cleaner, more expressive code that's easier to understand and maintain.

  3. Enhanced Performance
    Unlike chaining multiple operations that might require repeated traversals or temporary collections, gatherers can combine logic into a single, efficient pass over the data. This can lead to reduced memory usage and faster execution, particularly on large streams.

  4. Laziness and Composability
    Gatherers preserve the lazy evaluation nature of streams, meaning elements are only processed when needed. They also fit naturally into existing stream pipelines, allowing developers to compose them with map, filter, and collect seamlessly.

  5. Support for Stateful Processing
    Some data transformations require memory of previously seen elements, for example, deduplication, running totals, or grouping based on change. Gatherers support such stateful behaviors natively, enabling more powerful processing patterns than what was previously possible with stateless operations alone.

  6. Parallel Stream Compatibility
    Just like standard stream operations, gatherers can be designed to work efficiently with parallel streams, leveraging multi-core processors to handle large datasets more quickly.

  7. Functional and Declarative Style
    Gatherers encourage a functional programming style by allowing developers to express what they want to achieve rather than how to do it. This aligns well with Java's broader move toward more declarative APIs.


Java 24 Stream Gatherers are a game-changer for developers working with streams. They provide a clean, expressive, and powerful way to define custom stream operations without compromising performance or readability. Whether you're processing log files, streaming sensor data, or just need custom grouping or filtering logic, Stream Gatherers offer the flexibility and control you've been waiting for.

As this feature matures, it's expected to become a staple of idiomatic Java programming. Mastering Stream Gatherers can significantly boost your ability to write robust, efficient, and elegant data processing pipelines in Java.

For more in-depth understanding and technical implementation of Gatherers, stay tuned and look for part 2 of the article.

0
Subscribe to my newsletter

Read articles from Durlabh Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Durlabh Sharma
Durlabh Sharma