Fundamentals 13 min read

Why Does Sorting a List Inside a Loop Trigger ConcurrentModificationException?

The article explains how sorting a List while iterating over it in Java can cause a ConcurrentModificationException, describes the fast‑fail mechanism of iterators, shows the root cause in nested method calls, and provides a safe fix using a copied list.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Why Does Sorting a List Inside a Loop Trigger ConcurrentModificationException?

Problem Description

During a development task the business needed to know whether a message was refund‑driven. The only way was to extract the unique refund detail from a possibly unordered list of Detail objects, sort them by modification time, and check if the last element represented a refund.

public Boolean isReFundEvent(Event event) {
    List<Detail> details = event.getDetails();
    if (Collections.isEmpty(details)) {
        throw new Exception(...);
    }
    Collections.sort(details, new Comparator<Detail>() {
        @Override
        public int compare(Detail input1, Detail input2) {
            return input1.getModifiedDate().compareTo(input2.getModifiedDate());
        }
    });
    return details.get(details.size() - 1).isRefund();
}

Exception Encountered

After the code passed unit tests, integration tests and quality acceptance, it was deployed to a pre‑release environment. At runtime a ConcurrentModificationException was thrown.

// Outer service class
public void func1(Event event) {
    List<Detail> details = event.getDetails();
    if (Collections.isEmpty(details)) { return; }
    func2(event);
    // iterate over details
    for (Detail detail : details) {
        List<FundItem> refundItems = fundService.queryRefundItems(event.getExt().getRefundId());
        if (!Collections.isEmpty(refundItems)) {
            for (FundItem refundItem : refundItems) {
                func3(event, detail, refundItem);
            }
        }
    }
}

public void func2(Event event) {
    isReFundEvent(event); // also calls the sorting method
}

public void func3(Event event, Detail detail, FundItem refundItem) {
    isReFundEvent(event);
}

The inner call to isReFundEvent sorts the same details list while the outer for loop is still iterating, causing the fast‑fail check to fire.

Fast‑Fail Mechanism

Fast‑fail (or fail‑fast) is a design philosophy that makes a system report an error as soon as it is detected, rather than trying to continue execution. In Java collections, iterators are fast‑fail: if the underlying collection is structurally modified after the iterator is created, the iterator throws ConcurrentModificationException on the next access.

Iterator Implementation Details

private class Itr implements Iterator<E> {
    int cursor;          // index of next element
    int lastRet = -1;    // index of last returned element
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size) throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

The iterator records expectedModCount when it is created. Any structural change (add, remove, sort) increments the list’s modCount. When next() is called, checkForComodification() compares the two values and throws the exception if they differ.

Root Cause Analysis

Because isReFundEvent sorts the original details list, the first call (from func2) already orders the list. When the outer loop later reaches the last element, the subsequent call from func3 sorts the list again, but the iterator has already passed the point where next() would perform the check, so the modification goes unnoticed. The bug only appears when the refund detail is not already the last element, which is why the exception was intermittent.

Fix and Prevention

Copy the list before sorting, leaving the original list untouched during iteration:

public Boolean isReFundEvent(Event event) {
    List<Detail> original = event.getDetails();
    if (Collections.isEmpty(original)) {
        throw new Exception(...);
    }
    List<Detail> temp = new ArrayList<>(original); // deep copy
    Collections.sort(temp, (d1, d2) -> d1.getModifiedDate().compareTo(d2.getModifiedDate()));
    return temp.get(temp.size() - 1).isRefund();
}

Alternatively, use the iterator’s own remove or add methods, or employ thread‑safe collections when concurrent access is possible.

Takeaways

In single‑threaded code, ConcurrentModificationException usually stems from modifying a collection while iterating over it, especially inside nested method calls.

In multi‑threaded scenarios, use thread‑safe collections (e.g., CopyOnWriteArrayList) or synchronize access.

Prefer working on a copy of the collection when the original needs to stay stable during iteration.

Understand the fast‑fail behavior of Java iterators to avoid hidden bugs.

Exception stack trace illustration
Exception stack trace illustration
Demo output showing when exception occurs
Demo output showing when exception occurs
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DebuggingJavaCollectionsIteratorConcurrentModificationExceptionfast-fail
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.