Back to Stream: Grouping & Partitioning
Q17Question 17 of 22

Group a list of strings by their length into Map<Integer, List<String>>

Key Concepts
  • groupingBy(String::length) produces Map<Integer, List<String>> where keys are lengths and values are matching strings
  • String::length is an unbound instance-method reference used as the classifier function
  • The default map type is HashMap with no key ordering; supply TreeMap::new for sorted keys
  • The default downstream is toList(); supply a different downstream like counting() to change the value type
  • Guard against null strings before grouping, because length() would throw NullPointerException
  • Grouping by a derived property is the same mechanism whether the key is length, first letter, or any function

Definition

groupingBy(String::length) partitions strings by their character count into a Map where each key is a length and each value is the list of strings of that length.
  • 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::new as the map factory for sorted keys.
  • Default downstream is toList(); change to counting(), 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?

JAVA
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));
    }
}
TEXT
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.

JAVA
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));
TEXT
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> | List of strings per length |
| counting() | Map | Count per length |
| joining(", ") | Map | Comma-separated string per length |
| toSet() | Map> | Set of strings per length |

JAVA
// 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?

JAVA
// 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.

JAVA
// 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.

JAVA
// 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?

JAVA
// 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.

JAVA
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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate