groupingBy & partitioningBy in Java
Building maps of groups with downstream collectors.
null, another gives []. Same question, two answers — which collector is which?Sorting mail into pigeonholes
groupingBy(keyFn) puts every element into a pigeonhole by its key and returns a **Map<K, List<T>>. By default it's a HashMap, so the keys have no guaranteed order. Need sorted keys? Pass TreeMap::new**.
Downstream collectors
A downstream collector decides what each group becomes: **counting()** → Long, **summingInt(f)** → Integer, **averagingInt(f)** → Double, plus mapping, toSet… Inside a group, elements keep their encounter order.
Map<String, Integer> total = emps.stream()
.collect(Collectors.groupingBy(
Emp::dept,
Collectors.summingInt(Emp::salary)));Count by first letter
What does this print?
var m = Stream.of("ant", "ape", "bee", "cow")
.collect(Collectors.groupingBy(
s -> s.charAt(0), TreeMap::new,
Collectors.counting()));
System.out.println(m);{a=[ant, ape], b=[bee], c=[cow]}{a=2, b=1, c=1}{c=1, b=1, a=2}
Show the answer
{a=2, b=1, c=1} — counting() turns each group into its size, and TreeMap::new sorts the keys.
partitioningBy: exactly two buckets
partitioningBy(predicate) is the yes/no special case: the map **always has both keys, true and false** — even if a bucket is empty. It accepts downstream collectors too.
var p = Stream.of(1, 2, 3, 4, 5)
.collect(Collectors.partitioningBy(
n -> n > 3, Collectors.counting()));
// {false=3, true=2}Nobody passes
What does this print?
var p = Stream.of(1, 3, 5)
.collect(Collectors.partitioningBy(
n -> n > 10));
System.out.println(p);{false=[1, 3, 5]}{false=[1, 3, 5], true=[]}{true=null, false=[1, 3, 5]}
Show the answer
{false=[1, 3, 5], true=[]} — no number is > 10, but partitioningBy **still creates the true key**, with an empty list.
Asking for a group that doesn't exist
var g = Stream.of(2, 4).collect(
Collectors.groupingBy(
n -> n % 2 == 1));
g.get(true); // nullgroupingBy only creates keys that actually occur. A missing key gives null — a NullPointerException waiting to happen.
var p = Stream.of(2, 4).collect(
Collectors.partitioningBy(
n -> n % 2 == 1));
p.get(true); // []Both keys always exist, so you get an empty list instead.
Reports in one expression
"Revenue per region", "orders per status", "passed vs failed tests" — grouping is how dashboards and reports get built. Teams that print such maps in emails or logs pass TreeMap::new so the output order doesn't change between runs.
Key takeaways
- groupingBy(key) → Map<K, List<T>> (a HashMap, unordered)
- groupingBy(key, TreeMap::new, downstream) for sorted keys
- Downstream: counting(), summingInt(), mapping(), toSet()…
- partitioningBy always has both true and false keys
groupingBy is essentially SQL's GROUP BY in Java form — downstream collectors play the role of COUNT, SUM and AVG.
Practice questions
What does this print?
Map<Integer, List<String>> byLen =
Stream.of("cat", "ox", "dog", "be")
.collect(Collectors.groupingBy(
String::length, TreeMap::new,
Collectors.toList()));
System.out.println(byLen);- {2=[ox, be], 3=[cat, dog]}
- {3=[cat, dog], 2=[ox, be]}
- {2=2, 3=2}
- {2=[be, ox], 3=[cat, dog]}
Check your answer
{2=[ox, be], 3=[cat, dog]}. Elements are grouped by length, the TreeMap sorts the keys, and each list keeps the encounter order of its elements.
What does this print?
Map<Boolean, Long> p = Stream.of(1, 2, 3, 4, 5)
.collect(Collectors.partitioningBy(
n -> n > 3, Collectors.counting()));
System.out.println(p.get(true) + " " + p.get(false));- 3 2
- 2 3
- 4 5
- 5 0
Check your answer
2 3. 4 and 5 are > 3 (2 of them); 1, 2, 3 are not (3 of them). counting is the downstream collector applied to each partition.