The ConcurrentModificationException
is a common error that occurs when you modify a List
while iterating over it using methods like for-each
or Iterator
. This article will explain why it happens and explore different ways to avoid it with simple explanations and code examples.
Why Does ConcurrentModificationException
Occur?
In Java, most List
implementations, such as ArrayList
, are fail-fast. When you iterate over a list using an Iterator
, the underlying structure of the list is monitored for changes. If the structure changes (e.g., an element is added or removed) during iteration, the Iterator
detects this and throws a ConcurrentModificationException
to prevent unpredictable behavior.
import java.util.ArrayList;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // This will throw ConcurrentModificationException
}
}
}
}
Solutions to Prevent ConcurrentModificationException
Here are several approaches to safely modify a List
while iterating over it:
1. Use Iterator.remove()
Instead of directly modifying the list, use the remove()
method provided by the Iterator
. This is the only safe way to remove elements while iterating.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("B")) {
iterator.remove(); // Safe removal
}
}
System.out.println(list); // Output: [A, C]
}
}
2. Use a ListIterator
for More Control
If you need more control, such as replacing elements during iteration, use a ListIterator
. It supports both removal and addition.
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Example {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
String item = listIterator.next();
if (item.equals("B")) {
listIterator.remove(); // Safe removal
listIterator.add("D"); // Add a new element
}
}
System.out.println(list); // Output: [A, D, C]
}
}
3. Use a Copy of the List
If modifying the original list directly isn’t a requirement, you can iterate over a copy of the list while modifying the original.
import java.util.ArrayList;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for (String item : new ArrayList<>(list)) {
if (item.equals("B")) {
list.remove(item); // Safe because we are iterating over a copy
}
}
System.out.println(list); // Output: [A, C]
}
}
4. Use Java Streams
In Java 8 and later, streams provide a concise way to filter and collect elements into a new list.
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Example {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list = list.stream()
.filter(item -> !item.equals("B")) // Remove "B"
.collect(Collectors.toList());
System.out.println(list); // Output: [A, C]
}
}
5. Use a Concurrent Collection
For multithreaded environments, consider using a concurrent collection like CopyOnWriteArrayList
. This collection creates a copy of the list during modifications, ensuring thread-safety.
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Example {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // Safe in CopyOnWriteArrayList
}
}
System.out.println(list); // Output: [A, C]
}
}
When to Use Each Approach?
Iterator.remove()
orListIterator
: For single-threaded environments where you need precise control over modifications.- Copy of the List: When modifications don’t need to reflect immediately in the current iteration.
- Streams: For concise, functional-style filtering and transformation.
- Concurrent Collections: In multi-threaded applications requiring thread-safe operations.
The ConcurrentModificationException
occurs because most List
implementations are fail-fast, designed to detect structural modifications during iteration. By using techniques like Iterator.remove()
, ListIterator
, copying the list, streams, or concurrent collections, you can safely modify a list without encountering this exception.