apple/pkl · error · ParserError
invalidUnicodeEscapeSequence
invalidUnicodeEscapeSequence
Error message
Invalid Unicode escape sequence `{text}`.
Valid Unicode escape sequences are {prefix}'{'0'}' to {prefix}'{'10FFFF'}' (1-6 hexadecimal characters). What it means
Pkl string escapes use the form `\u{XXXXXX}` with 1–6 hexadecimal digits (code points up to 10FFFF). The parser decodes the escape by parsing the hex substring between braces with Integer.parseInt(radix 16); if that fails (invalid characters, empty body, or a code point out of range caught as NumberFormatException) it throws invalidUnicodeEscapeSequence, echoing the full offending text and the escape prefix, at the token's span.
Source
Thrown at pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java:1664
case STRING_ESCAPE_QUOTE -> "\"";
case STRING_ESCAPE_BACKSLASH -> "\\";
case STRING_ESCAPE_TAB -> "\t";
case STRING_ESCAPE_RETURN -> "\r";
case STRING_ESCAPE_CONTINUATION -> "";
case STRING_ESCAPE_UNICODE -> parseUnicodeEscape(tk);
default -> throw new RuntimeException("Unreacheable code");
};
}
private String parseUnicodeEscape(FullToken tk) {
var text = tk.text(lexer);
var lastIndex = text.length() - 1;
var startIndex = text.indexOf('{', 2);
try {
var codepoint = Integer.parseInt(text.substring(startIndex + 1, lastIndex), 16);
return Character.toString(codepoint);
} catch (NumberFormatException e) {
throw new ParserError(
ErrorMessages.create("invalidUnicodeEscapeSequence", text, text.substring(0, startIndex)),
tk.span);
}
}
private String getCommonIndent(List<TempNode> nodes, Span span) {
var lastNode = nodes.get(nodes.size() - 1);
if (lastNode.token == null) {
throw new ParserError(
ErrorMessages.create("closingStringDelimiterMustBeginOnNewLine"), lastNode.span());
}
if (lastNode.token.token == Token.STRING_NEWLINE) return "";
var beforeLast = nodes.get(nodes.size() - 2);
if (beforeLast.token != null && beforeLast.token.token == Token.STRING_NEWLINE) {
var indent = getTrailingIndent(lastNode);
if (indent != null) {
return indent;
}View on GitHub (pinned to f3efcbfc9b)
Solutions
- Fix the escape to 1–6 hexadecimal digits inside braces, e.g. `\u{1F600}`
- Convert non-hex characters to valid hex digits (e.g. use `\u{41}` instead of `\u{G}`)
- Ensure the code point is ≤ 10FFFF; split surrogate pairs into single code points
- If migrating from Java/JSON, transform `\uXXXX` to Pkl's braced form `\u{XXXX}`
Example fix
// before
s = "\u{12G4}"
// after
s = "\u{12F4}" Defensive patterns
Strategy: validation
Validate before calling
function validPklUnicodeEscapes(src) {
return [...src.matchAll(/\\u\{([^}]*)\}/g)].every(([, hex]) =>
/^[0-9a-fA-F]{1,6}$/.test(hex) && parseInt(hex, 16) <= 0x10FFFF
);
}
// validate every \u{...} escape has 1-6 hex digits and value <= 10FFFF before parsing Type guard
null
Try / catch
try {
const result = parser.parse(source);
} catch (e) {
if (e.errorId === 'invalidUnicodeEscapeSequence') {
// correct the escape flagged in e.getMessage() at e.span
} else { throw e; }
} Prevention
- Convert \uXXXX (Java/JSON) escapes to Pkl's \u{XXXX} braced form
- Validate hex digits programmatically when generating escapes
- Emit code points, not surrogate pairs, and cap at U+10FFFF
When it happens
Trigger: Parsing a string containing a malformed `\u{...}` escape: non-hex characters inside the braces (`\u{12G4}`), nothing between the braces (`\u{}`), more than 6 hex digits (`\u{0010FFFF0}`), or a value beyond U+10FFFF such that Character.toString also fails.
Common situations: Copying `\uXXXX` escapes from JSON/Java (no braces) into Pkl; hand-typing escapes and mistyping a hex digit; generating escapes programmatically with a decimal code point instead of hex.
Related errors
- invalidLineContinuationEscapeSequence
- unexpectedCharacter
- unterminatedUnicodeEscapeSequence
- invalidLineContinuationEscapeSequence
- Cannot parse `%s` as number.
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/179dc10cd9dd7e22.
Report an issue: GitHub.