Java · Advanced level

Advanced level · Module 1: Generics, Lambdas and Streams · Lesson 4 of 4

Grouping, Summarising and Optional

Group and summarise data with Collectors, and handle possibly missing values with Optional.

In this lesson you will

  • Use groupingBy with downstream collectors
  • Use Optional instead of returning null

Key concepts

Collectors.groupingBysummingInt / counting / averagingDoubleOptionalorElse / map / ifPresent

The lesson

Collectors.groupingBy(Sale::region) builds a Map from each region to its list of sales. A downstream collector summarises each group instead: groupingBy(Sale::region, summingInt(Sale::amount)) gives the total per region. Wrapping the map in a TreeMap sorts the regions.

Methods that may find nothing should return Optional<T> rather than null. findFirst() and max() on streams already do this. Callers then decide explicitly: orElse(default), orElseThrow(), ifPresent(action) or map to transform the value if it exists.

Do not call get() on an Optional without checking, and do not use Optional for fields or parameters; it is designed for return values.

Step by step

  1. Group sales by region and count them.
  2. Change the downstream collector to sum amounts.
  3. Find the best-selling item with max and Comparator.
  4. Handle the empty-list case with Optional.orElse.
Code example

Sales per region with streams and groupingBy

Group sales by region, total them in sorted order and find the top region — without writing a single loop.

JavaRegionReport.java
import java.util.List;import java.util.Map;import java.util.TreeMap;import java.util.stream.Collectors; public class RegionReport {    record Sale(String region, String item, int amount) {}     public static void main(String[] args) {        List<Sale> sales = List.of(            new Sale("Juba", "rice", 14000),            new Sale("Wau", "sugar", 5600),            new Sale("Juba", "soap", 4800),            new Sale("Malakal", "rice", 7000),            new Sale("Wau", "rice", 10500),            new Sale("Juba", "sugar", 8400)        );         Map<String, Integer> totals = sales.stream()            .collect(Collectors.groupingBy(Sale::region, TreeMap::new,                     Collectors.summingInt(Sale::amount)));         totals.forEach((region, total) ->            System.out.printf("%-8s %,7d SSP%n", region, total));         String topRegion = totals.entrySet().stream()            .max(Map.Entry.comparingByValue())            .map(Map.Entry::getKey)            .orElse("none");        System.out.println("Top region: " + topRegion);    }}

Output

Juba      27,200 SSP
Malakal    7,000 SSP
Wau       16,100 SSP
Top region: Juba

How it works

groupingBy(Sale::region, TreeMap::new, summingInt(Sale::amount)) builds a map from each region to the sum of its amounts; TreeMap::new keeps the regions in alphabetical order. The second pipeline streams the map's entries, picks the entry with the largest value and maps it to its key. max returns an Optional, so orElse supplies a value for an empty list. Amounts are illustrative training data.

Key concepts

Collectors.groupingBydownstream summingIntTreeMap for sorted keysOptional from max

Practice activity

Produce a regional sales summary with totals per region and the top product per region, handling regions with no sales.

Expected result: The summary matches the expected totals, and a region with no sales prints a clear 'no sales' line instead of failing.

Check your understanding

  1. What does groupingBy return?

    Show answer

    A Map from each key to the grouped results, by default a list of elements.

  2. Why return Optional instead of null?

    Show answer

    It makes the possibility of no result explicit and forces callers to handle it.

Taught live by an instructor — online or in person. Free course lessons are for self-study and do not lead to a certificate — see registration options.