Skip to content
PickNTech
Java

Java Streams in Practice: 10 Patterns You'll Use Every Day

The stream recipes that show up again and again in real backend code — grouping, lookups, deduplication, pagination and the mistakes to avoid.

2 min read · Updated 24 Sept 2026

Most stream tutorials stop at filter and map. Real codebases lean on a small set of more interesting patterns. Here are the ten we see most in production Java, with the pitfalls that come with them.

All examples use this record:

record Order(long id, String customer, String status, long amount, LocalDate date) {}

1. Build a lookup map by ID

Turning a list into a map for O(1) lookups is the single most common collector:

Map<Long, Order> byId = orders.stream()
        .collect(Collectors.toMap(Order::id, Function.identity()));

Common mistake

toMap throws IllegalStateException on duplicate keys. If duplicates are possible, pass a merge function: toMap(Order::customer, o -> o, (a, b) -> a).

2. Group into lists

Map<String, List<Order>> byCustomer = orders.stream()
        .collect(Collectors.groupingBy(Order::customer));

3. Group and aggregate

Pass a downstream collector to aggregate each group instead of collecting lists:

Map<String, Long> revenueByCustomer = orders.stream()
        .collect(Collectors.groupingBy(Order::customer, Collectors.summingLong(Order::amount)));
 
Map<String, Long> countByStatus = orders.stream()
        .collect(Collectors.groupingBy(Order::status, Collectors.counting()));

4. Split into two with partitioningBy

When the grouping is a yes/no question, partitioningBy always returns both keys, even when one side is empty:

Map<Boolean, List<Order>> bigAndSmall = orders.stream()
        .collect(Collectors.partitioningBy(o -> o.amount() > 10_000));

5. Top-N

List<Order> top5 = orders.stream()
        .sorted(Comparator.comparingLong(Order::amount).reversed())
        .limit(5)
        .toList();

6. Find the max per group

Map<String, Optional<Order>> biggestPerCustomer = orders.stream()
        .collect(Collectors.groupingBy(Order::customer,
                Collectors.maxBy(Comparator.comparingLong(Order::amount))));

7. Deduplicate by a field

distinct() uses equals, so it can't dedupe by a single field. Collect into a map keyed by that field instead:

Collection<Order> onePerCustomer = orders.stream()
        .collect(Collectors.toMap(Order::customer, o -> o, (first, second) -> first, LinkedHashMap::new))
        .values();

8. Flatten nested collections

List<String> allTags = articles.stream()
        .flatMap(a -> a.tags().stream())
        .distinct()
        .toList();

9. Join strings

String csv = orders.stream()
        .map(o -> String.valueOf(o.id()))
        .collect(Collectors.joining(", ", "[", "]"));
// [101, 102, 103]

10. Paginate an in-memory list

int page = 2, size = 20;
List<Order> pageItems = orders.stream()
        .skip((long) page * size)
        .limit(size)
        .toList();

For database data, paginate in the query — never load everything and page in memory.

Common mistakes

Using forEach to build collections

// Don't
List<String> names = new ArrayList<>();
orders.stream().forEach(o -> names.add(o.customer()));
 
// Do
List<String> names = orders.stream().map(Order::customer).toList();

Using streams for everything

A plain for loop is clearer when you need early return, checked exceptions, or to mutate several variables at once. Streams are a tool, not a rule.

Reaching for parallelStream()

Parallel streams share the common ForkJoinPool with the rest of your app and only pay off for large, CPU-bound work. In a web server they usually make latency worse.

Summary

NeedReach for
Lookup by keytoMap
GroupgroupingBy
Group + totalgroupingBy + summingLong / counting
Yes/no splitpartitioningBy
Top-Nsorted + limit
FlattenflatMap
Build a stringjoining

Want to go deeper?

Continue your learning journey

This article covers Java 8+ on the Java Full Stack roadmap. Follow the path to know exactly what comes next.

  1. Collections
  2. Java 8+You’re reading
  3. Spring Boot
  4. REST APIs

Keep reading