oracle/graal · error · UnsupportedRegexException

Independent non-capturing groups are not supported

Error message

Independent non-capturing groups are not supported

What it means

Thrown by the Java-flavor lexer for the atomic/independent group construct (?>...), which prevents backtracking into the group once it has matched. TRegex's compilation pipeline has no mechanism to seal a subexpression against backtracking (its executors either match purely or backtrack exhaustively), so parseCustomGroupBeginQ rejects the '>' after '(?' with UnsupportedRegexException.

Source

Thrown at regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/java/JavaRegexLexer.java:910

    @Override
    protected int parseCustomEscapeCharFallback(int c, boolean inCharClass) {
        // any non-alphabetic character can be used after an escape
        // digits are not accepted here since they should have been parsed as octal sequence or
        // backreference earlier
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
            throw syntaxError(JavaErrorMessages.ILLEGAL_ESCAPE_SEQUENCE, ErrorCode.InvalidEscape);
        }
        return c;
    }

    @Override
    protected Token parseCustomGroupBeginQ(char charAfterQuestionMark) {
        // TODO do we need to do something else than inline flags? check "Special constructs" in
        // documentation
        char c = charAfterQuestionMark;
        if (c == '>') {
            throw new UnsupportedRegexException("Independent non-capturing groups are not supported");
        }
        int firstCharPos = position;
        JavaFlags newFlags = getLocalFlags();
        while (JavaFlags.isValidFlagChar(c)) {
            newFlags = newFlags.addFlag(c);
            if (atEnd()) {
                throw handleUnfinishedGroupQ();
            }
            c = consumeChar();
        }
        if (c == '-') {
            if (atEnd()) {
                throw handleUnfinishedGroupQ();
            }
            c = consumeChar();
            while (JavaFlags.isValidFlagChar(c)) {
                newFlags = newFlags.delFlag(c);
                if (atEnd()) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Replace (?>X) with a plain non-capturing group (?:X) and accept the backtracking semantics difference
  2. If atomicity guarded against catastrophic backtracking, restructure the pattern (e.g. unroll loops, use negated character classes) to bound the search instead
  3. Verify correctness after the rewrite: (?:X)+ and (?>X)+ can produce different matches on partial-match inputs

Example fix

// before
String p = "(?>\\d+)\\.";

// after
String p = "(?:\\d+)\\.";
Defensive patterns

Strategy: validation

Validate before calling

boolean usesAtomicGroup(String pattern) {
    return pattern.contains("(?>");
}

Try / catch

try {
    RegexObject re = compileJavaFlavor(pattern);
} catch (UnsupportedRegexException e) {
    pattern = pattern.replace("(?>", "(?:"); // semantics differ: verify matches still correct
    re = compileJavaFlavor(pattern);
}

Prevention

When it happens

Trigger: Compiling any Java-flavor pattern containing (?> ... ), e.g. "(?>ab)+c" or possessive-style optimizations like "(?>\\d+)\\.". The lexer sees '(' then '?' then '>' in parseCustomGroupBeginQ and throws before parsing group contents.

Common situations: ReDoS-hardening guides recommend atomic groups; developers copy PCRE/Perl/.NET patterns using (?>...) for performance; patterns migrated from Rust/RE2-style possessive notation into a TRegex-backed engine.

Related errors


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