quarkusio/quarkus · error · IOException
Stream is closed
Error message
Stream is closed
What it means
VertxOutputStream.write(byte[], int, int) throws IOException("Stream is closed") when the response output stream has already been closed and a further write is attempted. After close(), the underlying Vert.x response ends and any additional byte writes are invalid.
Source
Thrown at extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxOutputStream.java:58
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;
ByteBuf buffer = pooledBuffer;
try {
if (buffer == null) {
pooledBuffer = buffer = allocator.allocateBuffer();
}
while (rem > 0) {
int toWrite = Math.min(rem, buffer.writableBytes());
buffer.writeBytes(b, idx, toWrite);
rem -= toWrite;
idx += toWrite;
if (!buffer.isWritable()) {
ByteBuf tmpBuf = buffer;
this.pooledBuffer = buffer = allocator.allocateBuffer();
response.writeBlocking(tmpBuf, false);View on GitHub (pinned to e1c734241f)
Solutions
- Guard writes with a closed check or restructure so all writes happen before close(); track stream lifetime in one owner class.
- Do not write in finally blocks; only clean up resources there.
- Catch IOException and treat it as 'client already got the response / stream done' instead of propagating.
- Ensure only one component (the framework or your code, not both) closes the stream.
Example fix
// before stream.close(); stream.write(finishBytes); // IOException: Stream is closed // after stream.write(finishBytes); stream.close();
Defensive patterns
Strategy: validation
Validate before calling
private void safeWrite(OutputStream out, byte[] data) throws IOException {
if (out == null || !(out instanceof io.quarkus.resteasy.runtime.standalone.VertxOutputStream v) || vIsClosed(v)) {
return;
}
out.write(data);
} Type guard
boolean isStreamOpen(java.io.OutputStream out) {
try {
out.flush();
return true;
} catch (IOException e) {
return !"Stream is closed".equals(e.getMessage());
}
} Try / catch
try {
out.write(chunk);
} catch (IOException e) {
if ("Stream is closed".equals(e.getMessage())) {
LOG.debug("Stream already closed; dropping trailing write");
return;
}
throw e;
} Prevention
- Perform all writes before close(); single owner for stream lifecycle.
- Never write from finally blocks.
- Guard streaming loops with a volatile 'closed' flag set in close().
- Avoid writing error bodies after the stream was closed on an exception path.
When it happens
Trigger: Writing to the response OutputStream after close() was called — e.g. StreamingOutput/StreamingResponse continuing to write after an early return closed the stream, a finally block writing a terminator after close, or writing after the request lifecycle ended the response.
Common situations: Manual streaming code that ignores close semantics; double flush/close in a finally; frameworks (e.g. SSE or file download handlers) closing the stream while an application filter still writes; exception path that closes the stream then logs the error to the response body.
Related errors
- Stream is closed
- Error reading stream.
- Stream is closed
- Entity stream has already been read and is not buffered: cal
- Stream is closed
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/df99f343197464ed.
Report an issue: GitHub.