MuntashirAkon/AppManager · error · IOException

Stream has already been finished

Error message

Stream has already been finished

What it means

putArchiveEntry checks the finished flag and throws IOException if the archive was already finished, since no new entries can be added after the end-of-archive records were written. finish() may have been called directly or via close().

Solutions

  1. Create a new TarArchiveOutputStream if you need to keep writing.
  2. Reorder logic so all putArchiveEntry/closeArchiveEntry pairs happen before finish/close.
  3. Guard entry-writing code paths with a check on your own 'archive open' state.

Example fix

// before
tar.close();
tar.putArchiveEntry(entry); // throws
// after
try (TarArchiveOutputStream tar2 = new TarArchiveOutputStream(out2)) {
    tar2.putArchiveEntry(entry);
    tar2.closeArchiveEntry();
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Guard with your own lifecycle flag
if (archiveClosed) throw new IllegalStateException("archive already closed");

Try / catch

try {
    tar.putArchiveEntry(entry);
} catch (IOException e) {
    if ("Stream has already been finished".equals(e.getMessage())) {
        // reopen a fresh stream or abort the batch
    } else throw e;
}

Prevention

When it happens

Trigger: Calling putArchiveEntry after finish() or close(); reusing a finished TarArchiveOutputStream for another archive; a helper (handleLongName, writePaxHeaders, addManifestEntry) invoked on a finished stream.

Common situations: Writing archives in a loop where a later iteration uses a closed stream; returning the stream from a method after closing; accidentally closing in a finally block then continuing to write entries.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/40d136ddd910c446. Report an issue: GitHub.

Appendix: source

Thrown at app/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStream.java:335

     * output stream for writing the contents of the entry. Once this method is called, the stream
     * is ready for calls to write() to write the entry's contents. Once the contents are written,
     * closeArchiveEntry() <B>MUST</B> be called to ensure that all buffered data is completely
     * written to the output stream.
     *
     * @param archiveEntry The TarEntry to be written to the archive.
     * @throws IOException on error
     * @throws ClassCastException if archiveEntry is not an instance of TarArchiveEntry
     * @throws IllegalArgumentException if the {@link TarArchiveOutputStream#longFileMode} equals
     *                                  {@link TarArchiveOutputStream#LONGFILE_ERROR} and the file
     *                                  name is too long
     * @throws IllegalArgumentException if the {@link TarArchiveOutputStream#bigNumberMode} equals
     *         {@link TarArchiveOutputStream#BIGNUMBER_ERROR} and one of the numeric values
     *         exceeds the limits of a traditional tar header.
     */
    @Override
    public void putArchiveEntry(final ArchiveEntry archiveEntry) throws IOException {
        if (finished) {
            throw new IOException("Stream has already been finished");
        }
        final TarArchiveEntry entry = (TarArchiveEntry) archiveEntry;
        if (entry.isGlobalPaxHeader()) {
            final byte[] data = encodeExtendedPaxHeadersContents(entry.getExtraPaxHeaders());
            entry.setSize(data.length);
            entry.writeEntryHeader(recordBuf, zipEncoding, bigNumberMode == BIGNUMBER_STAR);
            writeRecord(recordBuf);
            currSize= entry.getSize();
            currBytes = 0;
            this.haveUnclosedEntry = true;
            write(data);
            closeArchiveEntry();
        } else {
            final Map<String, String> paxHeaders = new HashMap<>();
            final String entryName = entry.getName();
            final boolean paxHeaderContainsPath = handleLongName(entry, entryName, paxHeaders, "path",
                TarConstants.LF_GNUTYPE_LONGNAME, "file name");

View on GitHub (pinned to 0152f468fc)