ConcurrentModificationException in Java: Causes and Fixes
A ConcurrentModificationException comes from a fail-fast iterator whose modCount changed. Four safe ways to remove while iterating, ListIterator and map views.
- Course: Java study plan
- Module: Collections framework
- Kind: Lesson
- Reading time: 11 min
- Runtime: OpenJDK 18
What causes ConcurrentModificationException in Java?
A ConcurrentModificationException in Java is thrown when a collection is structurally modified — an element added or removed — while an iterator over it is active, usually in a single thread: calling list.remove inside a for-each loop is the classic case. Collections such as ArrayList and HashMap count modifications in modCount, and the iterator checks that count on every next().
Lesson
ConcurrentModificationException is the exception most Java developers meet in their first month: they remove an element from a list while looping over it. Understanding why it happens — the fail-fast iterator and its modCount — makes the fix obvious and reveals the four correct ways to modify a collection you are walking. This lesson also covers Iterator/ListIterator directly, and the map views' iteration rules.
Iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.isBlank()) it.remove(); // remove the element last returned by next() — the safe way
}
For-each is sugar over exactly this loop (minus remove). remove() is optional (immutable collections throw UnsupportedOperationException), may be called once per next(), and throws IllegalStateException otherwise. Iterator is single-pass; iterator() gives a fresh one.
ListIterator (lists only) adds bidirectional movement (hasPrevious/previous), positional info (nextIndex), set(e) to replace the last element returned, and add(e) to insert at the cursor — the tool for in-place edits during a walk.
Fail-fast: modCount
ArrayList, HashMap, HashSet and friends keep a modCount incremented on every structural modification (add, remove, clear — anything that changes size; set does not count). An iterator records the count when created and checks it on every next(); a mismatch throws ConcurrentModificationException:
for (String s : list) {
if (s.isBlank()) list.remove(s); // modCount changes; the NEXT next() throws
}
Two facts that surprise:
- The exception comes from the iterator, on the following
next()— so removing the second-to-last element and then ending the loop may not throw. The behaviour is "best-effort": never rely on it not throwing, and never rely on it throwing. - "Concurrent" is misleading: this is single-threaded. Genuine multi-thread modification also triggers it (and worse, since the check is not synchronised); that is Module 17's problem.
Fail-fast exists so that a bug — iterating stale structure — fails loudly instead of skipping or repeating elements silently.
The four safe ways to modify while iterating
removeIf(predicate)— the clearest for removals:list.removeIf(String::isBlank),map.entrySet().removeIf(e -> e.getValue() == 0).- The iterator's own
remove()(orListIterator.set/add) — the iterator updates its expected count. - A backwards index loop on a list:
for (int i = list.size() - 1; i >= 0; i--) if (…) list.remove(i);— removing atidoes not disturb indices below it. - Collect then apply: gather the elements to remove (or add) into a separate collection and modify after the loop — the only option when you need to add while iterating.
For replacements without size change: list.set(i, x) inside an index loop, or replaceAll(fn); these are not structural and do not trip the check.
Iterating maps safely
for (Map.Entry<K, V> e : map.entrySet()) {
e.setValue(newValue); // fine: not structural
map.put(e.getKey(), newValue); // fine for an EXISTING key in HashMap (no structural change) — but do not rely on it
map.put(newKey, v); // ConcurrentModificationException
map.remove(e.getKey()); // ConcurrentModificationException
}
map.entrySet().removeIf(e -> …); // the safe removal
map.keySet().removeIf(k -> …);
map.replaceAll((k, v) -> …); // safe bulk replacement
Fail-safe and weakly consistent iterators
The concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentLinkedQueue) never throw ConcurrentModificationException: their iterators are weakly consistent — they reflect the state at some point during iteration and may or may not show later changes — or, for CopyOnWriteArrayList, iterate a snapshot. This is the right choice for multi-threaded reads with rare writes, and the wrong choice as a way to "fix" a single-threaded modification bug (use removeIf).
Iterable and forEach
Iterable.forEach(Consumer) and Map.forEach(BiConsumer) are the lambda forms of iteration; they carry the same fail-fast behaviour and the same rule: do not structurally modify the collection inside the consumer. Streams over collections likewise must not modify the source (the pipeline's behaviour is then undefined).
Iteration order recap
| Collection | Order |
|---|---|
List, ArrayDeque (as queue) | index / FIFO |
ArrayDeque as stack | top first |
LinkedHashSet/LinkedHashMap | insertion (or access) |
TreeSet/TreeMap | sorted |
EnumSet/EnumMap | declaration order |
HashSet/HashMap | unspecified; changes on resize |
PriorityQueue | heap order — not sorted |
Set.of/Map.of | deliberately randomised per run |
Tests that assert on the iteration order of a hash collection are flaky by construction; use ordered collections or sort before asserting.
Interview angle
- "What causes
ConcurrentModificationException?" Structural modification of a collection while an iterator over it is active, detected viamodCount; usually single-threaded. - "How do you remove elements while iterating?"
removeIf, the iterator'sremove, a backwards index loop, or collect-then-remove. - "Is fail-fast guaranteed?" No — best-effort; it may miss a modification near the end.
- "Which collections have fail-safe iterators?" The concurrent ones (
ConcurrentHashMap,CopyOnWriteArrayList) — weakly consistent or snapshot iterators. - "Does
settrigger it?" No — only structural (size-changing) modifications.
Key takeaways
- For-each is an
Iterator;iterator.remove()is the only removal the iterator tolerates. - Fail-fast iterators check
modCountonnext()— best-effort detection of structural modification, single-threaded included. - Modify safely with
removeIf, the iterator, a backwards index loop, or collect-then-apply;set/replaceAll/entry.setValueare non-structural. - Concurrent collections are weakly consistent instead; hash-collection order is never something to assert on.
Common questions
How do you remove elements from a list while iterating in Java?
Use removeIf — list.removeIf(String::isBlank) — or the iterator's own remove(), or loop over the indices backwards and call remove(i). When you need to add elements, collect the changes in a separate collection and apply them after the loop.
What does fail-fast mean in Java?
A fail-fast iterator throws ConcurrentModificationException as soon as it notices the collection changed structurally underneath it, instead of silently skipping or repeating elements. The check is best-effort: a removal just before the end of a loop may go undetected, so never rely on it either way.
Does list.set cause a ConcurrentModificationException?
No. Only structural modifications — ones that change the size, such as add, remove and clear — increment modCount. set, replaceAll and Map.Entry.setValue replace values in place and are safe inside a loop.
What is the difference between fail-fast and fail-safe iterators?
Fail-fast iterators, as in ArrayList and HashMap, throw on a structural change during iteration. The concurrent collections' iterators never throw: ConcurrentHashMap's are weakly consistent, reflecting the state at some point during iteration, and CopyOnWriteArrayList iterates a snapshot.
What is a ListIterator in Java?
A ListIterator is the list-only iterator that moves both ways, with hasPrevious and previous, reports nextIndex, and can edit during the walk: set(e) replaces the last element returned and add(e) inserts at the cursor.
Exercises
Remove safely, four ways
Read an integer n and n integers. Remove every even number four times from four independent copies of the list, using: (1) removeIf; (2) an explicit Iterator with it.remove(); (3) a backwards index loop; (4) collect-then-removeAll. Print the four resulting lists; then demonstrate the failure: attempt the removal in a for-each loop inside a try, catching ConcurrentModificationException, and print cme=<true|false> (whether it was thrown).
Example: 5 then 1 2 3 4 5 →
[1, 3, 5]
[1, 3, 5]
[1, 3, 5]
[1, 3, 5]
cme=trueEdit while iterating, with a ListIterator
Read n integers into an ArrayList. Walk it once with a ListIterator<Integer>: replace every negative value with 0 (set), insert x / 2 after every even positive value (add), and remove every value greater than 100 (remove). Print the resulting list and size=<n>. This is the one way to change a list while iterating it without a ConcurrentModificationException.
Example: 5 then -3 8 5 200 2 →
[0, 8, 4, 5, 2, 1]
size=6In this module: Collections framework
- The collections framework — the map and how to choose
- Lists — ArrayList, LinkedList and the List API
- Sets — HashSet, LinkedHashSet and TreeSet
- Maps — HashMap internals, TreeMap, LinkedHashMap and the modern API
- Queues, deques and PriorityQueue
- Sorting, Comparable and Comparator in depth
- Iterators, fail-fast behaviour and safe modification (this lesson)
- Immutable collections and the utility toolkit
- Checkpoint — Collections framework
← Sorting, Comparable and Comparator in depth · Immutable collections and the utility toolkit →