Definition
- groupingBy(classifier) groups stream elements by the key returned by the classifier function.
- String::length is an unbound method reference that returns each string's character count.
- Default map is HashMap with no ordering; supply
TreeMap::newas the map factory for sorted keys. - Default downstream is
toList(); change tocounting(),joining(), or others for different aggregations. - Null safety: filter out null strings before grouping to avoid NullPointerException.
Priya at the National Bank of India sorts reference codes into bins by how many characters they have: a three-character bin for codes like SAV and CUR, a seven-character bin for codes like SAV2001, and so on.
Understanding the Concepts
What does this question ask?
The interviewer wants you to group strings by their length using groupingBy. This tests the classifier function, map type selection, and downstream collectors.
How do you group strings by length?
package com.bank.core;
import java.util.*;
import java.util.stream.Collectors;
public class GroupByLength {
public static void main(String[] args) {
List<String> codes = List.of(
"SAV", "CUR", "SAV2001", "FD2024", "HDFC", "ICICI", "SBI");
Map<Integer, List<String>> byLength = codes.stream()
.collect(Collectors.groupingBy(String::length));
byLength.forEach((len, list) ->
System.out.println("Length " + len + ": " + list));
}
}
Length 3: [SAV, CUR, SBI]
Length 7: [SAV2001]
Length 6: [FD2024]
Length 4: [HDFC]
Length 5: [ICICI]
How do you get sorted keys?
Supply a TreeMap factory as the second argument.
Map<Integer, List<String>> sorted = codes.stream()
.collect(Collectors.groupingBy(
String::length,
TreeMap::new,
Collectors.toList()));
sorted.forEach((len, list) ->
System.out.println("Length " + len + ": " + list));
Length 3: [SAV, CUR, SBI]
Length 4: [HDFC]
Length 5: [ICICI]
Length 6: [FD2024]
Length 7: [SAV2001]
How do you change the downstream collector?
| Downstream | Result type | What it produces |
|---|---|---|
| toList() (default) | Map
| counting() | Map
| joining(", ") | Map
| toSet() | Map
// Count strings per length
Map<Integer, Long> countByLength = codes.stream()
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
// Join strings per length
Map<Integer, String> joinedByLength = codes.stream()
.collect(Collectors.groupingBy(
String::length, Collectors.joining(", ")));
How do you group by a different derived key?
// Group by first character
Map<Character, List<String>> byFirstChar = codes.stream()
.collect(Collectors.groupingBy(s -> s.charAt(0)));
// Group by uppercase first letter (case-insensitive)
Map<Character, List<String>> byInitial = codes.stream()
.collect(Collectors.groupingBy(
s -> Character.toUpperCase(s.charAt(0))));
The mechanism is the same: just change the classifier function.
What are common mistakes?
Mistake: not handling null strings.
// WRONG: NullPointerException on null.length()
List<String> withNull = Arrays.asList("SAV", null, "CUR");
Map<Integer, List<String>> wrong = withNull.stream()
.collect(Collectors.groupingBy(String::length)); // NPE!
// CORRECT: filter nulls first
Map<Integer, List<String>> correct = withNull.stream()
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(String::length));
Mistake: assuming the result map has sorted keys.
// WRONG: HashMap has no ordering guarantee
Map<Integer, List<String>> unordered = codes.stream()
.collect(Collectors.groupingBy(String::length));
// Keys could appear as: 7, 3, 6, 4, 5
// CORRECT: use TreeMap for sorted keys
Map<Integer, List<String>> ordered = codes.stream()
.collect(Collectors.groupingBy(
String::length, TreeMap::new, Collectors.toList()));
What about grouping with multiple levels?
// Two-level grouping: by length, then by first character
Map<Integer, Map<Character, List<String>>> twoLevel = codes.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.groupingBy(s -> s.charAt(0))));
What about empty groups?
groupingBy only creates keys for values that exist. If no string has length 2, there is no key 2 in the map.
Map<Integer, List<String>> byLength = codes.stream()
.collect(Collectors.groupingBy(String::length));
// Length 2 does not exist
List<String> len2 = byLength.getOrDefault(2, List.of());
System.out.println("Length 2: " + len2); // []
What are common interview follow-up questions?
- "How do you find the length with the most strings?" Stream the entrySet and max by list size.
- "How do you flatten the groups back into a single list?" Use values().stream().flatMap(Collection::stream).
- "How do you group employees by department?" Same pattern: groupingBy(Employee::getDepartment).
Visual Overview
List: ["SAV", "CUR", "SAV2001", "FD2024", "HDFC", "ICICI", "SBI"]
│
│ groupingBy(String::length)
▼
┌──────────────────────────────────────┐
│ 3 → ["SAV", "CUR", "SBI"] │
│ 4 → ["HDFC"] │
│ 5 → ["ICICI"] │
│ 6 → ["FD2024"] │
│ 7 → ["SAV2001"] │
└──────────────────────────────────────┘
With TreeMap::new:
Keys are sorted: 3, 4, 5, 6, 7
Key Takeaway
Grouping strings by their length uses groupingBy with String::length as the classifier, producing a Map from length to the list of strings of that length. The classifier is an unbound instance-method reference, and the same mechanism works for any derived key.
Supply a TreeMap factory for sorted keys and guard against null strings before the classifier runs. The default downstream is toList(), but swapping it to counting(), joining(), or any other collector changes the aggregation without changing the grouping structure.
At the National Bank of India, Rajesh used this in a validation report to bucket reference codes by length and spot any of an unexpected size, flagging potential data entry errors.
Remember one sentence: groupingBy(String::length) partitions strings by character count, and supplying TreeMap::new sorts the length keys.
---