Tencent/tinker · error · ZipException

STORED entry size/compressed size mismatch

Error message

STORED entry size/compressed size mismatch

What it means

For a STORED (uncompressed) entry the compressed size must equal the uncompressed size by definition. After the missing-size checks pass, putNextEntry() compares ze.size != ze.compressedSize and throws ZipException("STORED entry size/compressed size mismatch") when the caller supplied two different values. This catches inconsistent metadata copied or computed by the caller before writing a corrupt local file header.

Source

Thrown at third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipOutputStream.java:467

        int method = ze.getMethod();
        if (method == -1) {
            method = defaultCompressionMethod;
        }
        // If the method is STORED, check that the ZipEntry was configured appropriately.
        if (method == STORED) {
            if (ze.getCompressedSize() == -1) {
                ze.setCompressedSize(ze.getSize());
            } else if (ze.getSize() == -1) {
                ze.setSize(ze.getCompressedSize());
            }
            if (ze.getCrc() == -1) {
                throw new ZipException("STORED entry missing CRC");
            }
            if (ze.getSize() == -1) {
                throw new ZipException("STORED entry missing size");
            }
            if (ze.size != ze.compressedSize) {
                throw new ZipException("STORED entry size/compressed size mismatch");
            }
        }
        checkOpen();
        // checkAndSetZip64Requirements(ze);

        //zhangshaowen edit here, we just want the same time and modDate
        ze.comment = null;
        ze.extra = null;
        ze.time = TIME_CONST;
        ze.modDate = MOD_DATE_CONST;

        nameBytes = ze.name.getBytes(StandardCharsets.UTF_8);
        checkSizeIsWithinShort("Name", nameBytes);
        entryCommentBytes = BYTE;
        if (ze.comment != null) {
            entryCommentBytes = ze.comment.getBytes(StandardCharsets.UTF_8);
            // The comment is not written out until the entry is finished, but it is validated here
            // to fail-fast.

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. For STORED entries always set compressedSize = size explicitly: ze.setCompressedSize(ze.getSize()) (or set both to the same buffer length).
  2. When copying from a source entry, recompute both fields from the raw data length rather than trusting source compressedSize.
  3. Add an assert before putNextEntry: if method == STORED, assert size == compressedSize.

Example fix

// before
e.setSize(rawData.length);
e.setCompressedSize(srcEntry.getCompressedSize()); // deflated length: mismatch
zos.putNextEntry(e);

// after
e.setSize(rawData.length);
e.setCompressedSize(rawData.length); // STORED: must equal size
zos.putNextEntry(e);
Defensive patterns

Strategy: validation

Validate before calling

static void assertStoredEntryConsistent(TinkerZipEntry e) {
    if (e.getMethod() == TinkerZipEntry.STORED && e.getSize() != e.getCompressedSize()) {
        throw new IllegalStateException("STORED size mismatch for " + e.getName());
    }
}
// call before putNextEntry(e)

Prevention

When it happens

Trigger: putNextEntry(entry) with method STORED where setSize() and setCompressedSize() were both called with different values — typically size set from the original file but compressedSize copied from a DEFLATED source entry (or vice versa), or each populated from different variables in a copy loop.

Common situations: Copying entry metadata from a source zip that was DEFLATED into a STORED output: compressedSize (deflated length) differs from size (raw length); refactoring where one of the two setters is fed a different length variable; stale compressedSize left over after switching an entry's method to STORED.

Related errors


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