Tencent/tinker · error · ZipException

No entries

Error message

No entries

What it means

TinkerZipOutputStream.finish() finalizes the archive by writing the central directory and end-of-central-directory records. A zip archive with zero entries is rejected with ZipException("No entries") because Tinker's repack flow (used to rebuild APK/zip files during patch generation) treats an empty central directory as an invalid output. close() also triggers this since it calls finish().

Solutions

  1. Ensure at least one entry is added via putNextEntry() before calling finish()/close(); track a counter in the copy loop and verify it is > 0.
  2. If an empty archive is legitimately possible in your flow, catch ZipException around finish() and handle it explicitly (e.g. delete the empty output file and skip it).
  3. Debug why the entry-copy loop produced zero entries: log source entry names and the filter conditions before writing.

Example fix

// before
for (TinkerZipEntry e : src.entries()) {
    if (shouldCopy(e)) copyOne(zos, e);
}
zos.finish(); // throws if nothing copied

// after
int copied = 0;
for (TinkerZipEntry e : src.entries()) {
    if (shouldCopy(e)) { copyOne(zos, e); copied++; }
}
if (copied == 0) {
    throw new IllegalStateException("no entries copied from " + srcName);
}
zos.finish();
Defensive patterns

Strategy: validation

Validate before calling

// before finishing, track how many entries were written
int entriesWritten = 0;
// ... in copy loop, after each successful putNextEntry/closeEntry:
//   entriesWritten++;
if (entriesWritten == 0) {
    throw new IllegalStateException("refusing to finish empty archive " + outPath);
}
zos.finish();

Try / catch

// only if empty archives are acceptable in your flow
try {
    zos.finish();
} catch (ZipException e) {
    if ("No entries".equals(e.getMessage())) {
        // handle deliberately-empty output (e.g. delete partial file)
    } else { throw e; }
}

Prevention

When it happens

Trigger: Calling finish() or close() on a TinkerZipOutputStream without any successful putNextEntry() call — e.g. a copy loop over a source zip that matched zero entries, or all putNextEntry calls being skipped by a filter, or an exception aborting the loop before the first entry was written and the caller still closing the stream.

Common situations: Repacking an APK where a filter (e.g. exclude patterns for META-INF signatures) excludes every entry; iterating source entries that fail an md5/name check so nothing is copied; an empty input zip passed into Tinker's zip utils during patch build.

Related errors


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

Appendix: source

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

    /**
     * Indicates that all entries have been written to the stream. Any terminal
     * information is written to the underlying stream.
     *
     * @throws IOException
     *             if an error occurs while terminating the stream.
     */
    // @Override
    public void finish() throws IOException {
        // TODO: is there a bug here? why not checkOpen?
        if (out == null) {
            throw new IOException("Stream is closed");
        }
        if (cDir == null) {
            return;
        }
        if (entries.isEmpty()) {
            throw new ZipException("No entries");
        }
        if (currentEntry != null) {
            closeEntry();
        }
        int cdirEntriesSize = cDir.size();
        /*if (archiveNeedsZip64EocdRecord) {
            Zip64.writeZip64EocdRecordAndLocator(cDir, entries.size(), offset, cdirEntriesSize);
        }*/
        // Write Central Dir End
        writeLongAsUint32(cDir, ENDSIG);
        writeIntAsUint16(cDir, 0); // Disk Number
        writeIntAsUint16(cDir, 0); // Start Disk
        // Instead of trying to figure out *why* this archive needed a zip64 eocd record,
        // just delegate all these values to the zip64 eocd record.
        if (archiveNeedsZip64EocdRecord) {
            writeIntAsUint16(cDir, 0xFFFF); // Number of entries
            writeIntAsUint16(cDir, 0xFFFF); // Number of entries
            writeLongAsUint32(cDir, 0xFFFFFFFF); // Size of central dir

View on GitHub (pinned to 1b7ea02c23)