Definition
- toConcurrentMap(keyMapper, valueMapper) is a concurrent collector building a
ConcurrentHashMap. - Parallel advantage: all threads insert into one shared map, skipping the per-thread map creation and merge step.
- Duplicate key throws
IllegalStateExceptionunless a merge function is provided as the third argument. - Unordered: encounter order is not preserved.
- Sequential streams:
toMapis simpler and preserves order;toConcurrentMapgains nothing.
At the National Bank of India, many clerks file employee records directly into one shared, thread-safe filing cabinet at the same time, instead of each keeping a separate cabinet that must be combined later.
Understanding the Concepts
What is toConcurrentMap?
Collectors.toConcurrentMap(keyMapper, valueMapper) is a concurrent collector that produces a ConcurrentHashMap. Unlike the regular toMap, which in parallel creates per-thread intermediate maps and merges them, toConcurrentMap has all threads insert directly into a single shared ConcurrentHashMap. This avoids the merge step and can be faster for large parallel collections.
How do you use it?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
public class ConcurrentMapCollector {
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("SAV2002", "Priya", new BigDecimal("1200000")),
new Employee("SAV2003", "Meera", new BigDecimal("1500000")),
new Employee("SAV2004", "Deepak", new BigDecimal("900000"))
);
ConcurrentMap<String, String> idToName = staff.parallelStream()
.collect(Collectors.toConcurrentMap(
Employee::id,
Employee::name));
System.out.println(idToName);
System.out.println("Type: " + idToName.getClass().getSimpleName());
}
}
{SAV2004=Deepak, SAV2001=Rajesh, SAV2003=Meera, SAV2002=Priya}
Type: ConcurrentHashMap
How do you handle duplicate keys?
// With duplicate keys, two-arg form throws
List<Employee> withDuplicateId = List.of(
new Employee("SAV2001", "Rajesh", new BigDecimal("1800000")),
new Employee("SAV2001", "Priya", new BigDecimal("1200000")) // same ID
);
// WRONG: throws IllegalStateException
// withDuplicateId.parallelStream()
// .collect(Collectors.toConcurrentMap(Employee::id, Employee::name));
// CORRECT: provide a merge function
ConcurrentMap<String, String> merged = withDuplicateId.parallelStream()
.collect(Collectors.toConcurrentMap(
Employee::id,
Employee::name,
(existing, replacement) -> existing)); // keep first
How is toConcurrentMap different from toMap?
| Aspect | toMap | toConcurrentMap |
|---|---|---|
| Parallel strategy | Per-thread maps, then merge | Single shared ConcurrentHashMap |
| Encounter order | Preserved (with LinkedHashMap supplier) | Not preserved |
| Parallel overhead | Merge step at the end | No merge, but CAS contention |
| Best for | Sequential or ordered parallel | Large unordered parallel |
| Result type | HashMap (default) | ConcurrentHashMap |
What are common mistakes?
Mistake: Using toConcurrentMap on a sequential stream expecting a benefit.
// WRONG: no benefit on sequential, loses ordering
ConcurrentMap<String, String> seq = staff.stream() // sequential!
.collect(Collectors.toConcurrentMap(Employee::id, Employee::name));
// Same speed as toMap, but loses encounter order
// CORRECT: use toMap for sequential streams
var map = staff.stream()
.collect(Collectors.toMap(Employee::id, Employee::name));
What about thread safety of the result?
The resulting ConcurrentHashMap is itself thread-safe for later concurrent reads and writes. This makes it useful when the map will be accessed by multiple threads after construction.
When should you use toConcurrentMap?
- Parallel stream with many elements where avoiding the merge matters.
- Result will be shared across threads after construction.
- Order does not matter (unordered collector).
What should a senior engineer know?
toConcurrentMap trades ordering for throughput. It only pays off for genuinely parallel streams with many elements, where the merge step of toMap is a bottleneck. On sequential streams it gains nothing and loses ordering. A senior engineer uses toMap by default and switches to toConcurrentMap only after measuring.
What are common interview follow-up questions?
- "When does toConcurrentMap outperform toMap?" In parallel with many elements, where the merge is costly.
- "Does it preserve order?" No, it is an unordered collector.
- "What happens with duplicate keys?" Same as toMap: throws unless a merge function is provided.
- "Is the result thread-safe?" Yes, it is a ConcurrentHashMap.
Visual Overview
toMap (parallel): toConcurrentMap (parallel): ┌────────────────────┐ ┌────────────────────────────┐ │ Thread-1: local map│ │ Thread-1 → shared CHM │ │ Thread-2: local map│ │ Thread-2 → shared CHM │ │ Thread-3: local map│ │ Thread-3 → shared CHM │ ├────────────────────┤ │ │ │ Merge all maps │ │ No merge needed │ └────────────────────┘ └────────────────────────────┘
Key Takeaway
Collectors.toConcurrentMap builds a ConcurrentHashMap where, in parallel, every thread inserts directly into one shared map instead of creating per-thread maps and merging. This skips the merge step and can be faster for large parallel collections.
At the National Bank of India, Rajesh uses toConcurrentMap when a parallel batch builds an ID-to-name index over millions of employee records. The resulting ConcurrentHashMap is also thread-safe for later concurrent access.
The trade-off is that toConcurrentMap is an unordered collector: encounter order is not preserved. On a sequential stream it gains nothing and loses ordering, so there Rajesh uses the regular toMap. The rule is: default to toMap, and switch to toConcurrentMap only for large parallel workloads where ordering does not matter.
Remember one sentence: toConcurrentMap skips the parallel merge by inserting directly into a shared ConcurrentHashMap, but only benefits large parallel streams and does not preserve order.
---