quarkusio/quarkus · warning · IOException

Connection has been closed

Error message

Connection has been closed

What it means

While waiting for the Vert.x response write queue to drain, VertxBlockingOutput checks request.response().closed(); if the client's connection is already closed it throws IOException("Connection has been closed") because no further bytes can be delivered. This is a client-disconnect detection during blocking output.

Source

Thrown at extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxBlockingOutput.java:153

                ret.completeExceptionally(res.cause());
        };
    }

    private void awaitWriteable() throws IOException {
        if (first) {
            first = false;
            return;
        }
        assert Thread.holdsLock(request.connection());
        while (request.response().writeQueueFull()) {
            if (throwable != null) {
                throw new IOException(throwable);
            }
            if (Context.isOnEventLoopThread()) {
                throw new BlockingOperationNotAllowedException("Attempting a blocking write on io thread");
            }
            if (request.response().closed()) {
                throw new IOException("Connection has been closed");
            }
            if (!drainHandlerRegistered) {
                drainHandlerRegistered = true;
                Handler<Void> handler = new Handler<Void>() {
                    @Override
                    public void handle(Void event) {
                        if (waitingForDrain) {
                            HttpConnection connection = request.connection();
                            synchronized (connection) {
                                connection.notifyAll();
                            }
                        }
                    }
                };
                request.response().drainHandler(handler);
                request.response().closeHandler(handler);
            }
            try {

View on GitHub (pinned to e1c734241f)

Solutions

  1. Handle it as a normal termination: catch IOException around the streaming code and stop producing data (do not retry).
  2. Detect client disconnects early (response closeHandler) and cancel the upstream generation task to avoid wasted work.
  3. Reduce time-to-first-byte and response duration so clients/proxies don't time out; adjust LB/proxy idle timeouts if legitimately slow.

Example fix

// stream defensively
try {
    while (hasData()) {
        out.write(chunk);
    }
} catch (IOException e) {
    LOG.debugf("Client went away while streaming: %s", e.getMessage());
    cancelUpstream(); // stop generating data
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (request.response().closed()) {
    cancelUpstream();
    return; // nothing can be delivered
}

Type guard

boolean writable(io.vertx.core.http.HttpServerResponse resp) {
    return resp != null && !resp.closed() && !resp.headWritten() || !resp.closed();
}

Try / catch

try {
    streamAll(out);
} catch (IOException e) {
    if ("Connection has been closed".equals(e.getMessage())) {
        LOG.debug("Client disconnected; aborting stream");
        cancelUpstream();
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Blocking write loop in awaitWriteable() observes the Vert.x HttpServerResponse is closed: client closed the socket/tab, TCP reset, load balancer killed an idle/slow connection while the server was still streaming the response.

Common situations: Long-running SSE or streamed downloads interrupted by the client; user cancels a browser request; mobile client loses network mid-response; reverse-proxy timeouts on slow upstreams.

Related errors


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