antlr/antlr4 · error · IllegalStateException

Cannot serialize a transition to a removed state.

Error message

Cannot serialize a transition to a removed state.

What it means

Thrown by ATNSerializer while walking an ATN's transitions: a transition points at a state whose slot in atn.states is null (a state that was removed/never assigned). The ATN is a graph whose states are indexed in a list; serialization walks every state's outgoing edges and refuses to emit an edge to a null (removed) target. It signals a corrupted or internally inconsistent ATN, not a grammar-syntax problem.

Source

Thrown at runtime/Java/src/org/antlr/v4/runtime/atn/ATNSerializer.java:175

	}

	private void addEdges(int nedges, Map<IntervalSet, Integer> setIndices) {
		data.add(nedges);
		for (ATNState s : atn.states) {
			if ( s==null ) {
				// might be optimized away
				continue;
			}

			if (s.getStateType() == ATNState.RULE_STOP) {
				continue;
			}

			for (int i=0; i<s.getNumberOfTransitions(); i++) {
				Transition t = s.transition(i);

				if (atn.states.get(t.target.stateNumber) == null) {
					throw new IllegalStateException("Cannot serialize a transition to a removed state.");
				}

				int src = s.stateNumber;
				int trg = t.target.stateNumber;
				int edgeType = Transition.serializationTypes.get(t.getClass());
				int arg1 = 0;
				int arg2 = 0;
				int arg3 = 0;
				switch ( edgeType ) {
					case Transition.RULE :
						trg = ((RuleTransition)t).followState.stateNumber;
						arg1 = ((RuleTransition)t).target.stateNumber;
						arg2 = ((RuleTransition)t).ruleIndex;
						arg3 = ((RuleTransition)t).precedence;
						break;
					case Transition.PRECEDENCE:
						PrecedencePredicateTransition ppt = (PrecedencePredicateTransition)t;
						arg1 = ppt.precedence;

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Ensure the ANTLR jar used to generate the parser matches the runtime jar version exactly (e.g. both 4.13.1)
  2. If you build/modify ATNs programmatically, never remove states; keep atn.states fully populated and only add states via atn.addState so stateNumber stays consistent
  3. Re-generate the lexer/parser from the grammar with the current tool and re-serialize; do not reuse old serialized ATN data
  4. If you must remove a state, also remove/redirect every transition that targets it before serializing

Example fix

// before
atn.states.set(removedState.stateNumber, null); // leaves dangling transitions
new ATNSerializer(atn).serialize();
// after
// keep the ATN immutable; re-create it from the grammar instead of pruning states
ATNDeserializer d = new ATNDeserializer();
ATN cleanAtn = d.deserialize(ATNDeserializer.getSerializedAsString(oldAtn).chars());
new ATNSerializer(cleanAtn).serialize();
Defensive patterns

Strategy: validation

Validate before calling

boolean isSerializable(ATN atn) {
    for (ATNState s : atn.states) {
        if (s == null) continue;
        for (int i = 0; i < s.getNumberOfTransitions(); i++) {
            Transition t = s.transition(i);
            if (t.target == null || atn.states.get(t.target.stateNumber) == null) return false;
        }
    }
    return true;
}

Try / catch

catch (IllegalStateException e) { if (e.getMessage().contains("removed state")) { /* regenerate grammar/tool match, rebuild ATN */ } else throw e; }

Prevention

When it happens

Trigger: Calling new ATNSerializer(atn).serialize() (or ATNSerializer.getSerializedAsString/atn) on an ATN whose states list contains null entries, or on an ATN that was hand-built / programmatically mutated so a Transition.target.stateNumber indexes a null slot. Also seen when a deserialized ATN is edited (states removed) and then re-serialized, or when tool and runtime versions produce/consume incompatible ATN shapes.

Common situations: Mixing ANTLR tool version X with runtime version Y so the deserialized ATN is malformed. Custom code that builds or prunes ATN states (e.g. experimental optimizers, ATN-faking for testing). Serializing an ATN obtained from ATNDeserializer after mutating atn.states.

Related errors


AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14). Data as JSON: /api/errors/719793f41ba7a507. Report an issue: GitHub.