quarkusio/quarkus · warning · IOException
Failed to write
Error message
Failed to write
What it means
VertxBlockingOutput writes RESTEasy response bytes to the Vert.x HTTP response; when the underlying write fails for any reason (connection reset, client disconnect, socket error), it releases the ByteBuf and wraps the cause in an IOException with message "Failed to write". It is a wrapper around a low-level transport failure, not an application bug.
Source
Thrown at extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxBlockingOutput.java:99
}
request.response().end();
throw new IOException(throwable);
}
try {
//do all this in the same lock
synchronized (request.connection()) {
try {
awaitWriteable();
if (last) {
request.response().end(createBuffer(data));
} else {
request.response().write(createBuffer(data));
}
} catch (Exception e) {
if (data != null && data.refCnt() > 0) {
data.release();
}
throw new IOException("Failed to write", e);
}
}
} finally {
if (last) {
terminateResponse();
}
}
}
@Override
public CompletionStage<Void> writeNonBlocking(ByteBuf data, boolean last) {
CompletableFuture<Void> ret = new CompletableFuture<>();
if (last && data == null) {
request.response().end().onComplete(handler(ret));
return ret;
}
Buffer buffer = createBuffer(data);
if (last) {View on GitHub (pinned to e1c734241f)
Solutions
- Treat as an expected client-disconnect case: catch IOException and skip/short-circuit the rest of the response rather than retrying.
- Check server/client timeouts and reduce response size or stream in chunks to avoid long-lived writes being cut off.
- Inspect the wrapped cause (getCause()) to distinguish connection reset from real server-side failures; fix the underlying transport issue if server-side.
- Ensure no thread holds the pooled buffer incorrectly; the library already releases it, so do not double-release in your code.
Example fix
try {
streamingOutput.write(outputStream);
} catch (IOException e) {
if (e.getCause() instanceof io.vertx.core.net.impl.exceptions.ConnectionResetException) {
LOG.debug("Client disconnected mid-response");
return; // do not attempt further writes
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (response.closed() || request.isEnded()) {
return; // skip writing, client already gone
} Type guard
boolean clientStillConnected(io.vertx.core.http.HttpServerResponse resp) {
return resp != null && !resp.closed() && !resp.headWritten() == false || !resp.closed();
} Try / catch
try {
out.write(payload);
} catch (IOException e) {
Throwable cause = e.getCause();
if (cause instanceof java.io.IOException || cause instanceof io.vertx.core.VertxException) {
LOG.debugf("Write failed (likely client disconnect): %s", String.valueOf(cause));
} else {
throw e;
}
} Prevention
- Stream large responses in chunks and honor backpressure.
- Set sane server/proxy timeouts for long responses.
- Log the cause chain to distinguish disconnects from server bugs.
- Never manually release buffers the framework manages.
When it happens
Trigger: Blocking response streaming via request.response().write(...) throws: client closed the connection mid-response, socket/HTTP/2 stream error, or backpressure flush failure during synchronous output in a classic RESTEasy endpoint on Vert.x.
Common situations: Clients aborting large downloads, browsers cancelling requests, load balancer idle timeouts, network interruptions during big/binary payloads in RESTEasy Classic on Quarkus.
Related errors
- Connection has been closed
- 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/01274243444d46f2.
Report an issue: GitHub.