{"record":{"id":"8d0fd222ae2e6b75","repo":"google/ExoPlayer","slug":"invalid-utf-8-sequence-continuation-byte-value","errorCode":null,"errorMessage":"Invalid UTF-8 sequence continuation byte: {value}","messagePattern":"Invalid UTF-8 sequence continuation byte: (.+?)","errorType":"exception","errorClass":"NumberFormatException","httpStatus":null,"severity":"error","filePath":"library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java","lineNumber":595,"sourceCode":"    // find the high most 0 bit\n    for (int j = 7; j >= 0; j--) {\n      if ((value & (1 << j)) == 0) {\n        if (j < 6) {\n          value &= (1 << j) - 1;\n          length = 7 - j;\n        } else if (j == 7) {\n          length = 1;\n        }\n        break;\n      }\n    }\n    if (length == 0) {\n      throw new NumberFormatException(\"Invalid UTF-8 sequence first byte: \" + value);\n    }\n    for (int i = 1; i < length; i++) {\n      int x = data[position + i];\n      if ((x & 0xC0) != 0x80) { // if the high most 0 bit not 7th\n        throw new NumberFormatException(\"Invalid UTF-8 sequence continuation byte: \" + value);\n      }\n      value = (value << 6) | (x & 0x3F);\n    }\n    position += length;\n    return value;\n  }\n\n  /**\n   * Reads a UTF byte order mark (BOM) and returns the UTF {@link Charset} it represents. Returns\n   * {@code null} without advancing {@link #getPosition() position} if no BOM is found.\n   */\n  @Nullable\n  public Charset readUtfCharsetFromBom() {\n    if (bytesLeft() >= 3\n        && data[position] == (byte) 0xEF\n        && data[position + 1] == (byte) 0xBB\n        && data[position + 2] == (byte) 0xBF) {\n      position += 3;","sourceCodeStart":577,"sourceCodeEnd":613,"githubUrl":"https://github.com/google/ExoPlayer/blob/dd430f7053a1a3958deea3ead6a0565150c06bfc/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java#L577-L613","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nbyte[] webVttBytes = dataSource.readAltered();\nString line = new ParsableByteArray(webVttBytes).readStringUntil('\\n'); // splits emoji\n// after\nParsableByteArray p = new ParsableByteArray(webVttBytes);\ntry {\n  String line = p.readStringUntil('\\n');\n} catch (NumberFormatException e) {\n  Log.w(TAG, \"Invalid UTF-8 in subtitle, skipping cue\", e);\n}","handlingStrategy":"try-catch","validationCode":"// before scanning, sanity-check the region decodes: e.g. CharsetDecoder with REPORT\nCharsetDecoder d = StandardCharsets.UTF_8.newDecoder();\n// decode buffer.slice() and catch CharacterCodingException first","typeGuard":null,"tryCatchPattern":"try { s = p.readStringUntil(delim); } catch (NumberFormatException e) { /* skip malformed cue/tag, keep playing */ }","preventionTips":["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"],"tags":["parsing","utf-8","string","subtitles","corruption"],"backgroundTag":null,"analyzedSha":"dd430f7053a1a3958deea3ead6a0565150c06bfc","analyzedAt":"2026-08-14T12:22:02.982Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}