quarkusio/quarkus · error · IOException

Stream is closed

Error message

Stream is closed

What it means

VertxOutputStream tracks a closed flag; once close() has been called, any further write(byte[],int,int) throws IOException('Stream is closed'). Writing to a finished HTTP response is a programming error, so the stream fails fast rather than silently dropping bytes.

Source

Thrown at independent-projects/vertx-utils/src/main/java/io/quarkus/vertx/utils/VertxOutputStream.java:173

        write(new byte[] { (byte) b }, 0, 1);
    }

    /**
     * {@inheritDoc}
     */
    public void write(final byte[] b) throws IOException {
        write(b, 0, b.length);
    }

    /**
     * {@inheritDoc}
     */
    public void write(final byte[] b, final int off, final int len) throws IOException {
        if (len < 1) {
            return;
        }
        if (closed) {
            throw new IOException("Stream is closed");
        }

        int rem = len;
        int idx = off;
        try {
            while (rem > 0) {
                final int written = appendBuffer.append(b, idx, rem);
                if (written < rem) {
                    writeBlocking(appendBuffer.clear(), false);
                }
                rem -= written;
                idx += written;
            }
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Ensure close() is only called after all writes are complete and nothing else will write afterwards
  2. Guard producers with the stream's lifecycle (stop subscription on close or onResponseClosed)
  3. Restructure handlers so a response is either streamed or returned normally, never both
  4. Check/capture a `closed` flag in your writer loop before each write

Example fix

// before
out.close();
out.write(trailer); // IOException: Stream is closed
// after
out.write(trailer);
out.close();
Defensive patterns

Strategy: type-guard

Validate before calling

if (out == null || out.isClosed()) { /* skip write */ }

Type guard

boolean writable(VertxOutputStream s) {
    return s != null && !s.isClosed();
}

Try / catch

try {
    out.writeBlocking(data);
} catch (IOException e) {
    if ("Stream is closed".equals(e.getMessage())) {
        logger.debug("Write after close skipped");
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling write()/writeBlocking() after close(); writing from a deferred callback or subscription that fires after the response completed; double-handling of the response (e.g. returning a value after already streaming it).

Common situations: Reactive pipelines (Mutiny/CompletionStage) continuing to emit after the resource finished; framework filters that close the stream then downstream code writes more; error-handling paths that write to an already-committed/closed response.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/69413a62ee845188. Report an issue: GitHub.