Tencent/tinker · error · UTFDataFormatException

String more than 65535 UTF bytes long

Error message

String more than 65535 UTF bytes long

What it means

Thrown by Mutf8.countBytes(s, shortLength=true) when the MUTF-8 byte length of a string exceeds 65,535. Dex string_data_item stores utf16_size and the byte data is bounded by a u16-length world; Tinker's copy enforces this while counting, before writing. Only strings ≥64KiB of encoded bytes trip it.

Source

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

    }

    /**
     * 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;
            } else if (ch <= 2047) {
                result += 2;
            } else {
                result += 3;
            }
            if (shortLength && result > 65535) {
                throw new UTFDataFormatException("String more than 65535 UTF bytes long");
            }
        }
        return result;
    }

    /**
     * Encodes the modified UTF-8 bytes corresponding to {@code s} into  {@code
     * dst}, starting at {@code offset}.
     */
    public static void encode(byte[] dst, int offset, String s) {
        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.
                dst[offset++] = (byte) ch;
            } else if (ch <= 2047) {
                dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
                dst[offset++] = (byte) (0x80 | (0x3f & ch));

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Split the giant constant into smaller chunks (string array or StringBuilder concatenation) so no single pool string exceeds ~64KiB of MUTF-8 bytes.
  2. If the string is data, move it out of the dex into an asset/resource file and load at runtime.
  3. Re-check with the source toolchain (d8 will emit its own diagnostic pointing at the offending constant) to locate which string blew the limit.

Example fix

// before
private static final String BIG = "...80KB of base64..."; // one pool string

// after
private static final String[] BIG_PARTS = {"...32KB...", "...32KB...", "...16KB..."};
private static String big() { return String.join("", BIG_PARTS); }
Defensive patterns

Strategy: validation

Validate before calling

long bytes = Mutf8.countBytes(candidate, false);
if (bytes > 65535) throw new IllegalArgumentException("string constant too large for dex pool: " + bytes + " bytes");

Prevention

When it happens

Trigger: Writing/merging dexes containing an extremely long string constant (huge generated SQL, embedded base64 blobs, long JSON in a string pool) via DexProfile/Dex merger paths that call countBytes with shortLength=true.

Common situations: Code generators or ORM/schema tools that embed large literals; JSON/base64 config constants growing past 64KiB after a feature change; merge tools concatenating resource-ish strings into one entry.

Related errors


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