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

  1. Collect the nodes first (e.g. into an ArrayList) and mutate the graph in a second pass after iteration completes.
  2. Use graph.getNodes() iteration semantics or NodeIterable snapshots that tolerate mutation where offered.
  3. Mark nodes in another structure (e.g. a work list) instead of deleting during NodeBitMap iteration.
  4. 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

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


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/00276cdbef4f5e14. Report an issue: GitHub.