google/ExoPlayer · error · NumberFormatException

Invalid UTF-8 sequence first byte: {value}

Error message

Invalid UTF-8 sequence first byte: {value}

What it means

Thrown by ParsableByteArray.readUtf8CodePointUntilDelimiter / the UTF-8 code-point reader (used by readStringUntil and friends) when the FIRST byte of a character cannot start a valid UTF-8 sequence — the bit scan (j from 7 down) found no leading-zero bit position that yields length 1-4, i.e. the byte is 0xFF or 0xFE (all/leading ones), which are never legal UTF-8 lead bytes. It surfaces as NumberFormatException because the method decodes a numeric code point.

Source

Thrown at library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java:590

   * @return Decoded long value
   */
  public long readUtf8EncodedLong() {
    int length = 0;
    long value = data[position];
    // 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() {

View on GitHub (pinned to dd430f7053)

Solutions

  1. Verify the data source actually delivered decrypted, complete bytes (check Content-Length, response code, and that a DataSourceError/HttpDataSource exception wasn't swallowed)
  2. Bounds/alignment check: hex-dump bytes around position before readStringUntil and confirm the expected delimiter exists
  3. Use readStringUntil with a correct delimiter, or read a length-prefixed string, rather than free-running scans into binary regions
  4. Catch the NumberFormatException per item and skip/drop the malformed cue/section so playback continues

Example fix

// before
String title = data.readStringUntil((byte) '\n'); // hits 0xFF in corrupted ID3 padding
// after
try {
  String title = data.readStringUntil((byte) '\n');
} catch (NumberFormatException e) {
  Log.w(TAG, "Malformed metadata, skipping", e);
  title = "";
}
Defensive patterns

Strategy: try-catch

Validate before calling

int first = p.peekUnsignedByte();
if ((first & 0xFE) == 0xFE) { // 0xFF/0xFE can never start UTF-8
  // skip this region instead of reading a string
}

Try / catch

try { s = p.readStringUntil(delim); } catch (NumberFormatException e) { /* log and skip the malformed entry, continue parsing */ }

Prevention

When it happens

Trigger: Reading a string from a buffer whose position lands on binary garbage: 0xFF bytes appear in ID3 padding gone wrong, misaligned subtitle cues, encrypted/scrambled segments being parsed as plaintext, or when getPosition() was advanced incorrectly so a length/metadata binary field is consumed as text.

Common situations: HLS/DASH subtitle and metadata parsers hitting partially downloaded or CORS-corrupted responses; custom DataSource returning uncleared buffers; wrong charset assumption (UTF-16 data read as UTF-8); encrypted HLS segments parsed without the decryption key applied first.

Understand the failure class

Related errors


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