oracle/graal · error · UnsupportedRegexException
Grapheme clusters are not supported
Error message
Grapheme clusters are not supported
What it means
Thrown by the Java-flavor lexer when the pattern contains \X, the 'grapheme cluster' escape (any extended grapheme cluster, roughly one user-perceived character). TRegex has no AST node or NFA construction for unbounded grapheme clustering because it needs Unicode segmentation rather than a regular expression over code points, so case 'X' in parseCustomEscape rejects it outright with UnsupportedRegexException.
Source
Thrown at regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/java/JavaRegexLexer.java:801
}
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) {View on GitHub (pinned to a66e9ccd1d)
Solutions
- Replace \X with a base-plus-marks approximation such as (?:\P{M}\p{M}*)
- If only line-oriented matching was intended, use . with DOTALL or an explicit (?:\r\n|[\s\S]) alternation
- Do heavyweight grapheme segmentation with a dedicated Unicode library (ICU BreakIterator) outside the regex, matching each segment with a simple regex
Example fix
// before
String p = "\\X"; // one grapheme cluster
// after
String p = "(?:\\P{M}\\p{M}*)"; // base char + combining marks Defensive patterns
Strategy: try-catch
Validate before calling
boolean usesGraphemeEscape(String pattern) {
return pattern.contains("\\X");
} Try / catch
try {
RegexObject re = compileJavaFlavor(pattern);
} catch (UnsupportedRegexException e) {
pattern = pattern.replace("\\X", "(?:\\P{M}\\p{M}*)");
re = compileJavaFlavor(pattern);
} Prevention
- Avoid \X in patterns meant to run on TRegex; use (?:\P{M}\p{M}*) instead
- For exact grapheme segmentation use ICU BreakIterator outside the regex
When it happens
Trigger: Any Java-flavor pattern containing the two-character escape \X, e.g. compiling "\\X+" or "\\b\\X*\\b". The lexer hits case 'X' in parseCustomEscape and throws before any parser or DFA stage runs.
Common situations: Patterns ported from PCRE, Perl, Ruby, or .NET where \X is idiomatic for Unicode text segmentation; emoji/CJK processing regexes copied from other languages into a TRegex-backed Java engine; older patterns that used (?:\r\n|.) and get 'modernized' to \X.
Related errors
- Extended grapheme cluster boundaries are not supported
- End of previous match boundary matcher is not supported
- Literal escape not supported in this context
- Independent non-capturing groups are not supported
- possessive quantifiers are not supported
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/df85b285f2012b13.
Report an issue: GitHub.