Fail-fast iterators: ConcurrentModificationException — and when it DOESN'T fire
ArrayList's iterator checks a modCount on every next() call and throws ConcurrentModificationException
if the list was structurally modified outside the iterator. The commonly-missed detail: this check only runs
inside next(). If you remove an element such that hasNext() becomes false right after, the loop ends
before next() is called again — so the exception silently doesn't fire, and you get a subtly-wrong
result instead of a loud error. This is real, verified behavior, not a hypothetical.
import java.util.*;
System.out.println("case A: [1,2,3], remove value 2 (second-to-last) mid for-each");
List<Integer> a = new ArrayList<>(List.of(1, 2, 3));
try {
for (Integer v : a) { if (v == 2) a.remove(v); }
System.out.println(" NO exception thrown. list silently ends up = " + a + " <-- the trap");
} catch (ConcurrentModificationException e) {
System.out.println(" CME: " + e);
}
System.out.println("case B: [1,2,3,4], remove value 2 (NOT second-to-last)");
List<Integer> b = new ArrayList<>(List.of(1, 2, 3, 4));
try {
for (Integer v : b) { if (v == 2) b.remove(v); }
System.out.println(" no exception. list now = " + b);
} catch (ConcurrentModificationException e) {
System.out.println(" CME: " + e);
}
The actual correct fix — always use the iterator's own remove(), which updates modCount in sync so no mismatch is ever detected:
List<Integer> d = new ArrayList<>(List.of(1, 2, 3, 4));
Iterator<Integer> it = d.iterator();
while (it.hasNext()) {
if (it.next() == 2) it.remove();
}
System.out.println("Iterator.remove() result: " + d);