Iterator & ConcurrentModificationException in Java
Fail-fast iterators; removing safely with iterator.remove or removeIf.
for-each has a hidden iterator
for (String s : list) is shorthand for an **Iterator**: hasNext() / next() calls. Most iterators are fail-fast: they remember the list's modification count, and if the list changes behind their back, the **next next() throws ConcurrentModificationException**.
for (String s : list) { ... }
// is really:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next(); // checks here
}Your turn
What happens?
var list = new ArrayList<String>();
list.addAll(List.of("a", "b", "c", "d"));
for (String s : list) {
if (s.equals("a")) {
list.remove(s);
}
}
System.out.println(list);Prints [b, c, d]Throws ConcurrentModificationExceptionPrints [a, b, c, d]
Show the answer
list.remove(s) changes the list while the hidden iterator is active. On the very next next(), the iterator notices the count changed and throws. No threads needed.
Remove through the iterator
**it.remove() deletes the element last returned by next()** and keeps the iterator's bookkeeping in sync. No exception.
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().isBlank()) {
it.remove(); // safe
}
}Even simpler: removeIf
**removeIf(predicate) removes every matching element in one safe pass**. It's the cleanest way to filter a collection in place.
var nums = new ArrayList<>(List.of(1, 2, 3, 4));
nums.removeIf(n -> n % 2 == 0);
// [1, 3]The sneaky one
Now we remove the second-to-last element. What happens?
var list = new ArrayList<String>();
list.addAll(List.of("a", "b", "c", "d"));
for (String s : list) {
if (s.equals("c")) {
list.remove(s);
}
}
System.out.println(list);Throws ConcurrentModificationException[a, b, d][a, b, c, d]
Show the answer
No exception! After removing "c" the size drops to 3. The cursor is at 3 too, so **hasNext() returns false and the loop ends before next() can check**. "d" is silently never visited.
Fail-fast is best effort
As you just saw, the check isn't a guarantee. Never rely on the exception to catch your mistake: code that "works" in tests may be silently skipping elements. Always modify through it.remove() or removeIf.
On the job
ConcurrentModificationException is one of the most common Java exceptions in single-threaded code. When collections really are shared between threads, use concurrent ones like ConcurrentHashMap or CopyOnWriteArrayList, whose iterators never throw it.
Key takeaways
- Don't call list.remove(...) inside a for-each over that list
- iterator.remove() removes the element last returned by next()
- removeIf(predicate) is the cleanest way to filter in place
- Fail-fast is best-effort, not a guarantee — never rely on it
The fail-fast check is a plain int counter named modCount, declared in AbstractList. Every structural change bumps it, and each iterator compares it with the value it saw at the start.
Practice questions
What happens when this runs?
var list = new ArrayList<>(List.of(1, 2, 3, 4));
for (Integer n : list) {
if (n == 2) {
list.remove(n);
}
}
System.out.println(list);- [1, 3, 4]
- Throws ConcurrentModificationException
- [1, 2, 3, 4]
- [1, 4]
Check your answer
Throws ConcurrentModificationException. list.remove(n) (n is an Integer, so it removes by value) changes the list while the hidden iterator is active. The following next() notices and throws.
Fill the blank to remove blank names safely during iteration.
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().isBlank()) {
___;
}
}- names.remove(it.next())
- it.remove()
- names.remove(it)
- it.next().remove()
Check your answer
it.remove(). iterator.remove() deletes the element most recently returned by next() and keeps the iterator's bookkeeping in sync. Calling names.remove(...) would trigger ConcurrentModificationException.