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
- Reduce the number of capture groups (use '(?:...)' for groups whose positions you never read)
- Simplify alternations that mix many capturing branches; hoist common prefixes out of the capture groups
- If you do not need capture positions at all, compile in boolean-match mode or drop the groups entirely
- 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
- Keep capture-group counts low in DFA-compiled patterns
- Remember these DFA bailouts are normally invisible (automatic backtracking fallback) unless linear execution is forced
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
- too many branches in capture group tracking DFA
- Too much additional capture group tracking overhead
- too many parallel NFA states in one DFA state for bounded qu
- dependency cycle
- too many partial transitions
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/6ed0c73c20c2ab64.
Report an issue: GitHub.