Tencent/tinker · error · IOException

Stream is closed

Error message

Stream is closed

What it means

checkOpen() guards every mutating operation of AlignedZipOutputStream after close() has run; once the closed flag is set, any further putNextEntry, write, or closeEntry throws IOException("Stream is closed"). This is standard use-after-close protection for stream resources, preventing corruption of the already-finalized central directory.

Source

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

        if (currentEntry.getMethod() == STORED) {
            out.write(buffer, offset, byteCount);
        } else {
            super.write(buffer, offset, byteCount);
        }
        crc.update(buffer, offset, byteCount);
        crcDataSize += byteCount;
    }

    private void checkOffsetAndCount(int arrayLength, int offset, int count) {
        if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
            throw new ArrayIndexOutOfBoundsException("length=" + arrayLength + "; regionStart=" + offset
                    + "; regionLength=" + count);
        }
    }

    private void checkOpen() throws IOException {
        if (closed) {
            throw new IOException("Stream is closed");
        }
    }
}

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Establish single ownership of the stream: only the owner closes it, and helpers must not close resources they did not create.
  2. Track stream state in the caller (or wrap with a guard object exposing isOpen()) and check before writing after any operation that may close.
  3. For deferred writers, keep pending data buffered and write before close in a single serialized phase.

Example fix

// before: helper closes the stream, caller keeps writing
void copyAndClose(InputStream in, OutputStream out) throws IOException {
    ...; out.close();
}
copyAndClose(in, zos);
zos.putNextEntry(new ZipEntry("later")); // IOException: Stream is closed

// after: helper does not close a stream it does not own
void copy(InputStream in, OutputStream out) throws IOException { ... }
copy(in, zos);
zos.putNextEntry(new ZipEntry("later"));
zos.close(); // owner closes exactly once, last
Defensive patterns

Strategy: validation

Validate before calling

// Track lifecycle in the caller and check before writing
if (zipClosed) {
    zos = new AlignedZipOutputStream(new java.io.FileOutputStream(outFile, false));
    zipClosed = false;
}
zos.putNextEntry(entry);

Try / catch

try {
    zos.write(buffer, 0, n);
} catch (java.io.IOException e) {
    if ("Stream is closed".equals(e.getMessage())) {
        // reopen the archive from scratch; partial archives are not resumable
        zos = new AlignedZipOutputStream(new java.io.FileOutputStream(outFile, false));
        restartWrite();
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling any write-side method after close() — common when a finally block closes the stream but a queued/lazy writer, callback, or retry path still holds a reference and writes later.

Common situations: Utility methods that close the stream they were handed (e.g. a copy() helper closing the output) followed by the caller writing a footer; parallel or deferred tasks (async flushers) outliving the stream; shared streams closed by one code path while another continues appending entries.

Related errors


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