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
- Split the giant constant into smaller chunks (string array or StringBuilder concatenation) so no single pool string exceeds ~64KiB of MUTF-8 bytes.
- If the string is data, move it out of the dex into an asset/resource file and load at runtime.
- 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
- Lint generated code for oversized string literals (any constant near 60KB encoded is a smell).
- Store large payloads as assets/resources, never as single string-pool entries.
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
- bad second byte
- bad second or third byte
- bad byte
- Declared length ${expectedLength} doesn't match decoded leng
- file is null.
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/02cd8bd08d74cb36.
Report an issue: GitHub.