1. Introduction

In this tutorial, we’ll tackle a common issue in Kotlin: the ConcurrentModificationException. This usually occurs when we attempt to modify a collection while iterating through it, which is a common pitfall in concurrent programming.

This exception can be frustrating, especially when we’re unsure what’s causing it or how to fix it. Let’s explore the root cause of this exception and most importantly some strategies to avoid it in our code.

2. What Is the ConcurrentModificationException?

The ConcurrentModificationException is typically thrown when an operation tries to modify a collection while another operation is iterating over it:

val numbers = mutableListOf(1, 2, 3, 4, 5)

assertThrows<ConcurrentModificationException> {
    for (item in numbers) {
        if (item == 3) {
            numbers.remove(item)
        }
    }
}

Here, we’re trying to remove an item from the list while traversing it. This simultaneous action activates Kotlin’s safety mechanism to prevent data inconsistencies and race conditions, resulting in a ConcurrentModificationException.

In simple terms, when a collection is modified while being iterated over in Kotlin, a ConcurrentModificationException is thrown. Although having multiple threads can increase the likelihood of this exception, it can also occur within a single thread.

In concurrent programming, ‘concurrent’ means that two or more processes are happening simultaneously, but they don’t necessarily have to come from different threads. As we saw in the example above, we can encounter this exception on a single thread if we try to modify a collection while iterating over it.

Now that we understand the cause of the ConcurrentModificationException, let’s explore some strategies to avoid it in our code.

3. Using the Iterator

One strategy to avoid the ConcurrentModificationException is to use functions provided by Iterator while iterating. Iterator allows us to remove elements safely during iteration through the remove() function:

val numbers = mutableListOf(1, 2, 3, 4, 5)
val iterator = numbers.iterator()
while (iterator.hasNext()) {
    val number = iterator.next()
    if (number == 3) {
        iterator.remove()
    }
}

In the code above, we’re using the Iterator‘s remove() function instead of the List‘s remove() function. This way, we can prevent the ConcurrentModificationException.

4. Using removeAll()

We can also use the removeAll() function from the Kotlin standard library to avoid the ConcurrentModificationException.

Similar to Java’s removeIf() function, the removeAll() function uses functional programming concepts to filter the underlying collection using the given predicate:

val numbers = mutableListOf(1, 2, 3, 4, 5) 
numbers.removeAll { number -> number == 3 }

In the example above, the removeAll() function iterates internally over the collection and removes each element that satisfies the given condition. This is generally safer and more concise than manually iterating over and modifying the collection, which can lead to a ConcurrentModificationException if not handled correctly.

5. Modifying a Copy

Another approach we can take is to modify a copy of the original collection. Here, we copy the collection, perform any modifications on the copy, and replace the original one:

var numbers = mutableListOf(1, 2, 3, 4, 5)
val copyNumbers = numbers.toMutableList()
for (number in numbers) {
    if (number == 3) {
        copyNumbers.remove(number)
    }
}
numbers = copyNumbers

As we can see in the example, we’ve successfully avoided the exception by not making changes to the original list. However, this approach can be relatively resource-intensive. In cases where memory footprint is important, we should prefer other solutions highlighted in this article.

6. Using CopyOnWriteArrayList

Finally, we can use a CopyOnWriteArrayList to avoid the ConcurrentModificationException. CopyOnWriteArrayList* is a thread-safe variant of *ArrayList. *It creates a copy of the underlying list internally whenever a modification operation (such as add(), set(), or remove()) is performed*. Due to this mechanism, it can safely handle modifications while being iterated over:

val list = CopyOnWriteArrayList(listOf(1, 2, 3, 4, 5))

for (item in list) {
    if (item == 3) {
        list.remove(item)
    }
}

In this example, even though we’re modifying the list while iterating over it, no ConcurrentModificationException occurs. However, we must remember that CopyOnWriteArrayList incurs a memory cost because it makes a fresh copy of the underlying array upon each mutation. Therefore, we should use this with collections that predominantly serve read operations and have fewer write operations.

7. Conclusion

In this article, we’ve learned to manage ConcurrentModificationExceptions in Kotlin effectively through various strategies. Techniques such as using an Iterator for safe removal during iteration, employing removeAll() for clean functional-style modifications, making modifications on a copy of a collection, or opting for CopyOnWriteArrayList are some of the solutions available to us. Each approach balances safety with performance considerations, enabling us to effectively address concurrency issues in Kotlin.

As always, the code samples can be found over on GitHub.