Skip to main content

JDK 25's Stream Gatherer Example

· 3 min read
Linh Nguyen
T-90MS Main Battle Tank
thumbnail

Stream Gatherers are basically custom superpowers for your Stream API (they return another Stream and mostly just chill until a terminal operation shows up to actually do the work, basically intermediate operations). This JEP is like giving the Stream API steroids: now you can bend streams to your will in ways that would make previous Java developers weep tears of pure joy.

This article is a part of the original article here.

Stream Gatherers (JEP 485)

Current status: Finalized in JDK 24.

The Example

Take this classic headache: filtering by a field that's not the ID.

You've got two choices: wrap your objects in some custom contraption or drag Google Guava into the party with its Equivalence wizardry:

The Data Class

public record Person(Integer id, String name) {}

The Implementations

First we create our own gatherer:

public class DistinctByNameGatherer 
implements Gatherer<Person, Set<String>, Person> {

@Override
public Supplier<Set<String>> initializer() {
// Create the state: a HashSet that will track which names we've seen
return HashSet::new;
}

@Override
public Integrator<Set<String>, Person, Person> integrator() {
return Integrator.ofGreedy(
(state, element, downstream) -> {
// Extract the name from the Person element
var extracted = element.name();

// If we haven't seen this name before...
if (!state.contains(extracted)) {
// Remember it for posterity
state.add(extracted);

// Push the Person downstream (keep it in the stream)
downstream.push(element);
}

// Always return true because we're optimists
return true;
});
}
}

Then we apply our beautiful creation:

persons.stream()
.gather(new DistinctByNameGatherer())
.toList();

You can dig into JEP 485 for the nerdy details, or just download JDK 24 and explore the java.util.stream.Gatherers class for some sweet built-in examples!

Deep-dive by José Paumard

You can see the videos by José Paumard for a more comprehensive explanation: