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.