Tencent/tinker · error · IllegalStateException

Zip file closed

Error message

Zip file closed

What it means

Every read-style operation on TinkerZipFile (getEntry, getInputStream, getEntryComment, entries(), size()) starts with checkNotClosed(), which throws IllegalStateException('Zip file closed') once the underlying RandomAccessFile has been released. This fires not only after an explicit close() but also after a failed constructor path or a close from another thread. The state is not recoverable — you must open a new TinkerZipFile.

Source

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

     */
    public void close() throws IOException {
        // guard.close();
        RandomAccessFile localRaf = raf;
        if (localRaf != null) { // Only close initialized instances
            synchronized (localRaf) {
                raf = null;
                localRaf.close();
            }
            if (fileToDeleteOnClose != null) {
                fileToDeleteOnClose.delete();
                fileToDeleteOnClose = null;
            }
        }
    }

    private void checkNotClosed() {
        if (raf == null) {
            throw new IllegalStateException("Zip file closed");
        }
    }

    /**
     * Returns an enumeration of the entries. The entries are listed in the
     * order in which they appear in the zip file.
     *
     * <p>If you only need to iterate over the entries in a zip file, and don't
     * need random-access entry lookup by name, you should probably use {@link ZipInputStream}
     * instead, to avoid paying to construct the in-memory index.
     *
     * @throws IllegalStateException if this zip file has been closed.
     */
    public Enumeration<? extends TinkerZipEntry> entries() {
        checkNotClosed();
        final Iterator<TinkerZipEntry> iterator = entries.values().iterator();
        return new Enumeration<TinkerZipEntry>() {
            public boolean hasMoreElements() {

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Widen the try-with-resources scope (or defer close) so all reads finish before close() runs.
  2. Serialize close against readers with a lock or lifecycle flag; treat IllegalStateException('Zip file closed') as a signal to reopen, not to retry the same instance.
  3. Do not cache TinkerZipFile beyond the lifetime of the code that reads from it — open, use, close within one owner.

Example fix

// before
TinkerZipEntry e;
try (TinkerZipFile zf = new TinkerZipFile(f)) {
    e = zf.getEntry("classes.dex");
} // closed here
InputStream is = zf.getInputStream(e); // IllegalStateException

// after
try (TinkerZipFile zf = new TinkerZipFile(f)) {
    TinkerZipEntry e = zf.getEntry("classes.dex");
    try (InputStream is = zf.getInputStream(e)) {
        // fully consume is here
    }
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    TinkerZipEntry e = zf.getEntry(name);
} catch (IllegalStateException e) {
    if ("Zip file closed".equals(e.getMessage())) {
        // not retryable on this instance: reopen or treat as shutdown race
        handleClosedFile();
    }
}

Prevention

When it happens

Trigger: Calling any accessor on a TinkerZipFile instance after close(); racing between one thread closing the file (e.g. try-with-resources scope ending) and another thread still enumerating entries or reading streams obtained from it.

Common situations: try-with-resources blocks that are scoped too tightly around long-running stream reads; caching a TinkerZipFile across requests while some path closes it; concurrent shutdown hooks closing shared zip handles.

Related errors


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