Definition
- groupingBy(identity(), counting()) groups strings by their value and counts occurrences, producing
Map. - TreeMap::new as the map factory produces alphabetically sorted keys without a separate sort step.
- sorted(comparingByValue().reversed()) on the entry set produces a frequency-ranked list from most common to least.
- map(String::toLowerCase) before groupingBy enables case-insensitive counting.
- This single collector call replaces the verbose manual pattern of iterating with
map.merge(key, 1L, Long::sum).
At National Bank of India, the operations team logs every transaction type in a day: ["NEFT", "UPI", "NEFT", "RTGS", "UPI", "NEFT"]. At end of day, they need a summary: NEFT appeared 3 times, UPI appeared 2 times, RTGS appeared 1 time. The groupingBy plus counting collector produces this summary in one pass.
Understanding the Concepts
How do you count string frequencies?
package com.bank.analytics;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class StringFrequencyDemo {
public static void main(String[] args) {
List<String> txnTypes = List.of(
"NEFT", "UPI", "NEFT", "RTGS", "UPI", "NEFT");
// Basic frequency map
Map<String, Long> freq = txnTypes.stream()
.collect(Collectors.groupingBy(
Function.identity(), Collectors.counting()));
System.out.println("Frequency: " + freq);
}
}
Frequency: {UPI=2, RTGS=1, NEFT=3}
Note: HashMap produces random key order.
How do you get sorted keys?
Pass TreeMap::new as the map factory.
// Sorted keys (alphabetical)
Map<String, Long> sortedFreq = txnTypes.stream()
.collect(Collectors.groupingBy(
Function.identity(),
TreeMap::new,
Collectors.counting()));
System.out.println("Sorted: " + sortedFreq);
Sorted: {NEFT=3, RTGS=1, UPI=2}
How do you rank by frequency?
// Ranked by frequency descending
List<Map.Entry<String, Long>> ranked = freq.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.toList();
ranked.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
NEFT: 3
UPI: 2
RTGS: 1
How do you handle case-insensitive counting?
// Case-insensitive: normalize to lowercase before counting
List<String> mixed = List.of("Neft", "neft", "NEFT", "UPI", "upi");
Map<String, Long> caseInsensitive = mixed.stream()
.map(String::toLowerCase)
.collect(Collectors.groupingBy(
Function.identity(), Collectors.counting()));
System.out.println(caseInsensitive); // {neft=3, upi=2}
How is this different from the manual approach?
// Manual approach (verbose, error-prone)
Map<String, Long> manual = new HashMap<>();
for (String type : txnTypes) {
manual.merge(type, 1L, Long::sum);
}
The stream approach is one line, declarative, and works with parallel streams. The manual approach is imperative and requires an explicit mutable Map.
What about counting with a filter?
// Count only transaction types that appear more than once
Map<String, Long> repeats = txnTypes.stream()
.collect(Collectors.groupingBy(
Function.identity(), Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() > 1L)
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue));
System.out.println("Repeated: " + repeats); // {UPI=2, NEFT=3}
What are common mistakes?
WRONG: Using Collectors.toSet() and thinking it counts. It just deduplicates.
WRONG: Forgetting the type parameter on comparingByValue when chaining .reversed().
// WRONG: type inference fails without explicit type
freq.entrySet().stream()
.sorted(Map.Entry.comparingByValue().reversed()); // may not compile
// CORRECT: provide type parameters
freq.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed());
What should a senior engineer know?
groupingBy(identity(), counting())is the idiomatic one-liner for frequency counting in Java streams. Interviewers expect this exact pattern.- The
counting()downstream is more efficient thantoList()followed byList::sizebecause it only tracks alongcounter, not a fullArrayList. - For Integer frequency counting (like character codes),
Collectors.groupingByreturnsMap. UsemapToObj(c -> (char) c)first if you wantMap. - In parallel streams,
groupingByuses a thread-safe merge strategy automatically. For better parallel performance on large datasets, usegroupingByConcurrent.
Visual Overview
["NEFT", "UPI", "NEFT", "RTGS", "UPI", "NEFT"]
│
▼
groupingBy(identity(), counting())
│
▼
Map<String, Long>
┌───────────────┐
│ NEFT → 3 │
│ UPI → 2 │
│ RTGS → 1 │
└───────┬───────┘
│
▼
sorted(comparingByValue().reversed())
│
▼
[NEFT:3, UPI:2, RTGS:1] (frequency ranked)
Key Takeaway
String frequency counting is one of the most common stream interview questions. The answer is always groupingBy(Function.identity(), Collectors.counting()) which produces Map. For sorted keys, pass TreeMap::new as the map factory. For frequency-ranked output, stream the entrySet and sort by value descending.
This single-line collector replaces the manual pattern of iterating with map.merge(key, 1L, Long::sum). For case-insensitive counting, add map(String::toLowerCase) before groupingBy.
At National Bank of India, the operations dashboard shows transaction type volumes: NEFT (3 times), UPI (2 times), RTGS (1 time). The groupingBy collector produces this summary in one pass through the data.
Remember one sentence: groupingBy with identity and counting is the one-liner for frequency maps, and TreeMap gives sorted keys.
---