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
- Ensure close() is only called after all writes are complete and nothing else will write afterwards
- Guard producers with the stream's lifecycle (stop subscription on close or onResponseClosed)
- Restructure handlers so a response is either streamed or returned normally, never both
- 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
- Write-then-close in a single owner; never write after close() is invoked
- Wire Mutiny/CompletionStage continuations to stop when the stream closes
- Avoid paths that both stream the response and return a body afterwards
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.