Tencent/tinker · error · ZipException
File too short to be a zip file: ${raf.length()}
Error message
File too short to be a zip file: ${raf.length()} What it means
In readCentralDir, TinkerZipFile first computes raf.length() - ENDHDR (ENDHDR is the 22-byte EOCD record size); if the file is shorter than that, there is no room for even a minimal End Of Central Directory record, so it throws ZipException('File too short to be a zip file: <length>'). The length is included so you can immediately distinguish a truncated download from other corruption.
Source
Thrown at third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipFile.java:422
*
* <p>The central directory can be followed by a variable-length comment
* field, so we have to scan through it backwards. The comment is at
* most 64K, plus we have 18 bytes for the end-of-central-dir stuff
* itself, plus apparently sometimes people throw random junk on the end
* just for the fun of it.
*
* <p>This is all a little wobbly. If the wrong value ends up in the EOCD
* area, we're hosed. This appears to be the way that everybody handles
* it though, so we're in good company if this fails.
*/
private void readCentralDir() throws IOException {
// Scan back, looking for the End Of Central Directory field. If the zip file doesn't
// have an overall comment (unrelated to any per-entry comments), we'll hit the EOCD
// on the first try.
// No need to synchronize raf here -- we only do this when we first open the zip file.
long scanOffset = raf.length() - ENDHDR;
if (scanOffset < 0) {
throw new ZipException("File too short to be a zip file: " + raf.length());
}
raf.seek(0);
final int headerMagic = Integer.reverseBytes(raf.readInt());
if (headerMagic != LOCSIG) {
throw new ZipException("Not a zip archive");
}
long stopOffset = scanOffset - 65536;
if (stopOffset < 0) {
stopOffset = 0;
}
while (true) {
raf.seek(scanOffset);
if (Integer.reverseBytes(raf.readInt()) == ENDSIG) {
break;
}View on GitHub (pinned to 1b7ea02c23)
Solutions
- Check the reported length: 0 or a few hundred bytes almost always means a failed download — re-fetch the file.
- Validate the downloaded artifact's size/ checksum against the server-provided Content-Length before opening.
- Guard with a minimum-size check (>= 22 bytes plus entry data) in your pipeline and reject earlier with a clearer message.
Example fix
// before
TinkerZipFile zf = new TinkerZipFile(new File(path)); // may be a 0-byte stub
// after
File f = new File(path);
if (f.length() < 22) {
throw new IOException("Incomplete download: " + path + " is only " + f.length() + " bytes");
}
TinkerZipFile zf = new TinkerZipFile(f); Defensive patterns
Strategy: validation
Validate before calling
static final long MIN_ZIP_BYTES = 22; // ENDHDR
if (file.length() < MIN_ZIP_BYTES) {
throw new IOException("File too small to be a zip (" + file.length() + " bytes): " + file);
}
TinkerZipFile zf = new TinkerZipFile(file); Prevention
- Verify downloaded file size (Content-Length/checksum) before opening.
- Reject 0-byte files at ingestion — they usually mean a failed transfer.
- Keep a minimum-size guard in every pipeline stage that receives files from the network.
When it happens
Trigger: Constructing TinkerZipFile on a file smaller than 22 bytes: empty files (0 bytes), 1-byte stubs, HTML error pages saved with a .zip extension, or a download that created the file but wrote nothing.
Common situations: HTTP error responses saved as the payload; a downloader creating the target file then failing before writing; passing a directory or wrong path that resolves to a tiny placeholder; test fixtures that are empty by accident.
Related errors
- End Of Central Directory signature not found
- Expected ${DEX_IN_JAR_NAME} in ${file}
- Map is unsorted at ${section}
- CRC mismatch
- Size mismatch
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/8bd3d2ead41d2a40.
Report an issue: GitHub.