What if Java didn’t have modCount?
Exploring the lesser-known debugging field in mutable Java Collections
What is modCount?
Most developers know what a ConcurrentModificationException is in Java Collections. Developers typically encounter this error when they mutate a collection while iterating over it. I discovered recently, that developers rarely look to see what causes this exception to be thrown.
I have been asking developers whether they know about the modCount variable used in various types throughout the Java Collections Framework. So far, the answer has been an overwhelmingly chorus of “No.” This surprises me, since IntelliJ reports 131 usages of AbstractList.modCount in the JDK. I believe the modCount variable has existed since the Java Collections Framework was introduced in Java 2, all the way back in 1998. The places AbstractList.modCount is referenced are displayed in detail in the mind map above.
How could Java developers today not know what the modCount variable is?
Because the modCount variable is hard to find
The following is the JavaDoc and field definition for modCount in AbstractList. The field is buried two thirds of the way into the class on line 630. Note: the modCount field is the only field defined in AbstractList.
I don’t know why a field definition for one of the most referenced fields in the Java Collections framework is defined on line 630 of AbstractList. I’ve also rarely if ever seen a lone field require 25 lines of JavaDoc. If modCount and ConcurrentModificationException are vitally important for the mutable Java Collections, then I would move modCount to the top of the class and display it prominently with blinking warning signs and sirens. The usage of modCount is certainly not well-hidden. It is the first line in one of the most used methods in the entire Java Collections framework — ArrayList.add().
There is no comment about modCount in the add method. I guess a developer can always use their IDE to step into where the field is defined to find out why it is incremented here.
Et tu HashMap?
Unlike AbstractList, HashMap has fields other than modCount, and has them all together with a “Fields” separator comment. They are still defined a few hundred lines in the source file but stand out with the separator.
The following is a mind map showing the references to HashMap.modCount.
Note, because java.util.HashSet is built using java.util.HashMap, it does not have to define its own modCount field. HashSet gets the fail-fast behavior directly from HashMap.
Are there other Java Collection types that define modCount?
This is the modCount definition in TreeMap. As we can see, the field is moving up closer to the class definition. The amount of Javadoc is significantly reduced here when compared to the AbstractList and HashMap versions.
Other types that define a modCount field can be found by searching for usages of ConcurrentModificationException. The types I was able to find the define a modCount variable were:
java.util.Hashtablejava.util.WeakHashMapjava.util.PriorityQueuejava.util.regex.Matcherjava.util.IdentityHashMap
Are there Java Collection types that do not use modCount?
Some of the Collection types in Java I was able to find that do not use modCount to provide fail-fast iterators were:
java.util.ArrayDequejava.util.CopyOnWriteArrayListjava.util.CopyOnWriteArraySet
The ArrayDeque class does provide fail-fast iterator behavior, but without using modCount. You can take a look at how ArrayDeque achieves this in the source code.
CopyOnWriteArrayList and CopyOnWriteArraySet do need to use modCount because they are thread-safe collections and return fail-safe iterators. There are other classes with the Concurrent prefix, which suggest to me they would not define modCount, because their iterators should be fail-safe.
Why modCount?
The modCount fields are used in Java to implement fail-fast iterators. A fail-fast iterator is intended to help developers discover bugs that they can introduce when mutating collections while iterating over them. A common case of this happening can be seen in the following code example.
@Test
public void howToEncounterAConcurrentModificationExceptionInArrayList()
{
List<Integer> integers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
Assertions.assertThrows(
ConcurrentModificationException.class,
() -> {
for (Integer each : integers)
{
if (each % 2 == 0)
{
integers.remove(each);
}
}
});
}In this example, we create and iterate over a List of integers from one to five, and remove all of the even numbers from the list while iterating over it. Using ArrayList, a ConcurrentModificationException is thrown.
There are multiple ways for a developer to fix this bug. The classic way is to use an Iterator explicitly with iterator.remove(). Using an Iterator directly in a loop is slightly more complicated than using an enhanced for loop.
@Test
public void howToAvoidConcurrentModificationExceptionInArrayList1()
{
List<Integer> integers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
for (Iterator<Integer> iterator = integers.iterator(); it.hasNext();)
{
Integer each = iterator.next();
if (each % 2 == 0)
{
iterator.remove();
}
}
Assertions.assertEquals(List.of(1, 3, 5), integers);
}We could also fix this bug by copying the integer List to a separate result List before iterating over it. We mutate the result List instead.
@Test
public void howToAvoidConcurrentModificationExceptionInArrayList2()
{
List<Integer> integers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
List<Integer> result = new ArrayList<>(integers);
for (Integer each : integers)
{
if (each % 2 == 0)
{
result.remove(each);
}
}
Assertions.assertEquals(List.of(1, 3, 5), result);
}In more modern versions of Java (Java 8+), we could simply use the Stream filter method with a negated Predicate (see !=).
@Test
public void howToAvoidConcurrentModificationExceptionInArrayList3()
{
List<Integer> integers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
List<Integer> result = integers.stream()
.filter(each -> each % 2 != 0)
.toList();
Assertions.assertEquals(List.of(1, 3, 5), result);
}We could also use the Collection.removeIf with a Predicate.
@Test
public void howToAvoidConcurrentModificationExceptionInArrayList4()
{
List<Integer> integers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
integers.removeIf(each -> each % 2 ==0);
Assertions.assertEquals(List.of(1, 3, 5), integers);
}There are other bugs modCount can help find, but the bug above is a classic bug that most developers learn in the early days of first programming in Java. I first learned about this “mutating while iterating bug” in Smalltalk. Smalltalk did not provide fail-fast iterators. Developers got to learn about how not to do this by reading the Smalltalk FAQ or stepping through with the debugger when code was not working as expected. I stopped introducing this bug after the first few times I encountered in in Smalltalk.
What if modCount didn’t exist?
How would we protect developers from hurting themselves by mutating and iterating over non-concurrent collections? We could save an incalculable number of increments and comparisons of modCount by plastering the following annotation and corresponding JavaDoc at the top of each non-concurrent Collection in Java.
@NotConcurrent
public class EveryNonConcurrentJavaCollection {
/**
WARNING: DO NOT ITERATE OVER AND MUTATE A NON-CONCURRENT COLLECTION
AT THE SAME TIME!!!
Example:
List<Integer> integers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// HERE WE ITERATE
for (Integer each : integers)
{
if (each % 2 == 0)
{
// HERE WE MUTATE THE LIST WHILE ITERATING OVER IT.
// HERE BE DRAGONS. DRACARYS!!!
// YOU ARE NOW A PILE OF ASH.
integers.remove(each);
}
}
REPEAT 1,000,000 TIMES: DO NOT WRITE CODE LIKE THIS!!!
**/
We are charged a dual complexity and performance tax in ArrayList, HashSet, HashMap, and the other Java Collection types that use modCount in their code paths to protect us from running into common programming problems.
The performance tax of modCount is levied while modifying and iterating over collections. The performance tax of modCount, is very small and probably not likely to result in a noticeable improvement for most applications, but it may be measurable and noticeable in some JMH micro-benchmarks.
I decided to write some JMH benchmarks, even though I generally do not like comparing and sharing them because they take a lot of time to run and are mostly meaningless at this level when considering and addressing application performance bottlenecks.
The following chart shows the difference between using java.util.ArrayList (has modCount) and Eclipse Collections FastList (does not have modCount) to add 100 elements to a collection and then iterate over it using Collection.forEach, an indexed for loop, and an enhanced for loop. The unit of measure is operations per millisecond, which means bigger is better. Note: all of these are extremely fast, as they are measured in milliseconds.
I was expecting a much less noticeable performance difference. I would have expected more along the lines of perhaps a 10–30% improvement using FastList when compared to ArrayList because of the cost of incrementing modCount. There is either something wrong with my benchmark or something else is going on in the add method of ArrayList that is not happening in FastList. I hope it is my benchmark. There is a delegated method call to another private add method. The following screenshot shows both add methods in ArrayList. The JavaDoc in the private add method is highly suspect.
I think modCount is possibly complicating what should be completely inlined code in a single add method. The performance cost observed in my benchmark can’t possibly be from modCount alone. Perhaps if there is any benefit to me having spent the time researching modCount, spotting a potential performance issue in the add method of ArrayList will make my time worth it.
I don’t trust my own benchmarks, so here is the source code. Try them yourself on your own hardware. Let me know if you spot any obvious issues. I ran them using Java 25 (Azul Zulu) on my MacBook Pro M2 Max with 12 cores and 96gb RAM. YMMV.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.collections.api.tuple.Pair;
import org.eclipse.collections.api.tuple.primitive.IntObjectPair;
import org.eclipse.collections.impl.Counter;
import org.eclipse.collections.impl.list.mutable.FastList;
import org.eclipse.collections.impl.tuple.Tuples;
import org.eclipse.collections.impl.tuple.primitive.PrimitiveTuples;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(2)
@Warmup(iterations = 20, time = 2)
@Measurement(iterations = 10, time = 2)
public class ModCountVsNoModCountBenchmark
{
private static final int SIZE = 100;
@Benchmark
public List<String> modCountAdd()
{
List<String> arrayList = new ArrayList<>(SIZE);
for (int i = 0; i < SIZE; i++)
{
arrayList.add("");
}
return arrayList;
}
@Benchmark
public List<String> noModCountAdd()
{
List<String> fastList = new FastList<>(SIZE);
for (int i = 0; i < SIZE; i++)
{
fastList.add("");
}
return fastList;
}
@Benchmark
public Pair<Counter, List<String>> modCountAddForEach()
{
List<String> arrayList = new ArrayList<>(SIZE);
for (int i = 0; i < SIZE; i++)
{
arrayList.add("");
}
Counter counter = new Counter();
arrayList.forEach(each -> counter.add(each.length() + 1));
return Tuples.pair(counter, arrayList);
}
@Benchmark
public Pair<Counter, List<String>> noModCountAddForEach()
{
List<String> fastList = new FastList<>(SIZE);
for (int i = 0; i < SIZE; i++)
{
fastList.add("");
}
Counter counter = new Counter();
fastList.forEach(each -> counter.add(each.length() + 1));
return Tuples.pair(counter, fastList);
}
@Benchmark
public IntObjectPair<List<String>> modCountAddEnhancedFor()
{
List<String> arrayList = new ArrayList<>(SIZE);
for (int i = 0; i < SIZE; i++)
{
arrayList.add("");
}
int counter = 0;
for (String each : arrayList)
{
counter += each.length() + 1;
}
return PrimitiveTuples.pair(counter, arrayList);
}
@Benchmark
public IntObjectPair<List<String>> noModCountAddEnhancedFor()
{
List<String> fastList = new FastList<>(SIZE);
for (int i = 0; i < SIZE; i++)
{
fastList.add("");
}
int counter = 0;
for (String each : fastList)
{
counter += each.length() + 1;
}
return PrimitiveTuples.pair(counter, fastList);
}
@Benchmark
public IntObjectPair<List<String>> modCountAddIndexed()
{
List<String> arrayList = new ArrayList<>(SIZE);
for (int i = 0; i < SIZE; i++)
{
arrayList.add("");
}
int counter = 0;
final int localSize = arrayList.size();
for (int i = 0; i < localSize; i++)
{
String each = arrayList.get(i);
counter += each.length() + 1;
}
return PrimitiveTuples.pair(counter, arrayList);
}
@Benchmark
public IntObjectPair<List<String>> noModCountAddIndexed()
{
List<String> fastList = new FastList<>(SIZE);
for (int i = 0; i < SIZE; i++)
{
fastList.add("");
}
int counter = 0;
final int localSize = fastList.size();
for (int i = 0; i < localSize; i++)
{
String each = fastList.get(i);
counter += each.length() + 1;
}
return PrimitiveTuples.pair(counter, fastList);
}
}I ran the same benchmarks for 1,000,000 element lists. These were the results. The unit of measure was increased to Operations per second, instead of millisecond. In the code above just change the size to 1_000_000 and the TimeUnit to SECONDS.
The problem with modCount
The complexity tax of modCount is much greater than the performance tax, and is the reason why I decided to write this blog. The code in the Java Collections framework has to deal with modCount in many methods as illustrated in the two mind maps above. This code complicates designing, adding, and testing new features to the Java Collections Framework in addition to levying the always-on performance tax on every usage of modCount in every Java application. The purpose of modCount is to help developers find bugs they introduce by using non-concurrent collections incorrectly. This is a development time benefit. The modCount variable serves no purpose in production code. It is not monitored or reported on. It’s purpose is to prevent common bugs from going undiscovered and winding up in production.
The complexity cost of modCount is passed on to every developer who reads and debugs the code in the Java Collections and Streams framework. The code in Java Collections and Streams should be as simple as possible, and not any simpler. I do not believe it is currently as simple as it should be. The modCount variable is adding a long-term complexity debt to the Java Collections Framework which keeps increasing every time a new behavior is introduced to the standard library that requires incrementing or comparing modCount.
Eclipse Collections benefits from not extending AbstractList, and not using modCount in its non-concurrent collections. Non-concurrent collections should be used with care. Eclipse Collections does not provide fail-fast iterators… it provides fast iterators. Since Eclipse Collections has drop-in replacements for JDK types like ArrayList, HashSet, and HashMap, if you’re certain that you do not have any bugs in the non-concurrent collections code you have written (verified and supported with tests), you should be able to switch to Eclipse Collections and possibly get a minor speedup, with code which may also be easier to read and understand.
While writing this blog, I experimented implementing a FailOnSizeChangeIterator for Eclipse Collections types. I used a slightly less reliable method than modCount, that still catches the most common bug found by fail-fast iterators… removing from a collection while iterating over it. I record and compare the Collection.size() before and while iterating.
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.function.Consumer;
public class FailOnSizeChangeIterator<T> implements Iterator<T>
{
private int expectedSize;
private Collection<T> collection;
private Iterator<T> iterator;
public FailOnSizeChangeIterator(Collection<T> collection)
{
this.collection = collection;
this.expectedSize = collection.size();
this.iterator = collection.iterator();
}
private void checkSizeChange()
{
if (expectedSize != collection.size())
{
throw new ConcurrentModificationException();
}
}
@Override
public boolean hasNext()
{
this.checkSizeChange();
return iterator.hasNext();
}
@Override
public T next()
{
this.checkSizeChange();
return iterator.next();
}
@Override
public void remove()
{
this.checkSizeChange();
iterator.remove();
this.expectedSize = collection.size();
}
@Override
public void forEachRemaining(Consumer<? super T> action)
{
this.checkSizeChange();
this.iterator.forEachRemaining(action);
}
}I wrote a test which fails reliably with a ConcurrentModification exception, using FailFastIterator with a FastList.
@Test
public void howToEncounterAFailFastExceptionInFastList()
{
FastList<Integer> integers = new FastList<>(List.of(1, 2, 3, 4, 5));
Assertions.assertThrows(
ConcurrentModificationException.class,
() -> {
for (Iterator<Integer> it = new FailOnSizeChangeIterator<>(integers);
it.hasNext(); )
{
Integer each = it.next();
if (each % 2 == 0)
{
integers.remove(each);
}
}
});
}This approach is less reliable than modCount, because modCount can be used to capture changes to a collection that do not impact size, like sorting. I checked ArrayList, and it does increment modCount on sort, but does not increment modCount on set. It also increments modCount on replaceAll, so would seem to favor incrementing modCount on bulk behaviors that do not change size.
What’s in this blog for you?
I wrote this blog because I wanted to explore and learn more about modCount. We made the decision long ago not to extend AbstractList in Eclipse Collections, and I wanted to discover if there is something we are missing in Eclipse Collections by not using it. I don’t think there is, but I learned much more than I expected. I did not expect modCount to be so invasive.
As usual, mind maps proved useful at taking something very complex, like understanding the complexity of modCount usage, and putting in in a single diagram. I hope this proves useful for developers who add behavior to the Java Collections Framework in OpenJDK. This is the map of all the places you need to think about not just testing for behavior, but testing for concurrent behavior being applied to non-concurrent collections.
The lesson in here for the reader is that sometimes when we go exploring a tangent like modCount, we learn more than we might expect. I’ve tried to share some of what I learned along the way. I wrote, ran, and ultimately threw away a bunch of JMH benchmarks that I tested. The most compelling benchmark was the one that included add. I thought the results of that were interesting, confusing, and maybe worth looking into. As I noted, the difference may not be directly caused by modCount, but impacted by the complexity of including modCount. The benchmarks that measured the performance impact of using streams and and parallel streams with different types that have and don’t have modCount were less compelling.
Recommendation: The best way to get away from the complexity and cost of modCount and have both safe and performant code is to use immutable collections.
If you don’t like the complexity and always-on performance tax of modCount, you can use Eclipse Collections. If you depend regularly on the fail-fast behavior to protect yourself from yourself, you might want to stick with the fail-fast iterator approach in the JDK collections. Switch to the Eclipse Collections types when you are comfortable that you have good tests and have not introduced any bugs.
Thank you for reading!
I am the creator of and committer for the Eclipse Collections OSS project, which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions. I am the author of the book, Eclipse Collections Categorically: Level up your programming game.
