Back to Blog
Technical QA
February 6, 2026
11 min read
2,149 words

ConcurrentModificationException while iterating a HashMap — what is the safe fix?

Why This Matters In the development of a concurrent multi-threaded application in Java, you might encounter a ConcurrentModificationException while iterating over a HashMap. This a…

ConcurrentModificationException while iterating a HashMap — what is the safe fix?

Why This Matters

In the development of a concurrent multi-threaded application in Java, you might encounter a ConcurrentModificationException while iterating over a HashMap. This article aims to provide an extensive explanation of the issue and propose safe fixes for this problem, along with discussing failure modes, edge cases, verification steps, interview follow-ups, related checks, and locking strategies.

Short Answer

To avoid throwing a ConcurrentModificationException, use either the Collections.synchronizedMap(HashMap) method or the ConcurrentHashMap class when thread-safe access and modification of your map are required.

Deep Answer

What is happening?

When iterating over a HashMap in Java, an exception is thrown if any other thread modifies the map during the iteration process. This is because HashMap is not thread-safe by default, meaning it can only be accessed or modified by one thread at a time.

Failure Mode 1: Iteration and Concurrent Modification

When multiple threads try to access and modify the same HashMap concurrently, race conditions may occur, leading to inconsistent data and ConcurrentModificationException.

Failure Mode 2: Iterator Invalidation

The iterator maintains an internal pointer to the current entry in the map. If another thread modifies the map, the iterator's state becomes invalid, leading to the exception being thrown.

Why does this happen?

The ConcurrentModificationException is designed to prevent race conditions and data inconsistencies that may occur when multiple threads access and modify shared resources simultaneously. In the case of iterating over a HashMap, the iterator maintains an internal pointer to the current entry in the map. If another thread modifies the map, the iterator's state becomes invalid, leading to the exception being thrown.

When it breaks

The ConcurrentModificationException can occur whenever there are multiple threads accessing and modifying a non-thread-safe HashMap concurrently. This issue is particularly common in multi-threaded applications where multiple threads may need to read or write to the same data structure simultaneously.

Edge Case 1: High Concurrency Scenarios

In high concurrency scenarios, the performance of synchronizedMap can suffer due to contention on the lock. In such cases, using a ConcurrentHashMap might be more appropriate.

How to verify

To verify that your fix has resolved the ConcurrentModificationException, you can try running your application with multiple threads and iterating over the HashMap concurrently while modifying it. If no exceptions are thrown, your solution is working as intended.

Verification Steps

  1. Create a multi-threaded application that iterates over a HashMap and modifies it concurrently.
  2. Introduce a ConcurrentModificationException by having multiple threads modify the HashMap while one thread is iterating over it.
  3. Implement the fix using either Collections.synchronizedMap(HashMap) or ConcurrentHashMap.
  4. Repeat steps 1-3 and verify that no exceptions are thrown when running the application with the fixed code.

Pitfalls And Edge Cases

When using synchronizedMap or ConcurrentHashMap, be aware of potential performance implications. SynchronizedMap uses synchronization internally, which can lead to contention and slower performance in highly concurrent scenarios. On the other hand, ConcurrentHashMap offers better performance due to its optimized locking mechanism but may have slightly more complex API compared to HashMap.

Edge Case 2: Complex API of ConcurrentHashMap

ConcurrentHashMap's API is slightly more complex than that of HashMap, which might take some time for developers to get accustomed to. However, the improved performance and thread-safety make it a valuable tool in multi-threaded applications.

Related Checks

After implementing thread-safe access and modification of your map, make sure that any other shared resources are also properly synchronized to avoid race conditions and data inconsistencies. Additionally, consider using locks or synchronization utilities provided by Java to manage concurrent access to critical sections of your code.

Locking Strategies

Java provides several locking strategies to ensure thread-safety in multi-threaded applications:

  1. Synchronized Blocks: Use the synchronized keyword to create a synchronized block around critical sections of your code. This ensures that only one thread can access the section at a time, preventing race conditions.
public void criticalSection() {
// Critical section goes here
synchronized(this) {
// Access to shared resources goes here
}
}
  1. ReentrantLock: The ReentrantLock class offers more flexible locking mechanisms, such as fairness and interruption support, compared to the synchronized keyword. It can be particularly useful in scenarios where multiple threads may need to acquire the same lock repeatedly.
private final ReentrantLock lock = new ReentrantLock();

public void criticalSection() {
lock.lock();
try {
// Critical section goes here
// Access to shared resources goes here
} finally {
lock.unlock();
}
}
  1. Atomic Variables: Atomic variables are special classes that provide thread-safe access and modification of primitive types without the need for explicit synchronization. They can be particularly useful in scenarios where simple atomic operations are required, such as incrementing a counter or updating a flag.

Example with AtomicInteger

private final AtomicInteger counter = new AtomicInteger(0);

public void incrementCounter() {
counter.incrementAndGet();
}

By understanding and utilizing these locking strategies, you can ensure that your multi-threaded application remains thread-safe and free of ConcurrentModificationException.

Interview Follow-ups

During an interview, you may be asked to:

  1. Explain the differences between HashMap, synchronizedMap, and ConcurrentHashMap in terms of thread safety and performance.
  2. Provide examples of high concurrency scenarios where using a ConcurrentHashMap would be advantageous over synchronizedMap.
  3. Discuss potential edge cases when using ReentrantLock or AtomicVariables, and how to handle them effectively.
  4. Compare and contrast the use of locks and synchronization utilities in multi-threaded applications.
  5. Explain how to determine if a shared resource requires thread-safe access and modification, and provide strategies for managing such resources in your code.

Safe Fixes

Using Collections.synchronizedMap(HashMap)

Map<String, Integer> map = new HashMap<>();
SynchronizedMap<String, Integer> synchronizedMap = Collections.synchronizedMap(map);
// Use synchronizedMap instead of map to ensure thread-safety

Using ConcurrentHashMap

Map<String, Integer> map = new ConcurrentHashMap<>();
// Use map directly as it is already thread-safe

Performance Considerations

When choosing between synchronizedMap and ConcurrentHashMap, consider the following factors:

  1. Concurrency Level: If your application has a high concurrency level, use ConcurrentHashMap for better performance due to its optimized locking mechanism.
  2. Complexity of API: If you are more comfortable with the simpler API of HashMap, use synchronizedMap as it provides thread-safety while maintaining compatibility with HashMap's API.
  3. Lock Contention: In scenarios where there is significant contention on the lock, consider using finer-grained locking strategies like ReentrantLock or AtomicVariables to minimize lock contention and improve performance.

Pitfalls And Edge Cases

When using synchronizedMap or ConcurrentHashMap, be aware of potential performance implications. SynchronizedMap uses synchronization internally, which can lead to contention and slower performance in highly concurrent scenarios. On the other hand, ConcurrentHashMap offers better performance due to its optimized locking mechanism but may have slightly more complex API compared to HashMap.

Edge Case 1: Iterator Incompatibility with ConcurrentHashMap

ConcurrentHashMap's iterator does not support removal of elements while iterating. To remove an element, you should first obtain a reference to the entry and then remove it from the map.

Map<String, Integer> map = new ConcurrentHashMap<>();
// Add entries to map

for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (someCondition) {
map.remove(entry.getKey()); // Removes the entry from the map
}
}

Edge Case 2: ConcurrentModificationException with Iterator's forEachRemaining() method

When using the forEachRemaining() method on an iterator of a ConcurrentHashMap, be aware that it may still throw a ConcurrentModificationException. To avoid this exception, use the removeIf() method instead.

Map<String, Integer> map = new ConcurrentHashMap<>();
// Add entries to map

map.forEach((k, v) -> {
if (someCondition) {
map.remove(k); // Removes the entry from the map without throwing a ConcurrentModificationException
}
});

Related Checks

After implementing thread-safe access and modification of your map, make sure that any other shared resources are also properly synchronized to avoid race conditions and data inconsistencies. Additionally, consider using locks or synchronization utilities provided by Java to manage concurrent access to critical sections of your code.

Locking Strategies

Java provides several locking strategies to ensure thread-safety in multi-threaded applications:

  1. Synchronized Blocks: Use the synchronized keyword to create a synchronized block around critical sections of your code. This ensures that only one thread can access the section at a time, preventing race conditions.
public void criticalSection() {
// Critical section goes here
synchronized(this) {
// Access to shared resources goes here
}
}
  1. ReentrantLock: The ReentrantLock class offers more flexible locking mechanisms, such as fairness and interruption support, compared to the synchronized keyword. It can be particularly useful in scenarios where multiple threads may need to acquire the same lock repeatedly.
private final ReentrantLock lock = new ReentrantLock();

public void criticalSection() {
lock.lock();
try {
// Critical section goes here
// Access to shared resources goes here
} finally {
lock.unlock();
}
}
  1. Atomic Variables: Atomic variables are special classes that provide thread-safe access and modification of primitive types without the need for explicit synchronization. They can be particularly useful in scenarios where simple atomic operations are required, such as incrementing a counter or updating a flag.

Example with AtomicInteger

private final AtomicInteger counter = new AtomicInteger(0);

public void incrementCounter() {
counter.incrementAndGet();
}

By understanding and utilizing these locking strategies, you can ensure that your multi-threaded application remains thread-safe and free of ConcurrentModificationException.

Interview Follow-ups

During an interview, you may be asked to:

  1. Explain the differences between HashMap, synchronizedMap, and ConcurrentHashMap in terms of thread safety and performance.
  2. Provide examples of high concurrency scenarios where using a ConcurrentHashMap would be advantageous over synchronizedMap.
  3. Discuss potential edge cases when using ReentrantLock or AtomicVariables, and how to handle them effectively.
  4. Compare and contrast the use of locks and synchronization utilities in multi-threaded applications.
  5. Explain how to determine if a shared resource requires thread-safe access and modification, and provide strategies for managing such resources in your code.

Safe Fixes

Using Collections.synchronizedMap(HashMap)

Map<String, Integer> map = new HashMap<>();
SynchronizedMap<String, Integer> synchronizedMap = Collections.synchronizedMap(map);
// Use synchronizedMap instead of map to ensure thread-safety

Using ConcurrentHashMap

Map<String, Integer> map = new ConcurrentHashMap<>();
// Use map directly as it is already thread-safe

Performance Considerations

When choosing between synchronizedMap and ConcurrentHashMap, consider the following factors:

  1. Concurrency Level: If your application has a high concurrency level, use ConcurrentHashMap for better performance due to its optimized locking mechanism.
  2. Complexity of API: If you are more comfortable with the simpler API of HashMap, use synchronizedMap as it provides thread-safety while maintaining compatibility with HashMap's API.
  3. Lock Contention: In scenarios where there is significant contention on the lock, consider using finer-grained locking strategies like ReentrantLock or AtomicVariables to minimize lock contention and improve performance.

Pitfalls And Edge Cases

When using synchronizedMap or ConcurrentHashMap, be aware of potential performance implications. SynchronizedMap uses synchronization internally, which can lead to contention and slower performance in highly concurrent scenarios. On the other hand, ConcurrentHashMap offers better performance due to its optimized locking mechanism but may have slightly more complex API compared to HashMap.

Edge Case 1: Iterator Incompatibility with ConcurrentHashMap

ConcurrentHashMap's iterator does not support removal of elements while iterating. To remove an element, you should first obtain a reference to the entry and then remove it from the map.

Map<String, Integer> map = new ConcurrentHashMap<>();
// Add entries to map

for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (someCondition) {
map.remove(entry.getKey()); // Removes the entry from the map
}
}

Edge Case 2: ConcurrentModificationException with Iterator's forEachRemaining() method

When using the forEachRemaining() method on an iterator of a ConcurrentHashMap, be aware that it may still throw a ConcurrentModificationException. To avoid this exception, use the removeIf() method instead.

Map<String, Integer> map = new ConcurrentHashMap<>();
// Add entries to map

map.forEach((k, v) -> {
if (someCondition) {
map.remove(k); // Removes the entry from the map without throwing a ConcurrentModificationException
}
});

Related Checks

After implementing thread-safe access and modification of your map, make sure that any other shared resources are also properly synchronized to avoid race conditions and data inconsistencies. Additionally, consider using locks or synchronization utilities provided by Java to manage concurrent access to critical sections of your code.

Locking Strategies

Java provides several locking strategies to ensure thread-safety in multi-threaded applications:

  1. Synchronized Blocks: Use the synchronized keyword to create a synchronized block around critical sections of your code. This ensures that only one thread can access the section at a time, preventing race conditions.
public void criticalSection() {
// Critical section goes here
synchronized(this) {
// Access to shared resources goes here
}
}
  1. ReentrantLock: The ReentrantLock class offers more flexible locking mechanisms, such as fairness and interruption support, compared to the synchronized keyword. It can be particularly useful in scenarios where multiple threads may need to acquire the same lock repeatedly.
private final ReentrantLock lock = new ReentrantLock();

public void criticalSection() {
lock.lock();
try {
// Critical section goes here
// Access to shared resources goes here
} finally {
lock.unlock();
}
}
  1. Atomic Variables: Atomic variables are special classes that provide thread-safe access and modification of primitive types without the need for explicit synchronization. They can be particularly useful in scenarios where simple atomic operations are required, such as incrementing a counter or updating a flag.

Example with AtomicInteger

private final AtomicInteger counter = new AtomicInteger(0);

public void incrementCounter() {
counter.incrementAndGet();
}

By understanding and utilizing these locking strategies, you can ensure that your multi-threaded application remains thread-safe and free of ConcurrentModificationException.

Tags:Technical QATutorialGuide
X

Written by XQA Team

Our team of experts delivers insights on technology, business, and design. We are dedicated to helping you build better products and scale your business.