Reduce your TCO of building and using Spring Native applications with just 3 steps. Switch to the best Unified Java Runtime. Learn More.

Eliminate ConcurrentModificationException in Java apps

Incorrect access to Java Collections causing ConcurrentModificationException


Published August 05, 2022


As part of the series of short articles by BellSoft, we look into various exceptions, their root causes, and elimination.

ConcurrentModificationException is one of the most common issues related to collections. The solution comes down to understanding how to handle collections correctly.

Problem

Let’s examine the following example:

import java.util.HashMap;
import java.util.Map;

public class AppMap
{
    public static void main( String[] args )
    {
        HashMap<String, String> map = new HashMap<>();
        map.put("L", "London");

        for (Map.Entry<String, String> entry: map.entrySet()) {
            if ("B".equals(entry.getKey())) {
                map.remove("B");
            }
        }
    }
}

This code works fine, but what if you try to add more cities?

map.put("L", "London");
map.put("B", "Berlin");
map.put("P", "Paris");

The app will fail on the second iteration with an Exception like this one:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1493)
	at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1526)
	at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1524)
	at org.example.AppMap.main(AppMap.java:16)

The code above is the most straightforward example leading to the ConcurrentModificationException. In practice, it may be hidden deep inside your business logic, and debugging could be quite hard.

Root cause

An attempt to modify a Collection while iterating through it is called a “concurrent modification.” Such modification makes no sense for most algorithms inside the standard Java collections.

The name of the exception is quite misleading. You may think that “concurrent” modification assumes multithreading, concurrency, and parallelism. But as we’ve seen above, you can run into it even inside a single thread.

This leads to a way more critical and frightening conclusion: it’s not the exception you should be afraid of. Unsupported modifications may cause the bugs that are very hard to detect.

What’s the problem? Even though collections try to protect themselves, there’s no guarantee that these checks are perfect.

For example, HashMap protects itself by counting modifications and failing with the ConcurrentModificationException if the values do not match.

int mc = this.modCount;
v = mappingFunction.apply(key);
if (mc != this.modCount) {
    throw new ConcurrentModificationException();
}
//...
this.modCount = mc + 1;

Here’s what HashMap documentation says:

The iterators returned by all of this class’s “collection view methods’’ are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator’s own remove method, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

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.

Iterators will attempt to detect concurrent modifications, but you can easily avoid these checks. Let’s replace HashMap with ArrayList in our example. Will it fail or not? What’s your take?

import java.util.*;

public class AppList
{
    public static void main( String[] args )
    {
        List<String> list = new ArrayList<>(Arrays.asList("L", "B", "P"));

        for (String entry: list) {
            if ("B".equals(entry)) {
                list.remove("B");
            }
        }
    }
}

The answer is no. The exception is detected and thrown purely on a best-effort basis.

Solution

Debugging ConcurrentModificationException is hard. You must find all modification operations enhanced for loops (e.g., LinkedHashMap.get() modifies its collection), concurrent access to references to the collection, etc.

The best thing you can do is to prevent the modifications by hiding collection references whenever possible. Hide your collection inside a private field, never return references to the collection from methods, and so on.

To write concurrent code, you should master locking and synchronization. Java Concurrency in Practice (JCIP) is your best friend in this regard.

Further reading

Want to know how to deal with NPEs in JavaFX apps? Read our short article about NullPointerExceptions

Ready to embrace innovations in the Java world? Discover virtual threads

Curious to know about current work within OpenJDK? Find out about Leyden, the new OpenJDK project

Announcements
Author image

Dmitry Chuyko

Senior Performance Architect at BellSoft

 Twitter

 LinkedIn