oracle/graal · error · ConcurrentModificationException
NodeBitMap was modified between the calls to hasNext() and n
Error message
NodeBitMap was modified between the calls to hasNext() and next()
What it means
NodeBitMap's iterator resolves the 'current node' in hasNext() and hands it out in next(). Between those two calls the iterator re-checks currentNode.isAlive(); if the node was deleted (or the graph otherwise changed), it throws ConcurrentModificationException. This is a fail-fast guard against mutating the graph while iterating a NodeBitMap, since the bitmap is indexed by node id and mutation invalidates that mapping.
Source
Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/NodeBitMap.java:265
currentNodeId = -1;
}
}
@Override
public boolean hasNext() {
if (currentNode == null && currentNodeId >= 0) {
forward();
}
return currentNodeId >= 0;
}
@Override
public Node next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (!currentNode.isAlive()) {
throw new ConcurrentModificationException("NodeBitMap was modified between the calls to hasNext() and next()");
}
Node result = currentNode;
currentNode = null;
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
@Override
public Iterator<Node> iterator() {
return new MarkedNodeIterator();
}View on GitHub (pinned to a66e9ccd1d)
Solutions
- Collect the nodes first (e.g. into an ArrayList) and mutate the graph in a second pass after iteration completes.
- Use graph.getNodes() iteration semantics or NodeIterable snapshots that tolerate mutation where offered.
- Mark nodes in another structure (e.g. a work list) instead of deleting during NodeBitMap iteration.
- If you must delete while iterating, use iterator patterns designed for it (explicit while loop over NodeIterable with next() only, never hasNext()+delete+next()).
Example fix
// before
for (Node n : bitMap) {
if (shouldRemove(n)) {
n.replaceAndDelete(replacement); // may kill node between hasNext/next
}
}
// after
List<Node> toProcess = new ArrayList<>();
for (Node n : bitMap) {
if (shouldRemove(n)) toProcess.add(n);
}
for (Node n : toProcess) {
n.replaceAndDelete(replacement);
} Defensive patterns
Strategy: try-catch
Validate before calling
// structural prevention: snapshot before mutating List<Node> snapshot = new ArrayList<>(); for (Node n : bitMap) snapshot.add(n); // now mutate freely using snapshot
Try / catch
try {
Node n = it.next();
} catch (ConcurrentModificationException e) {
// graph changed mid-iteration: abandon iterator, snapshot, and restart
List<Node> snapshot = new ArrayList<>();
bitMap.iterator().forEachRemaining(snapshot::add); // may itself fail; rebuild from graph instead
} Prevention
- Adopt a strict two-pass discipline: read/collect during NodeBitMap iteration, mutate after.
- Prefer iterator patterns that tolerate deletion when Graal offers them for your iterable.
- In tests, run with assertions on and add mutation-during-iteration cases to catch this early.
When it happens
Trigger: Calling iterator.hasNext() then deleting the current node (directly or via a replacing transform) before calling next(). Loops like `for (Node n : bitMap) { n.replaceAndDelete(...); }` where the body mutates the graph. Any node deletion between hasNext() and next() on a NodeBitMap iterator.
Common situations: Custom phases that iterate a NodeBitMap (e.g. reachable-node sets, visited sets) and replace/delete nodes in the same loop. Debug passes that clean up nodes while walking a computed set. Porting iteration code from snapshot-based collections to NodeBitMap.
Related errors
- Graph was permanetly frozen.
- accessing node id in %s across %d graph compression%s
- this node is not alive:
- unknown verbosity:
- Could not load Graal NodeClass TYPE field for
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/00276cdbef4f5e14.
Report an issue: GitHub.