Collection throws or doesn’t throw ConcurrentModificationException based on the contents of the Collection [duplicate]

Short answer

Because the fail-fast behavior of an iterator isn’t guaranteed.

Long answer

You’re getting this exception because you cannot manipulate a collection while iterating over it, except through the iterator.

Bad:

// we're using iterator
for (Iterator<String> i = c.iterator(); i.hasNext();) {  
    // here, the collection will check it hasn't been modified (in effort to fail fast)
    String s = i.next();
    if(s.equals("lalala")) {
        // s is removed from the collection and the collection will take note it was modified
        c.remove(s);
    }
}

Good:

// we're using iterator
for (Iterator<String> i = c.iterator(); i.hasNext();) {  
    // here, the collection will check it hasn't been modified (in effort to fail fast)
    String s = i.next();
    if(s.equals("lalala")) {
        // s is removed from the collection through iterator, so the iterator knows the collection changed and can resume the iteration
        i.remove();
    }
}

Now to the “why”: In the code above, notice how the modification check is performed – the removal marks the collection as modified, and next iteration checks for any modifications and fails if it detects the collection changed. Another important thing is that ArrayList (not sure about other collections) does not check for modification in hasNext().

Therefore, two strange things may happen:

  • If you remove the last element while iterating, nothing will be thrown
    • That’s because there’s no “next” element, so the iteration ends before reaching the modification-checking code
  • If you remove the second-to-last element, ArrayList.hasNext() will actually also return false, because the iterator’s current index is now pointing at the last element (former second-to-last).
    • So even in this case, there’s no “next” element after the removal

Note that this all is in line with ArrayList’s documentation:

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

Edited to add:

This question provides some information on why the concurrent modification check is not performed in hasNext() and is only performed in next().

Leave a Comment