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
- Widen the try-with-resources scope (or defer close) so all reads finish before close() runs.
- 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.
- 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
- Scope try-with-resources around the entire read lifecycle, including stream consumption.
- Give each TinkerZipFile a single owner; close and read never race when ownership is clear.
- Treat this exception as a lifecycle bug to fix, not a condition to retry silently.
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
- Stream is closed
- Expected ${DEX_IN_JAR_NAME} in ${file}
- CRC mismatch
- Size mismatch
- Entry already exists: {}
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/bc71469818e58e85.
Report an issue: GitHub.