Skip to content

How to Use Java ConcurrentHashMap Correctly

DodaTech Updated 2026-06-24 3 min read

In this tutorial, you'll learn about How to Use Java ConcurrentHashMap Correctly. We cover key concepts, practical examples, and best practices.

The Problem

Your multi-threaded application uses ConcurrentHashMap but still gets ConcurrentModificationException, inconsistent sizes, or hangs on computeIfAbsent. ConcurrentHashMap is thread-safe but common usage patterns can still cause issues.

Quick Fix

Fix 1: ConcurrentModificationException During Iteration

WRONG — modifying the map while iterating:

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");

for (String key : map.keySet()) {
    if (key.equals("key1")) {
        map.remove(key);  // May throw ConcurrentModificationException
    }
}

RIGHT — use Iterator.remove() or forEach:

// Safe way 1: forEach with concurrent modification
map.forEach((key, value) -> {
    if (key.equals("key1")) {
        map.remove(key);  // safe in ConcurrentHashMap's forEach
    }
});

// Safe way 2: use entrySet iterator
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, String> entry = it.next();
    if (entry.getKey().equals("key1")) {
        it.remove();  // safe
    }
}

Fix 2: size() Is Approximate

WRONG — relying on size() for exact counts:

int size = map.size();
if (size > 0) {
    map.clear();  // Between size() and clear(), another thread added an entry
}

RIGHT — use mappingCount() for better accuracy and design for races:

long count = map.mappingCount();  // returns long, more accurate for large maps
// But still approximate — use atomic operations for correctness:
if (map.isEmpty()) {
    // no guarantee the map stays empty
}

Fix 3: computeIfAbsent and computeIfPresent Deadlock

// Deadlock risk in Java 8:
map.computeIfAbsent("key", k -> {
    map.computeIfAbsent("other-key", k2 -> "value");  // May deadlock!
    return "value";
});

RIGHT — avoid nested compute calls:

// Pre-compute the value outside:
String otherValue = map.get("other-key");
if (otherValue == null) {
    otherValue = "value";
}
map.computeIfAbsent("key", k -> {
    // Do not call computeIfAbsent inside
    return otherValue;
});

Fix 4: Iteration Is Not Atomic

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("count", 0);

// WRONG — read-then-write race:
int current = map.get("count");
map.put("count", current + 1);  // Another thread may have updated between get and put

RIGHT — use atomic operations:

// Use compute for atomic read-modify-write:
map.compute("count", (key, value) -> (value == null) ? 1 : value + 1);

// Or use merge:
map.merge("count", 1, Integer::sum);

Fix 5: Null Values Not Allowed

map.put("key", null);  // Throws NullPointerException!

RIGHT — use an alternative or check for null:

if (someValue != null) {
    map.put("key", someValue);
}

Fix 6: Bulk Operations Are Not Atomic

// putAll is not atomic relative to other operations
map.putAll(otherMap);
// Between putAll calls, another thread may see partial state

RIGHT — use a separate lock for compound operations:

synchronized (map) {
    map.clear();
    map.putAll(newData);
}

Use DodaTech's Concurrency Debugger to visualize thread interactions with ConcurrentHashMap and detect race conditions in your application.

Prevention

  • Use compute, merge, or forEach for atomic operations.
  • Avoid nested computeIfAbsent calls.
  • Do not rely on size() for exact counts.
  • Use <a href="/design-patterns/iterator/">Iterator</a>.remove() during iteration.
  • Design for concurrency — assume the map changes between operations.

Common Mistakes with concurrent hashmap

  1. Forgetting deriving (Show, Eq) on custom data types needed for debugging
  2. Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable
  3. Using head and tail instead of pattern matching, causing runtime errors on empty lists

These mistakes appear frequently in real-world JAVA code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### How does ConcurrentHashMap achieve thread safety?

ConcurrentHashMap uses fine-grained locking (striped locks) and CAS operations instead of synchronizing the entire map. Reads are generally lock-free. Writes only lock specific segments or bins, allowing high concurrency.

What is the difference between Hashtable and ConcurrentHashMap?

Hashtable synchronizes all methods (including reads), causing contention. ConcurrentHashMap allows concurrent reads and only locks specific segments for writes. ConcurrentHashMap is always preferred over Hashtable.

Why does ConcurrentHashMap not allow null keys or values?

Null values would make methods like get() ambiguous — returning null could mean the key is absent or mapped to null. ConcurrentHashMap's authors designed it to avoid this ambiguity, consistent with the Map contract for concurrent maps.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro