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

  1. Treat as an expected client-disconnect case: catch IOException and skip/short-circuit the rest of the response rather than retrying.
  2. Check server/client timeouts and reduce response size or stream in chunks to avoid long-lived writes being cut off.
  3. Inspect the wrapped cause (getCause()) to distinguish connection reset from real server-side failures; fix the underlying transport issue if server-side.
  4. 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

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


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