Concurrent Modification Exception in Java and How to Avoid it

Overview

1. Introduction

In this article, we'll look at the ConcurrentModificationException and how to avoid it using concurrent collections.

2. What is Concurrent Modification

One way to iterate over a collection is using an Iterator. An Iterator in Java is a class that contains information about the current and next elements of the collection.

A problem with iterators appears when we change their reference inside a loop. Changing the iterator reference while iterating the collection can lead to issues like infinite loops and data inconsistencies. The JVM implements a fail-fast strategy to avoid those kinds of issues. When we modify the iterator, which will potentially cause problems, the Iterator class throws a ConcurrentModificationException.

3. Exercising the ConcurrentModificationException

When we modify one collection at the same time we're reading from it, the JVM throws the exception.

3.1. Removing an Iterator During Loops

Let's see one example of that exception when removing the current Iterator inside a for loop:

 1public class ConcurrentModificationExample {
 2
 3  public static void main(String[] args) {
 4
 5    var names = new HashMap<String, Integer>();
 6
 7    names.put("John", 1);
 8    names.put("Mike", 2);
 9
10    for (String name : names.keySet()) {
11      names.remove(name);
12    }
13  }
14}

In the example above, we have two entries in our map. While iterating, we delete the current Iterator using the remove method. At this point, the JVM loses the reference of that Iterator, which makes it impossible to find the next element. Instead of hanging the program forever, the JVM throws the exception, as shown below:

1Exception in thread "main" java.util.ConcurrentModificationException
2  at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1597)
3  at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1620)
4  at com.blog.codesnippets.threads.ConcurrentModificationExample.main(ConcurrentModificationExample.java:13)

3.2. Adding New Iterator During Loops

A similar problem occurs when we add new elements to the current Iterator during a loop. Let's check out that in the code below:

 1public class ConcurrentModificationExample {
 2
 3  public static void main(String[] args) {
 4
 5    var names = new HashMap<String, Integer>();
 6
 7    names.put("John", 1);
 8    names.put("Mike", 2);
 9
10    for (String name : names.keySet()) {
11      names.put("Alice", 4);
12    }
13  }
14}

We add a new element for each element we read in the current Iterator, which can lead to an infinite loop scenario. Instead of running indefinitely, the JVM throws a ConcurrentModificationException and stops the program. The code above throws the same ConcurrentModificationException as shown previously.

Both examples show the problem in single-thread environments, but that can also appear in multi-threaded environments. Let's say that one thread reads a collection using a shared Iterator. If another thread deletes or adds another Iterator in a second for loop, the exception occurs again.

4. Avoiding Errors with Concurrent Collections

We can avoid the ConcurrentModificationException using concurrent collections from the java.util.concurrent package. We can modify the Iterator from these collections in single or multi-threaded environments without falling into an exception. Let's check one example using the ConcurrentHashMap:

 1public class ConcurrentModificationExample {
 2
 3  public static void main(String[] args) {
 4
 5    var names = new ConcurrentHashMap<String, Integer>();
 6
 7    names.put("John", 1);
 8    names.put("Mike", 2);
 9
10    for (String name : names.keySet()) {
11      names.put("Alice", 4);
12    }
13  }
14}

The above program doesn't throw any exceptions or fail with any of the errors mentioned, like infinite loops. That's because the ConcurrentHashMap class supports full concurrency for retrievals and updates. Anytime you need to modify a collection inside a loop, use a concurrent collection

Another way to create a ConcurrentHashMap object is with the static factory method Collections.synchronizedMap() that receives a regular HashMap

The concurrent package offers another set of concurrent collections like CopyOnWriteArrayList and CopyOnWriteArraySet for the list and set concurrent versions, respectively.

5. Conclusion

In this article, we've seen the ConcurrentModificationException and how to avoid it using concurrent collections.