Thank you for sharing your practical experience with Gatherers, Olexandr—it is 
much appreciated!

Some thoguhts while reading your email:


  *
Given that your custom Gatherers shown never short-circuit, you might find it 
performance-wise valuable to instantiate then with Gatherer.Integrator.ofGreedy.


  *
For your finishers, I've personally found it valuable to short-circuit of the 
downstream push returns false, so in the cases above it'd be something like

        If (!downstream.push(element))
            return;

Cheers,
√


Viktor Klang
Software Architect, Java Platform Group
Oracle

Confidential – Oracle Internal
________________________________
From: core-libs-dev <[email protected]> on behalf of Olexandr 
Rotan <[email protected]>
Sent: Friday, 17 October 2025 20:45
To: core-libs-dev <[email protected]>
Subject: My expirience with gatherers

Greetings to the core libs folks. I have been using Gatherers extensively for 
my job in a past few months, and would like to share some of the gatherers that 
I have been extensively using, so maybe some of them may be a source of 
inspiration for evolving the Gatherers class.

1. eagerlyConsume()
Implementation:

public static <T> Gatherer<T, ?, T> eagerlyConsume() {
    return Gatherer.of(
        ArrayList<T>::new,
        (list, val, downstream) -> {
            list.add(val);
            return true;
        },
        (left, right) -> {
            left.addAll(right);
            return left;
        },
        (list, downstream) -> {
            for (var item : list) {
                downstream.push(item);
            }
        }
    );
}

Purpose: many times, i need to perform a concurrent mapping of jpa entities to 
dtos. Unfortunately, mapConcurrent does not accept custom executors, which i 
need in order to propagate auth, transaction and other contexts. So, therefore, 
I previously have used following pattern:

stream().map(COmpletableFuture.supplyAsync(..., 
executor)).toList().stream().map(CompletableFuture::join)

toList is required here to eagerly start all futures, as otherwise the will 
actually launch sequentially due to the pulling nature of streams. With 
gatherer, on the other hand, i can achieve following:
stream().map(COmpletableFuture.supplyAsync(..., 
executor))..gather(eagerlyConsume()) .map(CompletableFuture::join), which looks 
much more readable, and (presumably, haven't actually verified it) should have 
better performance

2. ofCollector
Implementation:

public <T, A, R> Gatherer<T, A, R> ofCollector(Collector<T, A, R> collector) {
    return Gatherer.of(
        collector.supplier(),
        (a, t, _) -> {
            collector.accumulator().accept(a, t);
            return true;
        },
        collector.combiner(),
        (state, downstream) -> 
downstream.push(collector.finisher().apply(state))
    );
}

Pretty self explanatory, this is just an adapter of collector to gatherer, 
allowing arbitrary collector-defined folds

3. collectThenFlatten & co
Implementations:

public static <T, A, R extends Collection<T>> Gatherer<T, A, T> 
collectThenFlatten(Collector<T, A, R> collector) {
    return Gatherer.of(
        collector.supplier(),
        (a, t, _) -> {
            collector.accumulator().accept(a, t);
            return true;
        },
        collector.combiner(),
        (state, downstream) -> {
            for (var item : collector.finisher().apply(state)) {
                downstream.push(item);
            }
        }
    );
}

public static <T, A, K, V, R extends Map<K, V>> Gatherer<T, A, Map.Entry<K, V>> 
collectThenFlattenEntries(Collector<T, A, R> collector) {
    return Gatherer.of(
        collector.supplier(),
        (a, t, _) -> {
            collector.accumulator().accept(a, t);
            return true;
        },
        collector.combiner(),
        (state, downstream) -> {
            for (var entry : collector.finisher().apply(state).entrySet()) {
                downstream.push(entry);
            }
        }
    );
}

These are more specialized adapters of collector adapters, mostly a convenience 
for avoiding flatMapping results, THough, I would like to note that 
collectThenFlattenEntries is mostly used specifically with groupingBy 
collector, to avoid following nasty chains:

collect(groupingBy(...)).entrySet().stream()

Maybe it's just my personal preferences, but i really dislike this back n forth 
from stream to map, then to set and to stream again, so this gatherer seems 
pretty pleasant to use

That's basically all that I wanted to share regarding this topic, hope this 
experience will have some value for core libs maintainers

Best regards

Reply via email to