Unravel โบ Java concepts โบ Collections and streams
ConcurrentModificationException
This exception means a collection was changed while a loop was going through it.
Learn these first
At a glance
Counting the books on a shelf while someone keeps adding and removing books: you lose your place, and a careful librarian stops you with "the shelf changed".
- 1This throws: for (String s : names) if (s.isEmpty()) names.remove(s);The loop's iterator notices the list changed behind its back.
- 2Three safe ways: names.removeIf(String::isEmpty); Iterator with it.remove(); or loop over new ArrayList<>(names) and modify the original
- 3For maps, map.entrySet().removeIf(e -> e.getValue() == 0) or map.values().removeIf(...)
- 4It also happens with threads: one thread loops while another addsThat needs a concurrent collection or locking, not just a try-catch.
- Sometimes the code works by accident: removing the second-to-last element of a list does not throw because hasNext() is already false
- The exception is thrown by next(), not by the line that made the change, so the stack trace points at the loop; look for the earlier modification, possibly in a method the loop calls
- Each structural change (add, remove, clear) increments the collection's modCount
import java.util.*;public class Cleanup { static void dropEmpty(List<String> names) { names.removeIf(String::isEmpty); } static void dropZero(Map<String, Integer> m) { m.entrySet().removeIf(e -> e.getValue() == 0); } static boolean broken(List<String> names) { try { for (String s : names) if (s.isEmpty()) names.remove(s); return true; } catch (ConcurrentModificationException e) { return false; } }}- line 3 uses removeIf, the core of ConcurrentModificationException
- line 4 uses removeIf, the core of ConcurrentModificationException
- line 7 uses ConcurrentModificationException, the core of ConcurrentModificationException
Read the full explanation (about 1 min)
This exception means a collection was changed while a loop was going through it. The fix is to remove through the Iterator, use removeIf, or loop over a copy.
A mental model
Counting the books on a shelf while someone keeps adding and removing books: you lose your place, and a careful librarian stops you with "the shelf changed".
How it works
This throws: for (String s : names) if (s.isEmpty()) names.remove(s);. The loop's iterator notices the list changed behind its back.
Three safe ways: names.removeIf(String::isEmpty); Iterator with it.remove(); or loop over new ArrayList<>(names) and modify the original.
For maps, map.entrySet().removeIf(e -> e.getValue() == 0) or map.values().removeIf(...).
It also happens with threads: one thread loops while another adds. That needs a concurrent collection or locking, not just a try-catch.
Going deeper
Sometimes the code works by accident: removing the second-to-last element of a list does not throw because hasNext() is already false. Do not rely on it.
The exception is thrown by next(), not by the line that made the change, so the stack trace points at the loop; look for the earlier modification, possibly in a method the loop calls.
Never catch it to "make the problem go away": the state of the iteration is undefined afterwards.
Under the hood
Each structural change (add, remove, clear) increments the collection's modCount. The iterator remembers the value it saw at creation and compares it in next(); a mismatch throws.
Common mistakes
- Removing inside a for-each.
- Adding to a list from a callback it is currently iterating.
- Catching the exception and continuing.
How Unravel recognises it
When you paste code, Unravel looks for these signs of concurrentmodificationexception. Spotting them yourself is a good habit when you read unfamiliar code:
- Types:
ConcurrentModificationException - Method calls:
removeIf()retainAll()listIterator()copyOf()
Part of a learning path
- Collections in depth ยท previous: Iterator and Iterable ยท next: The Collections utility class
Related concepts
Frequently asked questions
What is ConcurrentModificationException?
This exception means a collection was changed while a loop was going through it. The fix is to remove through the Iterator, use removeIf, or loop over a copy.
What are the most common mistakes with ConcurrentModificationException?
Removing inside a for-each. Adding to a list from a callback it is currently iterating. Catching the exception and continuing.
How can I see ConcurrentModificationException in real code?
Paste your own Java into Unravel and it will point out where concurrentmodificationexception appears, explain how it works at the detail level you choose, and draw the flow of the code around it.