antlr/antlr4 · critical · RuntimeException
Invalid UTF-16 (dangling high surrogate at end of file)
Error message
Invalid UTF-16 (dangling high surrogate at end of file)
What it means
UnbufferedCharStream read a high surrogate as the final character of the input: the next read returned EOF, so the pair is incomplete. This is malformed UTF-16 (a dangling high surrogate at end of file) and the stream throws rather than buffer an unpaired surrogate.
Source
Thrown at runtime/Java/src/org/antlr/v4/runtime/UnbufferedCharStream.java:180
}
try {
int c = nextChar();
if (c > Character.MAX_VALUE || c == IntStream.EOF) {
add(c);
}
else {
char ch = (char) c;
if (Character.isLowSurrogate(ch)) {
throw new RuntimeException("Invalid UTF-16 (low surrogate with no preceding high surrogate)");
}
else if (Character.isHighSurrogate(ch)) {
int lowSurrogate = nextChar();
if (lowSurrogate > Character.MAX_VALUE) {
throw new RuntimeException("Invalid UTF-16 (high surrogate followed by code point > U+FFFF");
}
else if (lowSurrogate == IntStream.EOF) {
throw new RuntimeException("Invalid UTF-16 (dangling high surrogate at end of file)");
}
else {
char lowSurrogateChar = (char) lowSurrogate;
if (Character.isLowSurrogate(lowSurrogateChar)) {
add(Character.toCodePoint(ch, lowSurrogateChar));
}
else {
throw new RuntimeException("Invalid UTF-16 (dangling high surrogate");
}
}
}
else {
add(c);
}
}
}
catch (IOException ioe) {
throw new RuntimeException(ioe);View on GitHub (pinned to 7d5770395b)
Solutions
- Repair or re-fetch the truncated input file
- If slicing text before lexing, cut on code-point boundaries (e.g. offsetByCodePoints), never on raw char indexes
- Confirm the Reader charset matches the file (see error 47) so pairs are not created spuriously
Example fix
// before String chunk = input.substring(0, 1000); // may cut a surrogate pair CharStream cs = new UnbufferedCharStream(new StringReader(chunk)); // after int end = input.offsetByCodePoints(0, 1000); // code-point boundary CharStream cs = new UnbufferedCharStream(new StringReader(input.substring(0, end)));
Defensive patterns
Strategy: try-catch
Validate before calling
// check the tail of the string for a dangling high surrogate before lexing
String s = readAll(input);
if (!s.isEmpty() && Character.isHighSurrogate(s.charAt(s.length() - 1))) {
throw new IllegalArgumentException("input ends with a truncated surrogate pair");
} Try / catch
try { parse(stream); }
catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("dangling high surrogate"))
return Result.truncatedInput(); // treat as incomplete data, request re-upload
throw e;
} Prevention
- Slice strings on code-point boundaries (offsetByCodePoints)
- Detect truncated files (size checks, checksums) before parsing
- Keep surrogate pairs together in any pre-lexer text processing
When it happens
Trigger: Input whose last char is a lone high surrogate: truncated multi-byte character at EOF, a file cut mid-write, or single-byte encoding misread as UTF-16 so the final odd byte becomes half a pair.
Common situations: Truncated downloads or log files ending mid-character; pre-processing steps that slice strings at arbitrary offsets and cut a surrogate pair in half.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Invalid UTF-16 (low surrogate with no preceding high surroga
- Invalid UTF-16 (high surrogate followed by code point > U+FF
- Invalid UTF-16 (dangling high surrogate
- cannot consume EOF
- cannot consume EOF
AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14).
Data as JSON: /api/errors/50d786f16cd92c78.
Report an issue: GitHub.