google/ExoPlayer · error · NumberFormatException
Invalid UTF-8 sequence continuation byte: {value}
Error message
Invalid UTF-8 sequence continuation byte: {value} What it means
Same UTF-8 code-point reader in ParsableByteArray, thrown when a CONTINUATION byte fails the (x & 0xC0) != 0x80 test: after a valid multi-byte lead byte, the following bytes must be 10xxxxxx continuation bytes, and one is not. The stream is therefore not valid UTF-8 at this position — truncated multi-byte character, single-byte encoding misread, or corrupted data. Note the message misleadingly prints the lead byte 'value', not the offending continuation byte.
Source
Thrown at library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java:595
// find the high most 0 bit
for (int j = 7; j >= 0; j--) {
if ((value & (1 << j)) == 0) {
if (j < 6) {
value &= (1 << j) - 1;
length = 7 - j;
} else if (j == 7) {
length = 1;
}
break;
}
}
if (length == 0) {
throw new NumberFormatException("Invalid UTF-8 sequence first byte: " + value);
}
for (int i = 1; i < length; i++) {
int x = data[position + i];
if ((x & 0xC0) != 0x80) { // if the high most 0 bit not 7th
throw new NumberFormatException("Invalid UTF-8 sequence continuation byte: " + value);
}
value = (value << 6) | (x & 0x3F);
}
position += length;
return value;
}
/**
* Reads a UTF byte order mark (BOM) and returns the UTF {@link Charset} it represents. Returns
* {@code null} without advancing {@link #getPosition() position} if no BOM is found.
*/
@Nullable
public Charset readUtfCharsetFromBom() {
if (bytesLeft() >= 3
&& data[position] == (byte) 0xEF
&& data[position + 1] == (byte) 0xBB
&& data[position + 2] == (byte) 0xBF) {
position += 3;View on GitHub (pinned to dd430f7053)
Solutions
- Serve/decode with the declared charset: read the Content-Type charset header and use Charset with it rather than assuming UTF-8
- Ensure complete reads: use readFully/readString that respects the declared length instead of scanning until a delimiter that can split code points
- Repair at the source: re-encode subtitle files as valid UTF-8 (iconv/editor) — this is data corruption, not a library quirk
- Catch NumberFormatException per cue/tag and skip the malformed entry to keep playback alive
Example fix
// before
byte[] webVttBytes = dataSource.readAltered();
String line = new ParsableByteArray(webVttBytes).readStringUntil('\n'); // splits emoji
// after
ParsableByteArray p = new ParsableByteArray(webVttBytes);
try {
String line = p.readStringUntil('\n');
} catch (NumberFormatException e) {
Log.w(TAG, "Invalid UTF-8 in subtitle, skipping cue", e);
} Defensive patterns
Strategy: try-catch
Validate before calling
// before scanning, sanity-check the region decodes: e.g. CharsetDecoder with REPORT CharsetDecoder d = StandardCharsets.UTF_8.newDecoder(); // decode buffer.slice() and catch CharacterCodingException first
Try / catch
try { s = p.readStringUntil(delim); } catch (NumberFormatException e) { /* skip malformed cue/tag, keep playing */ } Prevention
- Ensure buffers are complete (use readFully) so multi-byte chars are never split
- Honor declared charsets from headers; transcode source files to UTF-8
- Unit-test parsers against emoji/CJK/truncation corpora
When it happens
Trigger: A multi-byte code point (e.g. emoji, CJK, accented letters) is cut mid-character because the buffer ends inside it (truncated read) or the delimiter split it; data is actually Latin-1/UTF-16/GBK being decoded as UTF-8; or bytes were damaged in transit. Any of these makes the byte after the lead byte fail the 10xxxxxx mask.
Common situations: Subtitles (WebVTT/SRT) with CJK/emoji content served with wrong charset headers; playlists truncated mid-tag by a flaky proxy; DVR/live edge recordings cutting frames mid-character; double-encoding (UTF-8 bytes re-encoded as UTF-8) mangling continuation bytes.
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-8 sequence first byte: {value}
- Cannot read frame at position ${lastDecodePosition}
- Invalid index {index}, size is {size}
- Top bit not zero: {result}
- Unsupported charset: %s
AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14).
Data as JSON: /api/errors/8d0fd222ae2e6b75.
Report an issue: GitHub.