oracle/graal · error · UnsupportedRegexException

End of previous match boundary matcher is not supported

Error message

End of previous match boundary matcher is not supported

What it means

Thrown by the Java-flavor lexer for \G, the 'end of previous match' anchor. \G is stateful: it depends on where the previous invocation of the matcher ended, and TRegex executors are stateless per-match compiled automata with no notion of a resumable match position handed back into the pattern, so case 'G' rejects it with UnsupportedRegexException.

Source

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

            case 'k' -> {
                if (atEnd()) {
                    handleUnfinishedEscape();
                }
                if (consumeChar() != '<') {
                    throw syntaxError(JavaErrorMessages.NAMED_CAPTURE_GROUP_REFERENCE_MISSING_BEGIN, ErrorCode.InvalidBackReference);
                }
                String groupName = javaParseGroupName();
                // backward reference
                if (namedCaptureGroups.containsKey(groupName)) {
                    return Token.createBackReference(getSingleNamedGroupNumber(groupName), false);
                }
                throw syntaxError(JavaErrorMessages.unknownGroupReference(groupName), ErrorCode.InvalidBackReference);
            }
            case 'R' -> {
                return Token.createLineBreak();
            }
            case 'X' -> throw new UnsupportedRegexException("Grapheme clusters are not supported");
            case 'G' -> throw new UnsupportedRegexException("End of previous match boundary matcher is not supported");
            case 'Q' -> {
                int start = position;
                int end = pattern.indexOf("\\E", start);
                if (end < 0) {
                    end = pattern.length();
                    position = end;
                } else {
                    position = end + 2;
                }
                return Token.createLiteralString(start, end);
            }
        }
        return null;
    }

    @Override
    protected int parseCustomEscapeChar(char c, boolean inCharClass) {
        switch (c) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Remove \G and enforce contiguity in the calling code: check matcher.start() == expectedPos after each match instead of anchoring in the pattern
  2. Replace \G with \A when you actually only ever match from the absolute beginning of a sliced input region
  3. Use java.util.regex directly for tokenizer patterns that depend on \G, if the embedding permits choosing the engine

Example fix

// before
String p = "\\G\\w+"; // tokenizer relying on \\G

// after
String p = "\\w+";
// enforce contiguity in the loop:
// if (m.end() != pos) break; else pos = m.end();
Defensive patterns

Strategy: try-catch

Validate before calling

boolean usesGAnchor(String pattern) {
    return pattern.contains("\\G");
}

Try / catch

try {
    RegexObject re = compileJavaFlavor(pattern);
} catch (UnsupportedRegexException e) {
    if (pattern.contains("\\G")) {
        pattern = pattern.replace("\\G", ""); // enforce contiguity in the match loop instead
    }
}

Prevention

When it happens

Trigger: Compiling a Java-flavor pattern containing \G, typically for tokenizing input in a loop, e.g. "\\G\\w+" or "\\Gitem(?:,|\\z)". parseCustomEscape case 'G' throws immediately at lex time.

Common situations: Ported tokenizer loops from java.util.regex, PHP preg_match_all, or .NET Regex where \G enforces contiguous token matching; developers moving tokenizer code into a GraalVM guest language or any embedding backed by TRegex.

Related errors


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