
Java util.ConcurrentModificationException – Causes and Fixes
The ConcurrentModificationException in Java is one of those runtime exceptions that can catch developers off guard, especially when working with collections in multi-threaded environments or even single-threaded scenarios with nested iterations. This exception occurs when the Java runtime detects that a collection has been modified while it’s being iterated over in an unsafe manner. Understanding this exception is crucial for building robust applications, whether you’re developing web services on a VPS or deploying enterprise applications on dedicated servers. In this post, we’ll dive deep into what causes this exception, examine real-world scenarios where it occurs, and explore multiple strategies to prevent and fix it.
Understanding ConcurrentModificationException
The ConcurrentModificationException is a runtime exception that extends RuntimeException. Contrary to what its name might suggest, this exception isn’t exclusively related to concurrent programming – it can occur in single-threaded applications too. The Java collections framework uses a fail-fast mechanism to detect structural modifications to collections during iteration.
When you create an iterator for a collection, it maintains an internal modification counter called modCount
. Every time the collection is structurally modified (elements added, removed, or replaced), this counter increments. The iterator checks this counter before each operation, and if it detects a mismatch, it throws a ConcurrentModificationException.
// This will throw ConcurrentModificationException
List<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");
for (String item : list) {
if (item.equals("item2")) {
list.remove(item); // Modifying collection during iteration
}
}
Common Causes and Scenarios
Let’s examine the most frequent scenarios where this exception occurs:
- Removing elements during enhanced for-loop: The most common cause, as shown in the example above
- Adding elements during iteration: Similar issue when adding new elements while iterating
- Multi-threaded modifications: One thread modifying a collection while another thread iterates over it
- Nested iterations: Modifying a collection in an inner loop while the outer loop is iterating
- Callback modifications: Collections modified within event handlers or callback methods during iteration
Here’s a more complex example showing nested iteration issues:
List<List<String>> nestedList = new ArrayList<>();
// ... populate nestedList
for (List<String> subList : nestedList) {
for (String item : subList) {
if (someCondition(item)) {
subList.remove(item); // ConcurrentModificationException
}
}
}
Step-by-Step Solutions and Fixes
There are several strategies to handle and prevent ConcurrentModificationException. Let’s explore each approach with practical examples:
Solution 1: Using Iterator.remove()
The safest way to remove elements during iteration is using the iterator’s remove method:
List<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("item2")) {
iterator.remove(); // Safe removal
}
}
Solution 2: Using removeIf() Method (Java 8+)
Java 8 introduced the removeIf method, which provides a clean functional approach:
List<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");
// Remove all items that match the condition
list.removeIf(item -> item.equals("item2"));
// More complex example with custom logic
list.removeIf(item -> {
// Your complex removal logic here
return item.startsWith("temp_") || item.length() > 50;
});
Solution 3: Collecting Items to Remove
Sometimes you need more complex logic that doesn’t fit into removeIf. In such cases, collect items to remove separately:
List<String> list = new ArrayList<>();
List<String> toRemove = new ArrayList<>();
// First pass: identify items to remove
for (String item : list) {
if (complexRemovalCondition(item)) {
toRemove.add(item);
}
}
// Second pass: remove identified items
list.removeAll(toRemove);
Solution 4: Using Concurrent Collections
For multi-threaded scenarios, use thread-safe collections:
// Using CopyOnWriteArrayList for scenarios with more reads than writes
List<String> concurrentList = new CopyOnWriteArrayList<>();
concurrentList.add("item1");
concurrentList.add("item2");
// Safe to modify during iteration in multi-threaded environment
for (String item : concurrentList) {
if (someCondition(item)) {
concurrentList.remove(item); // No ConcurrentModificationException
}
}
// Using ConcurrentHashMap for key-value pairs
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
// Similar safety guarantees for map operations
Real-World Use Cases and Examples
Let’s examine some practical scenarios where these solutions apply:
Web Application Session Management
public class SessionManager {
private List<UserSession> activeSessions = new ArrayList<>();
// Unsafe approach - will throw ConcurrentModificationException
public void cleanupExpiredSessionsUnsafe() {
for (UserSession session : activeSessions) {
if (session.isExpired()) {
activeSessions.remove(session); // Problem!
}
}
}
// Safe approach using removeIf
public void cleanupExpiredSessions() {
activeSessions.removeIf(UserSession::isExpired);
}
// Alternative safe approach for complex logic
public void cleanupExpiredSessionsComplex() {
List<UserSession> expiredSessions = activeSessions.stream()
.filter(session -> session.isExpired() || session.getIdleTime() > MAX_IDLE)
.collect(Collectors.toList());
activeSessions.removeAll(expiredSessions);
// Log cleanup activity
expiredSessions.forEach(session ->
logger.info("Cleaned up session: {}", session.getId()));
}
}
Event Processing System
public class EventProcessor {
private Queue<Event> eventQueue = new ConcurrentLinkedQueue<>();
private List<EventListener> listeners = new CopyOnWriteArrayList<>();
public void processEvents() {
Event event;
while ((event = eventQueue.poll()) != null) {
// Safe iteration even if listeners are added/removed concurrently
for (EventListener listener : listeners) {
try {
listener.handleEvent(event);
} catch (Exception e) {
// Remove failed listeners safely
listeners.remove(listener);
}
}
}
}
}
Performance Comparison of Different Approaches
Approach | Performance | Memory Usage | Thread Safety | Best Use Case |
---|---|---|---|---|
Iterator.remove() | High | Low | Single-threaded only | Simple removals during iteration |
removeIf() | High | Low | Single-threaded only | Functional-style conditional removal |
Collect & Remove | Medium | Higher | Single-threaded only | Complex removal logic |
CopyOnWriteArrayList | Low (writes) | High | Full | Read-heavy, occasional writes |
ConcurrentHashMap | High | Medium | Full | High-concurrency key-value operations |
Advanced Patterns and Best Practices
Here are some advanced techniques for handling complex modification scenarios:
Producer-Consumer Pattern with Safe Modifications
public class SafeProducerConsumer<T> {
private final BlockingQueue<T> queue = new LinkedBlockingQueue<>();
private final Set<T> processing = ConcurrentHashMap.newKeySet();
public void produce(T item) {
queue.offer(item);
}
public void consume() {
try {
T item = queue.take();
if (processing.add(item)) { // Atomic check-and-add
try {
processItem(item);
} finally {
processing.remove(item);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// Safe way to get snapshot of currently processing items
public List<T> getProcessingSnapshot() {
return new ArrayList<>(processing);
}
}
Batch Modification Pattern
public class BatchModificationExample {
private List<DataRecord> records = new ArrayList<>();
public void optimizedBatchUpdate(Predicate<DataRecord> condition,
Function<DataRecord, DataRecord> updater) {
// Collect indices to modify (avoiding object references)
List<Integer> indicesToUpdate = IntStream.range(0, records.size())
.filter(i -> condition.test(records.get(i)))
.boxed()
.collect(Collectors.toList());
// Apply updates in reverse order to maintain indices
Collections.reverse(indicesToUpdate);
for (Integer index : indicesToUpdate) {
DataRecord updated = updater.apply(records.get(index));
if (updated != null) {
records.set(index, updated);
} else {
records.remove(index.intValue());
}
}
}
}
Troubleshooting and Debugging Tips
When dealing with ConcurrentModificationException, consider these debugging strategies:
- Enable detailed logging: Log collection modifications to identify the exact modification point
- Use synchronized blocks sparingly: They can mask the real issue and hurt performance
- Consider immutable collections: Use Guava’s ImmutableList or Collections.unmodifiableList() when appropriate
- Implement defensive copying: Create copies of collections before passing them to untrusted code
// Debugging helper to track modifications
public class DebuggableList<T> extends ArrayList<T> {
private static final Logger logger = LoggerFactory.getLogger(DebuggableList.class);
@Override
public boolean remove(Object o) {
logger.debug("Removing element: {} at {}", o,
Thread.currentThread().getStackTrace()[2]);
return super.remove(o);
}
@Override
public boolean add(T t) {
logger.debug("Adding element: {} at {}", t,
Thread.currentThread().getStackTrace()[2]);
return super.add(t);
}
}
Integration with Popular Frameworks
Different frameworks handle collection modifications in various ways. Here’s how to work safely with popular Java frameworks:
Spring Framework
@Component
public class SpringSafeCollectionHandler {
@EventListener
public void handleCustomEvent(CustomEvent event) {
// Use CopyOnWriteArrayList for event listener collections
// that might be modified during event processing
List<EventHandler> handlers = new CopyOnWriteArrayList<>(eventHandlers);
handlers.parallelStream()
.forEach(handler -> handler.process(event));
}
}
For more information on Java collections and concurrent programming, refer to the official Java Concurrent Collections documentation and the Java Collections Framework Guide.
Understanding and properly handling ConcurrentModificationException is essential for building robust Java applications. Whether you’re running lightweight applications on a VPS or heavy enterprise workloads on dedicated infrastructure, these patterns and practices will help you avoid common pitfalls and build more reliable software. The key is choosing the right approach based on your specific use case, performance requirements, and concurrency needs.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.