collect()

Turn a stream into a set, map, string or grouped summary with a Collector, and know when toList() or the three-argument form fits better.

Signature
<R, A> R collect(Collector<? super T, A, R> collector)
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$collectorCollector<? super T, A, R>Describes how to create, fill, merge and finish the result. Usually one of the Collectors factory methods.
$supplierSupplier<R>Second overload: creates a new, empty result container. In parallel it may be called several times.
$accumulatorBiConsumer<R, ? super T>Second overload: adds one element to a container.
$combinerBiConsumer<R, R>Second overload: merges the second container into the first. Used only by parallel streams but always required.

Return value

RThe result container produced by the collector, or by the supplier after every element has been added with the accumulator.

Errors

Whatever the collector throws, for example IllegalStateException from Collectors.toMap on a duplicate key, or NullPointerException from collectors that reject null values.

collect is the general-purpose terminal operation: it gathers the elements into a mutable result container and returns it. Most calls pass a ready-made Collector from the Collectors class, which covers lists, sets, maps, strings and summaries.

The collectors you will use most

GoalCollector
Set of valuesCollectors.toSet(), or toCollection(TreeSet::new) for sorted
Lookup map by IDCollectors.toMap(User::id, u -> u)
GroupsCollectors.groupingBy(Order::status)
Two bucketsCollectors.partitioningBy(o -> o.paid())
One stringCollectors.joining(", ")
TotalssummingLong, averagingDouble, counting()

For a plain list, stream.toList() (Java 16) is shorter than collect(Collectors.toList()) and returns an unmodifiable list.

Downstream collectors

groupingBy and partitioningBy accept a second collector that runs on each group, which replaces loops that build maps of totals by hand. groupingBy(Order::customer, TreeMap::new, summingInt(Order::totalPence)) gives each customer's total in a sorted map. By default groupingBy builds a HashMap with no order guarantee, so pass TreeMap::new or LinkedHashMap::new when the order reaches a JSON response or a test assertion. partitioningBy always has exactly the keys false and true.

The toMap trap

Collectors.toMap(keyMapper, valueMapper) throws IllegalStateException on a duplicate key. In production that usually means two rows with the same email or two items with the same SKU, and the request fails with a 500. Decide what should happen and pass a merge function: (old, neu) -> neu keeps the latest. toMap also throws NullPointerException when the value mapper returns null, even though HashMap itself allows null values.

The three-argument form

collect(supplier, accumulator, combiner) builds a custom container without writing a Collector: collect(StringBuilder::new, StringBuilder::append, StringBuilder::append). The combiner runs only in parallel, but it must still be correct.

collect versus reduce

reduce combines immutable values into a new value each step. collect adds to a mutable container, which is far cheaper for lists, maps and strings. Building a list with reduce copies it on every element; use collect.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Build a Map from ID to entity so a batch of child rows can be attached to their parents without a query per row.
  • Group orders by status for a dashboard endpoint, counting each group with a downstream collector.
  • Join validation error messages into one string for a 400 response body.

Frequently asked questions

What is the difference between collect(Collectors.toList()) and toList()?

Stream.toList(), added in Java 16, returns an unmodifiable list. collect(Collectors.toList()) makes no promise about mutability; in current JDKs it returns an ArrayList, but code should not rely on that. If you need a mutable list, use collect(Collectors.toCollection(ArrayList::new)).

How do I fix 'IllegalStateException: Duplicate key' from Collectors.toMap?

Pass a merge function as the third argument to say which value to keep, for example toMap(User::email, u -> u, (first, second) -> first). If duplicates should never happen, the exception is telling you about bad data, so fix that instead.

How do I keep the order of keys from groupingBy?

Use the three-argument overload with a map factory: groupingBy(classifier, LinkedHashMap::new, Collectors.toList()) keeps first-seen order, and TreeMap::new sorts the keys.

More examples

toMap and duplicate keys

The two-argument toMap refuses duplicates outright. The four-argument form takes a merge function that decides which value wins and a map factory that fixes the key order.

Example · java
record User(String email, String name) {}

void main() {
    List<User> users = List.of(
        new User("[email protected]", "Ada"),
        new User("[email protected]", "Bob"),
        new User("[email protected]", "Ada L"));

    try {
        users.stream().collect(Collectors.toMap(User::email, User::name));
    } catch (IllegalStateException e) {
        IO.println(e.getMessage());
    }

    Map<String, String> latest = users.stream()
        .collect(Collectors.toMap(User::email, User::name, (old, neu) -> neu, TreeMap::new));
    IO.println(latest);
}
// Output:
// Duplicate key [email protected] (attempted merging values Ada and Ada L)
// {[email protected]=Ada L, [email protected]=Bob}

The three-argument collect

Supplier, accumulator and combiner are the three pieces every collector is made of. This form is handy for a one-off container; for anything reused, Collector.of or a Collectors method reads better.

Example · java
void main() {
    StringBuilder header = Stream.of("GET", "POST", "PUT")
        .collect(StringBuilder::new, (b, s) -> b.append(s).append(';'), StringBuilder::append);
    IO.println(header);

    ArrayList<String> list = Stream.of("x", "y")
        .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    list.add("z");
    IO.println(list);
}
// Output:
// GET;POST;PUT;
// [x, y, z]

Two results in one pass with teeing

Collectors.teeing (Java 12) feeds every element to two collectors and merges their results. For plain numeric statistics, summarizingInt gives count, sum, min, max and average together.

Example · java
void main() {
    List<Integer> latenciesMs = List.of(120, 45, 300, 80);

    String range = latenciesMs.stream().collect(Collectors.teeing(
        Collectors.minBy(Comparator.<Integer>naturalOrder()),
        Collectors.maxBy(Comparator.<Integer>naturalOrder()),
        (min, max) -> min.orElseThrow() + ".." + max.orElseThrow()));
    IO.println(range);

    IntSummaryStatistics stats = latenciesMs.stream()
        .collect(Collectors.summarizingInt(Integer::intValue));
    IO.println(stats.getMin() + " " + stats.getMax() + " " + stats.getSum());
}
// Output:
// 45..300
// 45 300 545

Discussion

  • Be the first to comment on this lesson.