Definition
- Two-argument toMap throws
IllegalStateException: Duplicate keyon key collision. - Deliberate fail-fast: surfaces a data integrity problem rather than silently dropping data.
- Three-argument toMap adds a merge function:
(existing, replacement) -> .... - Merge policies: keep first, keep latest, or apply a domain-specific rule.
- When IDs should be unique, letting it throw is correct behavior.
At National Bank of India, if two transaction forms claim the same slot number, the filing system refuses and raises an alarm until Priya gives a rule for which one wins.
Understanding the Concepts
What happens with duplicate keys?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ToMapDuplicateKey {
record Employee(String id, String name, BigDecimal salary) {}
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("SAV2001", "Rajesh", new BigDecimal("1800000")),
new Employee("SAV2001", "Priya", new BigDecimal("1200000")) // duplicate id!
);
try {
Map<String, String> map = staff.stream()
.collect(Collectors.toMap(Employee::id, Employee::name));
} catch (IllegalStateException e) {
System.out.println("Exception: " + e.getMessage());
}
// Fix: provide a merge function
Map<String, String> fixed = staff.stream()
.collect(Collectors.toMap(
Employee::id,
Employee::name,
(existing, replacement) -> existing)); // keep first
System.out.println("Fixed: " + fixed);
}
}
Exception: Duplicate key SAV2001 (attempted merging value Priya)
Fixed: {SAV2001=Rajesh}
Why is this a feature, not a bug?
When IDs are supposed to be unique, failing fast on a duplicate surfaces a data integrity problem rather than silently dropping a record. This is deliberate: it alerts you to a problem upstream.
What merge policies are common?
// Keep first occurrence
(existing, replacement) -> existing
// Keep latest (last wins)
(existing, replacement) -> replacement
// Keep the one with higher salary
(existing, replacement) -> existing.salary().compareTo(replacement.salary()) >= 0
? existing : replacement
// Concatenate (for string values)
(existing, replacement) -> existing + ", " + replacement
How is toMap different from groupingBy for duplicates?
| Collector | Duplicate key behavior | Result type |
|---|---|---|
| toMap(key, value) | Throws IllegalStateException | Map |
| toMap(key, value, merge) | Merge function resolves | Map |
| groupingBy(key) | Collects all into a list per key | Map |
What are common mistakes?
Mistake: Not providing a merge function when duplicates are possible.
// WRONG: throws on duplicate
staff.stream().collect(Collectors.toMap(Employee::id, Employee::name));
// CORRECT: provide a merge function
staff.stream().collect(Collectors.toMap(
Employee::id, Employee::name, (a, b) -> a));
What should a senior engineer know?
Treat the exception as a feature: when IDs should be unique, failing fast is exactly right. Only add a merge function when duplicates are legitimately expected and you have a clear business policy for resolving them.
What are common interview follow-up questions?
- "What exception is thrown?"
IllegalStateExceptionwith 'Duplicate key'. - "How do you fix it?" Add a merge function as the third argument.
- "When should you let it throw?" When duplicates indicate a data integrity problem.
- "What if you want all values per key?" Use
groupingByinstead.
Visual Overview
Two-arg toMap:
┌──────────────────────────────────────┐
│ SAV2001 → Rajesh ← inserted │
│ SAV2001 → Priya ← COLLISION! │
│ → IllegalStateException thrown │
└──────────────────────────────────────┘
Three-arg toMap with merge:
┌──────────────────────────────────────┐
│ SAV2001 → Rajesh ← inserted │
│ SAV2001 → Priya ← merge: │
│ (Rajesh, Priya) → Rajesh (keep) │
│ Result: {SAV2001=Rajesh} │
└──────────────────────────────────────┘
Key Takeaway
With the two-argument toMap, two employees sharing an ID cause an IllegalStateException with 'Duplicate key', because the collector has no instruction for which value to keep. This is a deliberate fail-fast that surfaces data integrity problems.
At the National Bank of India, an ID-to-name index threw exactly this when a feed contained a duplicate ID. The fix is the three-argument toMap where the merge function decides which value to keep: first, latest, or a domain-specific rule.
The senior insight is to treat the exception as a feature, not a nuisance. When IDs should be unique, letting it throw is the correct behavior. Only add a merge function when duplicates are legitimately expected.
Remember one sentence: two-argument toMap throws IllegalStateException on duplicate keys; add a merge function as the third argument to resolve collisions.
---