Search code examples
javajava-streamjava-11

ConcurrentModificationException when using java streams without modification


I get a ConcurrentModificationException although the piece of code that is listed in the stacktrace doesn't do any modification:

Map<String, ProblemItem> problemItemsMap = getProblemItemsMap();
Optional<ProblemItem> findAny =
    problemItemsMap.values().stream()
    .filter(pi -> pi.getId().equals(id))
    .findAny();

I understand from this answer that it has to do with the HashMap. But as you can see, there is no add or remove or any other modification on the map at this location. I assume the problem occurs because of a modification somewhere else that just happen to be at the same time as the iteration.

The stacktrace looks like this:

 Uncaught exception in thread 'pool-10-thread-1'.java.util.ConcurrentModificationException: null
    at java.base/java.util.HashMap$ValueSpliterator.tryAdvance(HashMap.java:1698)
    at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:127)
    at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:502)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:488)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.findAny(ReferencePipeline.java:548)

So I have no clue where the modification takes place and it confuses me that the stacktrace shows the iteration and not the modification.

It's worth mentioning that this exception only occurs once in a while in my multi thread application.


Solution

  • EDIT: I suspect that what we are solving is a typical instance of the X-Y problem. Probably the whole code needs refactoring, as it probably does not make much sense working with a map which is constantly changing in the backrgound. What is the expected overall behaviour?


    It seems like there are bad guys around conspiring against your thread, modifying the map in the background :)

    1. The best solution, if you can change the code of getProblemItemsMap() or implement your own, would be to reimplement it completely so no one changes it while you use it.

    Anyway, from the business logic point of view, how is the behaviour of your code defined if the underlying map is constantly changing?

    1. Copy the map before use:
    problemItemsMap = Map.copyOf(getProblemItemsMap());
    

    The documentation says:

    Returns an unmodifiable Map containing the entries of the given Map. The given Map must not be null, and it must not contain any null keys or values. If the given Map is subsequently modified, the returned Map will not reflect such modifications.

    I am not sure how copyOf is implemented, maybe it will be able to catch a snapshot of the underlying map. I don't know how tolerant it is.