Core Java

How to Avoid Concurrent Modification Exceptions in Java Collections

Java collections are powerful tools for managing data, but they can introduce complexity, especially when dealing with concurrent modifications. One of the most common runtime exceptions developers encounter is ConcurrentModificationException. This error arises when a collection is modified while it is being iterated, and it can be challenging to resolve without understanding its underlying causes. In this article, we’ll dive into the nature of this exception, common scenarios where it occurs, and effective strategies to avoid it.

1. What is ConcurrentModificationException?

ConcurrentModificationException is thrown when a collection is structurally modified while being iterated by an iterator, except when the modification is done through the iterator’s own remove method. This structural modification includes actions like adding, removing, or changing elements that affect the size of the collection.

Example:

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");

for (String s : list) {
    if (s.equals("A")) {
        list.remove(s); // Throws ConcurrentModificationException
    }
}

Why it happens: In the above example, modifying the list while iterating over it triggers the exception because the list’s internal state is altered during the iteration, and the iterator is not aware of the change.

2. Common Scenarios Leading to ConcurrentModificationException

1. Modifying Collections in Enhanced For-Loops

One of the most frequent cases where this exception occurs is when using enhanced for loops to iterate over collections and modifying them directly inside the loop.

Example:

List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);

for (Integer number : numbers) {
    if (number == 2) {
        numbers.remove(number); // Throws ConcurrentModificationException
    }
}

Solution: Use an explicit iterator to modify the collection safely.

Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
    Integer number = iterator.next();
    if (number == 2) {
        iterator.remove(); // Safe removal using iterator
    }
}

2. Modifying Collections While Iterating Over Multiple Threads

When multiple threads are accessing and modifying a shared collection, this can also lead to ConcurrentModificationException.

Example:

List<String> sharedList = new ArrayList<>();
sharedList.add("A");
sharedList.add("B");

Runnable modifyTask = () -> {
    for (String s : sharedList) {
        if (s.equals("A")) {
            sharedList.remove(s); // May throw ConcurrentModificationException
        }
    }
};

new Thread(modifyTask).start();
new Thread(modifyTask).start();

Solution: Use thread-safe collections like CopyOnWriteArrayList or synchronized blocks to handle modifications safely across multiple threads.

List<String> safeList = new CopyOnWriteArrayList<>();
safeList.add("A");
safeList.add("B");

Runnable modifyTask = () -> {
    for (String s : safeList) {
        if (s.equals("A")) {
            safeList.remove(s); // No ConcurrentModificationException with CopyOnWriteArrayList
        }
    }
};

new Thread(modifyTask).start();
new Thread(modifyTask).start();

3. Modifying Maps During Iteration

ConcurrentModificationException is not limited to lists. It also occurs when modifying maps during iteration.

Example:

Map<Integer, String> map = new HashMap<>();
map.put(1, "A");
map.put(2, "B");

for (Map.Entry<Integer, String> entry : map.entrySet()) {
    if (entry.getKey() == 1) {
        map.remove(entry.getKey()); // Throws ConcurrentModificationException
    }
}

Solution: Use the iterator’s remove method or opt for ConcurrentHashMap for thread-safe operations.

Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<Integer, String> entry = iterator.next();
    if (entry.getKey() == 1) {
        iterator.remove(); // Safe removal using iterator
    }
}

3. Strategies to Avoid ConcurrentModificationException

1. Use Iterator’s remove() Method

If you need to remove elements during iteration, the safest way is to use the iterator’s remove() method. This ensures that the iterator is aware of the structural modification and updates its internal state accordingly.

Example:

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String value = iterator.next();
    if (value.equals("B")) {
        iterator.remove(); // Safe removal
    }
}

2. Switch to Concurrent Collections

For multi-threaded environments, use thread-safe alternatives from the java.util.concurrent package. These collections, like ConcurrentHashMap and CopyOnWriteArrayList, are designed to handle concurrent modifications without throwing exceptions.

Example with ConcurrentHashMap:

ConcurrentMap<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "A");
map.put(2, "B");

map.forEach((key, value) -> {
    if (key == 1) {
        map.remove(key); // Safe removal in ConcurrentHashMap
    }
});

Example with CopyOnWriteArrayList:

List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");

for (String s : list) {
    if (s.equals("A")) {
        list.remove(s); // Safe removal in CopyOnWriteArrayList
    }
}

3. Use Synchronized Blocks for Manual Synchronization

In cases where concurrent collections are not an option, and you’re dealing with multiple threads, manually synchronizing the block of code that modifies the collection can help prevent exceptions.

Example:

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");

synchronized (list) {
    for (String s : list) {
        if (s.equals("A")) {
            list.remove(s); // Safe removal inside synchronized block
        }
    }
}

4. Iterate Over a Copy of the Collection

Another approach is to iterate over a copy of the collection while modifying the original. This avoids modifying the collection while iterating over it, thus preventing the exception.

Example:

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");

for (String s : new ArrayList<>(list)) {
    if (s.equals("A")) {
        list.remove(s); // Safe removal from the original list
    }
}

4. Conclusion

ConcurrentModificationException is a common issue when modifying Java collections during iteration, but it’s easily preventable with the right strategies. Whether you use an explicit iterator, switch to concurrent collections, or implement manual synchronization, understanding how Java handles structural modifications can save you from runtime surprises.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button