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
- Handle it as a normal termination: catch IOException around the streaming code and stop producing data (do not retry).
- Detect client disconnects early (response closeHandler) and cancel the upstream generation task to avoid wasted work.
- 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
- Register a close/drain handler and cancel generation tasks on client disconnect.
- Avoid very long-lived responses; paginate or chunk data.
- Tune load balancer and proxy idle timeouts to exceed worst-case response time.
- Treat disconnect IOExceptions as normal and log at debug level.
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
- Failed to write
- Failed to write
- Connection has been closed
- Stream is closed
- Failed to open path tree with root %s
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/1d8438a220082336.
Report an issue: GitHub.