Skip to content

Fix: Java ConcurrentModificationException

FixDevs ·

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 ArrayList or HashMap.
  • 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, use ListIterator (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, use removeIf() or Iterator.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:

FeatureHashMapConcurrentHashMap
Thread-safeNoYes
Null keys/valuesYesNo
Fail-fast iterationYesNo (weakly consistent)
Performance (single-thread)FasterSlightly 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles