quarkusio/quarkus · error · IOException

Stream is closed

Error message

Stream is closed

What it means

read() was called on a VertxInputStream after the request body stream was closed. Quarkus wraps the Vert.x request body in this InputStream; once closed=true (explicit close() or connection teardown) any further read throws IOException("Stream is closed").

Source

Thrown at extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java:79

    @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 (continueState == ContinueState.REQUIRED) {
            continueState = ContinueState.SENT;
            exchange.request.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

  1. Read the request body exactly once, before any close, and buffer it if it must be consumed more than once.
  2. Check application code for accidental close() of the request InputStream (closing it is not required and poisons further reads).
  3. Ensure the body is fully read during the blocking request phase, not after the response has been sent or the request dispatched.
  4. If caching is needed, wrap with a duplicate/caching filter or use quarkus.http.body-related caching facilities instead of re-reading the raw stream.

Example fix

// before
try (InputStream in = request.getInputStream()) {
    body = in.readAllBytes();
} // stream closed; later code calls in.read() -> 'Stream is closed'
// after
InputStream in = request.getInputStream();
byte[] body = in.readAllBytes(); // do NOT close the request stream; reuse bytes as needed
Defensive patterns

Strategy: validation

Validate before calling

if (in == null) throw new IllegalStateException("no request stream");
// read body once and cache before any close()
byte[] cached = in.readAllBytes();

Try / catch

try {
    int n = in.read(buf);
} catch (IOException e) {
    if ("Stream is closed".equals(e.getMessage())) {
        // treat as end-of-stream: use previously cached body
    } else throw e;
}

Prevention

When it happens

Trigger: Calling read()/read(byte[],off,len) on the request InputStream after close() was invoked, or after the request/response lifecycle already ended (e.g. reading the body after writing the response or after async dispatch completed).

Common situations: Filters or interceptors that close the input stream then downstream code tries to read the body; reading the body twice (e.g. once for logging, once for deserialization) without caching; asynchronous processing that outlives the request.

Related errors


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