Tencent/tinker · error · UTFDataFormatException

bad second or third byte

Error message

bad second or third byte

What it means

Thrown by Mutf8.decode when a 3-byte MUTF-8 sequence (lead byte 0xE0–0xEF) has a second or third byte that is not a 0b10xxxxxx continuation. All non-ASCII chars above U+07FF occupy 3 bytes in MUTF-8 and both trailing bytes must be continuations.

Source

Thrown at third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Mutf8.java:54

        while (true) {
            char a = (char) (in.readByte() & 0xff);
            if (a == 0) {
                return new String(out, 0, s);
            }
            out[s] = a;
            if (a < '\u0080') {
                s++;
            } else if ((a & 0xe0) == 0xc0) {
                int b = in.readByte() & 0xff;
                if ((b & 0xC0) != 0x80) {
                    throw new UTFDataFormatException("bad second byte");
                }
                out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
            } else if ((a & 0xf0) == 0xe0) {
                int b = in.readByte() & 0xff;
                int c = in.readByte() & 0xff;
                if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
                    throw new UTFDataFormatException("bad second or third byte");
                }
                out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
            } else {
                throw new UTFDataFormatException("bad byte");
            }
        }
    }

    /**
     * Returns the number of bytes the modified UTF8 representation of 's' would take.
     */
    public static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
        long result = 0;
        final int length = s.length();
        for (int i = 0; i < length; ++i) {
            char ch = s.charAt(i);
            if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
                ++result;

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Verify the utf16_size prefix handling just before the string bytes — reading size with the wrong varint width shifts the whole decode.
  2. Checksum-validate the dex; corrupt 3-byte sequences almost always mean a damaged file.
  3. Reprocess the dex through a spec-conformant toolchain (d8/dx) and retry.
Defensive patterns

Strategy: try-catch

Try / catch

catch (UTFDataFormatException e) -> fail the whole dex with its identity and the failing string id; partial decode yields silently wrong identifiers downstream

Prevention

When it happens

Trigger: Decoding CJK/emoji/supplementary-string data whose continuation bytes are corrupt, or an off-by-one/two buffer position landing mid-sequence (lead byte read as continuation).

Common situations: Same family as the 2-byte failure: corrupt dex string sections, misencrypted string pools from obfuscators, or readers positioned incorrectly after a length prefix (utf16_size) mismatch.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/9302147267329517. Report an issue: GitHub.