Skip to content

Java Streams: filter, map and collect explained

November 4, 2026 · 2 min read

A Java stream is a pipeline: filter keeps the elements you want, map converts each one, and collect gathers the result, for example into groups with Collectors.groupingBy.

Example

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

public class Main {
    public static void main(String[] args) {
        List<Sale> week = List.of(
            new Sale("Bloom Café", "HE-5LB", 6), new Sale("Steam & Co", "HE-5LB", 8),
            new Sale("Hearth", "SO-5LB", 3), new Sale("Juniper", "HE-5LB", 4),
            new Sale("Bloom Café", "DC-5LB", 2), new Sale("Ferry Street", "SO-5LB", 5),
            new Sale("Steam & Co", "SO-5LB", 6), new Sale("Hearth", "HE-5LB", 5));

        List<Integer> big = week.stream()
                .filter(s -> s.bags() >= 5)
                .map(Sale::bags)
                .toList();
        System.out.println(big);

        Map<String, Integer> bagsBySku = week.stream()
                .collect(Collectors.groupingBy(Sale::sku, TreeMap::new,
                        Collectors.summingInt(Sale::bags)));
        System.out.println(bagsBySku);
    }
}

record Sale(String cafe, String sku, int bags) {}

Save it as Main.java and run java Main.java. Output:

[6, 8, 5, 6, 5]
{DC-5LB=2, HE-5LB=23, SO-5LB=14}

How it works

  • week.stream() is the source. The list itself never changes.
  • filter(s -> s.bags() >= 5) keeps only sales of five bags or more.
  • map(Sale::bags) turns each remaining Sale into its bag count.
  • toList() is the terminal operation. Streams are lazy, so nothing runs until you call one.

Collectors.groupingBy(Sale::sku, ...) builds a Map with one key per SKU. The last argument, summingInt(Sale::bags), is a downstream collector: it adds up the bags inside each group. Passing TreeMap::new just sorts the keys; without it you get a HashMap with no guaranteed order.

If you leave out the downstream collector, each key maps to a List<Sale> of its sales instead.

Common mistake

Forgetting the terminal operation. This compiles but does nothing:

week.stream().filter(s -> s.bags() >= 5).map(Sale::bags);

End the pipeline with toList(), collect(...), sum() or similar.

Another trap: toList() returns an unmodifiable list, so calling add on it throws UnsupportedOperationException. Copy it with new ArrayList<>(list) if you need to change it.

Takeaway

Think of a stream as filter, then map, then one terminal step. Add groupingBy with a downstream collector whenever you need totals per category.

More on Java & Kotlin