oracle/graal · error · UnsupportedRegexException

too many capture group transitions

Error message

too many capture group transitions

What it means

Thrown by DFACaptureGroupTransitionBuilder when the number of a state's capture-group partial transitions exceeds Short.MAX_VALUE (32767). Partial-transition ids are stored as short in DFACaptureGroupLazyTransitionBuilder, so once getId() > Short.MAX_VALUE the builder refuses to continue and throws UnsupportedRegexException('too many capture group transitions').

Source

Thrown at regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/dfa/DFACaptureGroupTransitionBuilder.java:228

            DFAStateNodeBuilder successor = getTarget();
            DFACaptureGroupPartialTransition[] partialTransitions = new DFACaptureGroupPartialTransition[successor.getSuccessors().length];
            for (int i = 0; i < successor.getSuccessors().length; i++) {
                DFACaptureGroupTransitionBuilder successorTransition = (DFACaptureGroupTransitionBuilder) successor.getSuccessors()[i];
                partialTransitions[i] = createPartialTransition(successorTransition.getRequiredStates(), successorTransition.getRequiredStatesIndexMap(), compilationBuffer);
            }
            DFACaptureGroupPartialTransition transitionToFinalState = null;
            DFACaptureGroupPartialTransition transitionToAnchoredFinalState = null;
            if (successor.isUnAnchoredFinalState()) {
                NFAState src = successor.getUnAnchoredFinalStateTransition().getSource();
                transitionToFinalState = createPartialTransition(StateSet.create(dfaGen.getNfa(), src), StateSetToIntMap.create(src), compilationBuffer);
            }
            if (successor.isAnchoredFinalState()) {
                NFAState src = successor.getAnchoredFinalStateTransition().getSource();
                transitionToAnchoredFinalState = createPartialTransition(StateSet.create(dfaGen.getNfa(), src), StateSetToIntMap.create(src), compilationBuffer);
            }
            assert getId() >= 0;
            if (getId() > Short.MAX_VALUE) {
                throw new UnsupportedRegexException("too many capture group transitions");
            }
            lazyTransitionBuilder = new DFACaptureGroupLazyTransitionBuilder((short) getId(), partialTransitions, transitionToFinalState, transitionToAnchoredFinalState);
        }
        return lazyTransitionBuilder;
    }

    public static class PartialTransitionDebugInfo implements JsonConvertible {

        private DFACaptureGroupPartialTransition node;
        private final short[] resultToTransitionMap;

        public PartialTransitionDebugInfo(DFACaptureGroupPartialTransition node) {
            this(node, 0);
        }

        public PartialTransitionDebugInfo(int nResults) {
            this(null, nResults);
        }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Reduce the number of capture groups (use '(?:...)' for groups whose positions you never read)
  2. Simplify alternations that mix many capturing branches; hoist common prefixes out of the capture groups
  3. If you do not need capture positions at all, compile in boolean-match mode or drop the groups entirely
  4. Accept the backtracking fallback: do not force linear execution for these patterns

Example fix

// before
String p = "(a|b|c)(d|e|f)(g|h|i)(j|k|l)..."; // capturing every branch

// after
String p = "[abc][def][ghi][jkl]..."; // classes instead of capturing branches
Defensive patterns

Strategy: try-catch

Try / catch

try {
    return compileDfaExecutor(pattern);
} catch (UnsupportedRegexException e) {
    if (e.getReason().contains("capture group transitions")) {
        return compileWithFewerCaptureGroups(pattern); // rewrite (..) -> (?:..) and retry
    }
    throw e;
}

Prevention

When it happens

Trigger: Compiling a DFA-backed executor for a pattern whose capture-group tracking creates more than 32767 distinct partial transitions — typically dozens of capture groups combined with alternations or bounded quantifiers ({n,m}) that multiply transition combinations.

Common situations: Machine-generated matchers (log parsers, URI/route matchers, lexer specifications) with many capture groups; patterns translated from grammar tools where every rule captures; note that without forceLinearExecution this bailout is usually swallowed and triggers the backtracking fallback, so it mainly surfaces in forced-linear or eager-DFA configurations.

Related errors


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