Back to Collectors
Q18Question 18 of 21

Collect a stream result into a ConcurrentHashMap using Collectors.toConcurrentMap

Key Concepts
  • toConcurrentMap is a concurrent collector that builds a single shared ConcurrentHashMap
  • In parallel, all threads insert directly into one map, skipping the per-thread-then-merge overhead of toMap
  • Like toMap, duplicate keys throw IllegalStateException unless a merge function is provided
  • toConcurrentMap is an unordered collector: encounter order is not preserved
  • Only beneficial for parallel streams with many elements where avoiding the merge matters
  • On sequential streams, toMap is simpler, just as fast, and preserves encounter order

Definition

`toConcurrentMap` lets a parallel stream collect directly into one thread-safe `ConcurrentHashMap`, avoiding the per-thread-then-merge overhead of `toMap`.
  • 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 IllegalStateException unless a merge function is provided as the third argument.
  • Unordered: encounter order is not preserved.
  • Sequential streams: toMap is simpler and preserves order; toConcurrentMap gains 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?

JAVA
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());
    }
}
TEXT
{SAV2004=Deepak, SAV2001=Rajesh, SAV2003=Meera, SAV2002=Priya}
Type: ConcurrentHashMap

How do you handle duplicate keys?

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

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

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

---

Share
Use ← → arrow keys to navigate