{"record":{"id":"6e5227f6d394a1dc","repo":"oracle/graal","slug":"too-many-capture-groups","errorCode":null,"errorMessage":"too many capture groups","messagePattern":"too many capture groups","errorType":"exception","errorClass":"UnsupportedRegexException","httpStatus":null,"severity":"error","filePath":"regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/Counter.java","lineNumber":91,"sourceCode":"        count -= i;\n        return ret;\n    }\n\n    public static class ThresholdCounter extends Counter {\n\n        private final int max;\n        private final String errorMsg;\n\n        public ThresholdCounter(int max, String errorMsg) {\n            this.max = max;\n            this.errorMsg = errorMsg;\n        }\n\n        @Override\n        public int inc(int i) {\n            final int ret = super.inc(i);\n            if (getCount() > max) {\n                throw new UnsupportedRegexException(errorMsg);\n            }\n            return ret;\n        }\n    }\n\n    public static class ThreadSafeCounter extends Counter {\n\n        @Override\n        public int inc() {\n            int c = count;\n            if (c < Integer.MAX_VALUE) {\n                count = c + 1;\n            }\n            return count;\n        }\n\n        @Override\n        public int inc(int i) {","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/tregex/parser/Counter.java#L73-L109","documentation":"Thrown by Counter.ThresholdCounter.inc with message 'too many capture groups' — instantiated as RegexAST.groupCount, capped at TRegexOptions.TRegexMaxNumberOfCaptureGroups (Short.MAX_VALUE = 32767). Group numbers are stored in a short across the AST and engine data structures, so declaring group number 32768 aborts parsing.","triggerScenarios":"Compiling a pattern with more than 32767 capturing groups, e.g. a generated pattern that wraps every element of a large list in parentheses ((w1)|(w2)|...|(w40000)).","commonSituations":"Keyword-search patterns built by wrapping each word in a capture group to learn which one matched; generated extraction patterns from schemas or templates; word lists that grow past the threshold over time.","solutions":["Use non-capturing groups (?:w1|w2|...) and determine the matched word by re-inspecting the matched text (e.g. a Map lookup).","Split into several patterns each below the cap and run them in sequence.","Replace the regex with a keyword-matching structure (Aho-Corasick, trie, HashSet) when you only need to know which literal matched.","Add a generation-time guard counting '(' to fail before compiling."],"exampleFix":"// before\nString pattern = words.stream().map(w -> \"(\" + Pattern.quote(w) + \")\").collect(Collectors.joining(\"|\"));\n\n// after\nString pattern = \"(?:\" + words.stream().map(Pattern::quote).collect(Collectors.joining(\"|\")) + \")\";\nString matched = m.group(); // then map back to the word via a Map<String,String>","handlingStrategy":"validation","validationCode":"int groups = 0; for (int i = 0; i + 1 < pattern.length(); i++) { if (pattern.charAt(i) == '(' && pattern.charAt(i + 1) != '?') groups++; } if (groups > 32_000) throw new IllegalArgumentException(\"too many capture groups (max 32767)\");","typeGuard":"null","tryCatchPattern":"try { compile(pattern); } catch (UnsupportedRegexException e) { if (e.getMessage().contains(\"capture groups\")) { /* switch groups to non-capturing and recover matched text via lookup */ } }","preventionTips":["Default to (?:...) and add capturing groups only for values you actually read.","Recover 'which alternative matched' via a Map on m.group() instead of one group per branch.","Enforce a capture-group budget in pattern generators."],"tags":["regex","tregex","capture-groups","parser","limit-exceeded"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}