{"record":{"id":"609ca2f778038da0","repo":"oracle/graal","slug":"extended-grapheme-cluster-boundaries-are-not-suppo","errorCode":null,"errorMessage":"Extended grapheme cluster boundaries are not supported","messagePattern":"Extended grapheme cluster boundaries are not supported","errorType":"exception","errorClass":"UnsupportedRegexException","httpStatus":null,"severity":"error","filePath":"regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/java/JavaRegexLexer.java","lineNumber":777,"sourceCode":"            if (c2 == '\\\\' && (atEnd() || isEscapeCharClass(curChar()))) {\n                throw syntaxError(JavaErrorMessages.ILLEGAL_CHARACTER_RANGE, ErrorCode.InvalidCharacterClass);\n            }\n            int upper = parseCharClassAtomCodePoint(c2);\n            if (upper < ch) {\n                throw syntaxError(JavaErrorMessages.ILLEGAL_CHARACTER_RANGE, ErrorCode.InvalidCharacterClass);\n            }\n            return CodePointSet.create(ch, upper);\n        } else {\n            return CodePointSet.create(ch);\n        }\n    }\n\n    @Override\n    protected Token parseCustomEscape(char c) {\n        switch (c) {\n            case 'b' -> {\n                if (consumingLookahead(\"{g}\")) {\n                    throw new UnsupportedRegexException(\"Extended grapheme cluster boundaries are not supported\");\n                }\n                return Token.createWordBoundary();\n            }\n            case 'B' -> {\n                return Token.createNonWordBoundary();\n            }\n            case 'k' -> {\n                if (atEnd()) {\n                    handleUnfinishedEscape();\n                }\n                if (consumeChar() != '<') {\n                    throw syntaxError(JavaErrorMessages.NAMED_CAPTURE_GROUP_REFERENCE_MISSING_BEGIN, ErrorCode.InvalidBackReference);\n                }\n                String groupName = javaParseGroupName();\n                // backward reference\n                if (namedCaptureGroups.containsKey(groupName)) {\n                    return Token.createBackReference(getSingleNamedGroupNumber(groupName), false);\n                }","sourceCodeStart":759,"sourceCodeEnd":795,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/regex/src/com.oracle.truffle.regex/src/com/oracle/truffle/regex/flavor/java/JavaRegexLexer.java#L759-L795","documentation":"Thrown by the Java-flavor lexer when a pattern uses \\b{g}, the extended grapheme cluster boundary matcher that java.util.regex supports since JDK 21. TRegex compiles patterns to NFAs/DFAs and has no representation for Unicode grapheme cluster boundaries (they require UAX #29 segmentation, not a regular construction). The lexer detects the literal sequence '{g}' right after \\b and rejects the pattern at parse time with UnsupportedRegexException.","triggerScenarios":"Compiling a Java-flavor regex (e.g. via RegexCompiler for the Java flavor or GraalVM/Espresso/Truffle host regex) whose pattern contains \\b{g} or \\B{g}-style grapheme boundary syntax: parsing '\\b{g}word\\b{g}' reaches parseCustomEscape case 'b', the consumingLookahead(\"{g}\") succeeds, and the exception is thrown immediately.","commonSituations":"Porting a regex written for JDK 21+ java.util.regex into a GraalVM guest language or TRegex-backed engine; copy-pasting Unicode-aware patterns from Stack Overflow or newer JDK docs; upgrading a codebase from Java 8-17 syntax to Java 21 syntax while the regex engine underneath is TRegex, not java.util.regex.","solutions":["Replace \\b{g} with a plain word boundary \\b if combining-mark handling is not essential","Approximate a grapheme boundary with (?:\\P{M}\\p{M}*) or match whole clusters with (?:\\P{M}\\p{M}*) instead of relying on boundary syntax","Pre-process the pattern string to strip or rewrite \\b{g} / \\B{g} before handing it to the TRegex-based engine","Route patterns that need true UAX #29 semantics to java.util.regex via java.util.regex.Pattern on a non-host engine, if the embedding allows choosing an engine"],"exampleFix":"// before\nString p = \"\\\\b{g}\\\\w+\\\\b{g}\";\n\n// after\nString p = \"\\\\b\\\\w+\\\\b\";","handlingStrategy":"try-catch","validationCode":"boolean usesGraphemeBoundary(String pattern) {\n    return pattern.contains(\"\\\\b{g}\") || pattern.contains(\"\\\\B{g}\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    RegexObject re = compileJavaFlavor(pattern);\n} catch (UnsupportedRegexException e) {\n    // reason mentions grapheme cluster boundaries; rewrite pattern or switch engine\n    pattern = pattern.replace(\"\\\\b{g}\", \"\\\\b\");\n}","preventionTips":["When porting JDK 21+ regexes, scan for \\b{g}/\\B{g} before compiling on TRegex","Keep a list of known-unsupported Java-flavor constructs (\\X, \\G, (?>, possessive quantifiers) in your pattern linting"],"tags":["regex","java-flavor","unicode","grapheme-cluster","unsupported-feature"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}