Fix: Java ConcurrentModificationException
Quick Answer
How to fix Java ConcurrentModificationException caused by modifying a collection while iterating, HashMap concurrent access, stream operations, and multi-threaded collection usage.
The Error
You run a Java program and get:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at com.example.Main.process(Main.java:15)Or with HashMap:
java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1597)Despite the name, this exception does not require multiple threads. It occurs when you modify a collection (add, remove, or replace elements) while iterating over it with an iterator or enhanced for-loop.
Why This Happens
Java’s iterators are fail-fast. They track a modification count on the underlying collection. Each time the collection is structurally modified (elements added or removed), the count increments. When the iterator’s next() method detects that the count changed since iteration started, it throws ConcurrentModificationException.
This is a safety mechanism. Without it, the iterator might skip elements, visit elements twice, or throw an IndexOutOfBoundsException.
Common causes:
- Removing elements in a for-each loop. The most common cause by far.
- Adding elements during iteration. Inserting into a list or map while looping over it.
- Multiple threads modifying a non-thread-safe collection. Two threads accessing the same
ArrayListorHashMap. - Stream operations modifying the source. Using
.forEach()on a stream and modifying the original collection. - Nested iteration with modification. Modifying the outer collection from the inner loop.
Fix 1: Use Iterator.remove()
The iterator’s own remove() method safely removes the current element without breaking iteration:
Broken:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
for (String name : names) {
if (name.startsWith("B")) {
names.remove(name); // ConcurrentModificationException!
}
}Fixed:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
if (name.startsWith("B")) {
it.remove(); // Safe — removes via the iterator
}
}Iterator.remove() updates the modification count internally, so the fail-fast check does not trigger.
Note: Iterator.remove() only removes the last element returned by next(). You cannot call it twice in a row without calling next() in between.
Pro Tip: Use
Iterator.remove()only when you need to remove elements. If you need to add elements during iteration, useListIterator(Fix 2) or collect additions in a separate list and add them after the loop.
Fix 2: Use ListIterator for Add and Set
ListIterator extends Iterator with add() and set() methods:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
ListIterator<String> it = names.listIterator();
while (it.hasNext()) {
String name = it.next();
if (name.equals("Bob")) {
it.set("Robert"); // Replace current element
it.add("Bobby"); // Insert after current element
}
}
// [Alice, Robert, Bobby, Charlie]ListIterator works only with List implementations, not with Set or Map.
Fix 3: Use removeIf() (Java 8+)
The cleanest way to remove elements by condition:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
names.removeIf(name -> name.startsWith("B"));
// [Alice, Charlie]This works on any Collection (lists, sets, queues). It handles the iterator management internally.
For maps, use entrySet().removeIf():
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90);
scores.put("Bob", 45);
scores.put("Charlie", 70);
scores.entrySet().removeIf(entry -> entry.getValue() < 50);
// {Alice=90, Charlie=70}Fix 4: Collect and Modify After Iteration
Iterate first, collect modifications, then apply them:
For removals:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
List<String> toRemove = new ArrayList<>();
for (String name : names) {
if (name.startsWith("B")) {
toRemove.add(name);
}
}
names.removeAll(toRemove);For additions:
List<String> names = new ArrayList<>(List.of("Alice", "Bob"));
List<String> toAdd = new ArrayList<>();
for (String name : names) {
if (name.equals("Alice")) {
toAdd.add("Alice Jr.");
}
}
names.addAll(toAdd);This pattern is always safe because the modification happens after iteration completes.
Fix 5: Use Streams to Filter
Create a new collection from the filtered stream:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
List<String> filtered = names.stream()
.filter(name -> !name.startsWith("B"))
.collect(Collectors.toList());
// [Alice, Charlie]Warning: Do not modify the source collection inside a stream operation:
// WRONG — throws ConcurrentModificationException:
names.stream().forEach(name -> {
if (name.startsWith("B")) {
names.remove(name); // Modifying source during stream!
}
});Streams should be side-effect-free. Use filter() and collect() instead of modifying the source.
Common Mistake: Using
stream().forEach()to modify the collection the stream is based on. Streams are designed for transformation, not mutation. If you need to modify in place, useremoveIf()orIterator.remove().
Fix 6: Use CopyOnWriteArrayList
For concurrent access from multiple threads, use CopyOnWriteArrayList:
import java.util.concurrent.CopyOnWriteArrayList;
List<String> names = new CopyOnWriteArrayList<>(List.of("Alice", "Bob", "Charlie"));
// Safe — even with multiple threads
for (String name : names) {
if (name.startsWith("B")) {
names.remove(name); // No exception!
}
}CopyOnWriteArrayList creates a new copy of the array on every write operation. Iterators work on a snapshot and never throw ConcurrentModificationException.
Trade-off: Write operations (add, remove, set) are expensive because they copy the entire array. Use this only when reads vastly outnumber writes.
Fix 7: Use ConcurrentHashMap
For thread-safe map operations:
import java.util.concurrent.ConcurrentHashMap;
Map<String, Integer> scores = new ConcurrentHashMap<>();
scores.put("Alice", 90);
scores.put("Bob", 45);
scores.put("Charlie", 70);
// Safe iteration with modification:
scores.forEach((name, score) -> {
if (score < 50) {
scores.remove(name);
}
});ConcurrentHashMap allows concurrent read and write operations without throwing ConcurrentModificationException. Its iterators are weakly consistent — they reflect some but not necessarily all modifications made after the iterator was created.
For HashMap vs ConcurrentHashMap:
| Feature | HashMap | ConcurrentHashMap |
|---|---|---|
| Thread-safe | No | Yes |
| Null keys/values | Yes | No |
| Fail-fast iteration | Yes | No (weakly consistent) |
| Performance (single-thread) | Faster | Slightly slower |
If you need thread-safe operations and null values, use Collections.synchronizedMap() with explicit synchronization during iteration.
Fix 8: Iterate Over a Copy
If you cannot use the above approaches, iterate over a copy of the collection:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
// Iterate over a copy, modify the original:
for (String name : new ArrayList<>(names)) {
if (name.startsWith("B")) {
names.remove(name);
}
}new ArrayList<>(names) creates a shallow copy. The loop iterates over the copy while modifications happen on the original.
For maps:
Map<String, Integer> scores = new HashMap<>();
// ... populate ...
for (Map.Entry<String, Integer> entry : new HashMap<>(scores).entrySet()) {
if (entry.getValue() < 50) {
scores.remove(entry.getKey());
}
}This works but creates extra objects. Prefer removeIf() or Iterator.remove() when possible.
Fix 9: Fix Multi-Threaded Access
If the error occurs across threads, synchronize access to the collection:
List<String> names = Collections.synchronizedList(new ArrayList<>());
// All single operations are thread-safe:
names.add("Alice");
names.remove("Bob");
// BUT iteration must be manually synchronized:
synchronized (names) {
for (String name : names) {
System.out.println(name);
}
}Collections.synchronizedList() synchronizes individual operations but not iteration. You must wrap the entire iteration in a synchronized block.
For better performance, use CopyOnWriteArrayList (read-heavy) or ConcurrentLinkedQueue (write-heavy).
If multi-threading causes OutOfMemoryError from too many threads, see Fix: Java OutOfMemoryError.
Still Not Working?
If you have checked all the fixes above:
Check for indirect modifications. A method called inside the loop might modify the collection without you realizing it. Trace the full call chain.
Check for subList modifications. List.subList() returns a view of the original list. Modifying the original list invalidates the sublist:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
List<String> sub = names.subList(0, 2); // [Alice, Bob]
names.add("Dave"); // Invalidates sub
sub.get(0); // ConcurrentModificationException!Check for singleton or empty collections. Collections.singletonList() and Collections.emptyList() return immutable collections. Modifying them throws UnsupportedOperationException, not ConcurrentModificationException. If you see a different exception, see Fix: Java ClassNotFoundException for classpath issues that might cause unexpected collection types.
Check for Kotlin interop. If Java code receives a Kotlin collection, it might be immutable. Wrap it in a mutable copy: new ArrayList<>(kotlinList).
Use the debugger. Set a breakpoint on ConcurrentModificationException (in IntelliJ: Run → View Breakpoints → Add Exception Breakpoint). This stops execution at the exact point the modification count mismatch is detected.
For similar issues in Python where modifying a list during iteration causes problems, see Fix: Python IndexError: list index out of range.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Java ClassCastException: class X cannot be cast to class Y
How to fix Java ClassCastException by using instanceof checks, fixing generic type erasure, resolving ClassLoader conflicts, correcting raw types, and using pattern matching in Java 16+.
Fix: Java NoSuchMethodError
How to fix Java NoSuchMethodError caused by classpath conflicts, incompatible library versions, wrong dependency scope, shaded JARs, and compile vs runtime version mismatches.
Fix: Java java.lang.IllegalArgumentException
How to fix Java IllegalArgumentException caused by null arguments, invalid enum values, negative numbers, wrong format strings, and Spring/Hibernate validation failures.
Fix: Java java.lang.NullPointerException
How to fix Java NullPointerException by reading stack traces, adding null checks, using Optional, fixing uninitialized variables, avoiding null returns, handling auto-unboxing, and using static analysis annotations.