Wednesday, April 09, 2008

java.util.ConcurrentModificationException

Often, while working with maps and other collection utilities in Java you may come across this exception:

Exception in thread ... java.util.ConcurrentModificationException
at java.util.Hashtable$Enumerator.next(Unknown Source)
at ...


A quick look at the API documentation of ConcurrentModificationException says:
"This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible. For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it..."
Now this may not be very useful for newbies. They may be left wondering about multi-threading, concurrent access etc. Given below is a sample code which is single threaded, yet, it fails with ConcurrentModifcationAccess. In this code I am trying to remove keys that end with digit "5" from a Map:

public class MapTest {

public static void main(String[] args) {
//create a sample Map
Map<String, String> map = new Hashtable<String,String>();
map.put("key1", "value100");
map.put("key2", "value200");
map.put("key3", "value300");
map.put("key4", "value400");
map.put("key5", "value500");
map.put("key55", "value550");
map.put("key6", "value600");

System.out.println("mappings before: "+map);

//retrieve the set of Keys in the map
Set<String> keySet = map.keySet();

//iterate over keys and remove
// those which ends with "5"
for(String key : keySet) {
if(key.endsWith("5" )) {
keySet.remove(key);
}
}

System.out.println("mappings after: "+map);
}

}

When you run this code it will throw concurrent modification exception. Now try the following sample. Changed code is highlighted.

public class MapTest {

public static void main(String[] args) {
//create a sample Map
Map<String, String> map = new Hashtable<String,String>();
map.put("key1", "value100");
map.put("key2", "value200");
map.put("key3", "value300");
map.put("key4", "value400");
map.put("key5", "value500");
map.put("key55", "value550");
map.put("key6", "value600");

System.out.println("mappings before: "+map);

//retrieve the set of Keys in the map

Iterator<String> keySetItr = map.keySet().iterator();
while( keySetItr.hasNext()) {
String key = keySetItr.next();
if(key.endsWith("5" )) {
keySetItr.remove();
}
}

System.out.println("mappings after: "+map);
}

}
This code runs fine. Why?

The evil is in the code:
for(String key : keySet) {
...
keySet.remove(key);

The for(...) actually opens an iterator internally and when we remove the key from KeySet this iterator logic breaks, throwing the ConcurrentModificationException. To avoid this one should use the second example given above, where same Iterator (keySetItr) is used both for iteration and removing the keys.

12 comments:

Anonymous said...

good tip... thank you!

Anonymous said...

nice info!

Anonymous said...

That was driving me mad! Thanks for the solution mate!

Mikuto said...

Great tip man. More power!

Shahzad Khan said...

Thank you very much. This was such a difficult to understand bug.. such as elegant solution though, use the given remove feature in the iterator instead! Of course... duh !!

manoj said...

I have another question here.

If I have multi threaded application and one thread synchronized on the map and then open an iterator while other thread trying to modify the map (this thread is not synchronized), will we get the same error ?

Actually I am getting this error in this scenario and wondering how it is possible ?

Anonymous said...

Excellent! Gracias amigo

Anonymous said...

I always wondered how to get around this Exception. Thanks a lot!

Léna said...

Thanks a lot, you just saved my morning :)

Anonymous said...

really useful info! thank you!!

Anonymous said...

nice tip. Thanks much.

Anonymous said...

Che Dio ti benedica!
Dio benu vin! DANKON
God bless you