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

  1. Repair or re-fetch the truncated input file
  2. If slicing text before lexing, cut on code-point boundaries (e.g. offsetByCodePoints), never on raw char indexes
  3. 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

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

Related errors


AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14). Data as JSON: /api/errors/50d786f16cd92c78. Report an issue: GitHub.