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

  1. Serve/decode with the declared charset: read the Content-Type charset header and use Charset with it rather than assuming UTF-8
  2. Ensure complete reads: use readFully/readString that respects the declared length instead of scanning until a delimiter that can split code points
  3. Repair at the source: re-encode subtitle files as valid UTF-8 (iconv/editor) — this is data corruption, not a library quirk
  4. 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

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

Related errors


AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14). Data as JSON: /api/errors/8d0fd222ae2e6b75. Report an issue: GitHub.