quarkusio/quarkus · error · IOException
Stream is closed
Error message
Stream is closed
What it means
VertxInputStream.read throws this IOException when the request input stream has already been closed and read() is called again. It signals an attempt to consume the request body after the stream was finished/closed, which is not allowed.
Source
Thrown at independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxInputStream.java:81
@Override
public int read() throws IOException {
byte[] b = new byte[1];
int read = read(b);
if (read == -1) {
return -1;
}
return b[0] & 0xff;
}
@Override
public int read(final byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
if (closed) {
throw new IOException("Stream is closed");
}
if (vertxResteasyReactiveRequestContext.continueState == VertxResteasyReactiveRequestContext.ContinueState.REQUIRED) {
vertxResteasyReactiveRequestContext.continueState = VertxResteasyReactiveRequestContext.ContinueState.SENT;
vertxResteasyReactiveRequestContext.response.writeContinue();
}
readIntoBuffer();
if (limit > 0 && exchange.request.bytesRead() > limit) {
HttpServerResponse response = exchange.request.response();
if (response.headWritten()) {
//the response has been written, not much we can do
exchange.request.connection().close();
throw new IOException("Request too large");
} else {
response.setStatusCode(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE.code());
response.headers().add(HttpHeaderNames.CONNECTION, "close");
response.endHandler(new Handler<Void>() {
@Override
public void handle(Void event) {View on GitHub (pinned to e1c734241f)
Solutions
- Read the request body exactly once; pass the already-read bytes along instead of re-reading the stream
- Do not call close() yourself before you finish reading; remove explicit close in filters/interceptors
- If the body must be consumed multiple times, buffer it (byte[]/BlockingInputStream) first
- Fix lifecycle bugs where the stream reference outlives its request (avoid storing request streams in singletons/fields)
Example fix
// before String body = new String(in.readAllBytes()); in.close(); String again = new String(in.readAllBytes()); // IOException: Stream is closed // after byte[] data = in.readAllBytes(); in.close(); String body = new String(data); String again = new String(data); // reuse buffer
Defensive patterns
Strategy: try-catch
Validate before calling
// read the body once and buffer it byte[] buffered = inputStream.readAllBytes(); boolean streamUsable = !buffered.getClass().getName().isEmpty(); // reuse bytes, not the stream
Type guard
boolean isStreamOpen(InputStream in) {
return !(in instanceof VertxInputStream vi) || !vi.isClosed();
} Try / catch
try {
return readBody(in);
} catch (IOException e) {
if ("Stream is closed".equals(e.getMessage())) {
throw new IllegalStateException("Request body already consumed; buffer it first", e);
}
throw e;
} Prevention
- Read the request body exactly once — buffer to byte[] if needed multiple times
- Never store request InputStreams in fields/singletons or across requests
- Remove manual close() calls in filters before the body is fully read
- Pass already-read data through request context attributes instead of re-reading
When it happens
Trigger: Calling read() on the request InputStream after close(), after the body was fully consumed, or after the framework closed the stream (e.g. in a filter after reading, or reading the body twice).
Common situations: Reading the request body in both a request filter and the resource method; caching/holding the InputStream across requests (e.g. storing it in a field); reading a body after the request completed (async continuation after close); double-invocation of interceptors.
Related errors
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/84e55b63e7cfd721.
Report an issue: GitHub.