quarkusio/quarkus · error · BlockingOperationNotAllowedException
Attempting a blocking write on io thread
Error message
Attempting a blocking write on io thread
What it means
awaitWriteable() blocks waiting for the Vert.x response write queue to drain. If it finds itself running on an event-loop (IO) thread, blocking would freeze the event loop, so it throws BlockingOperationNotAllowedException "Attempting a blocking write on io thread". Quarkus forbids blocking operations on IO threads.
Source
Thrown at extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxBlockingOutput.java:150
if (res.succeeded())
ret.complete(null);
else
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);View on GitHub (pinned to e1c734241f)
Solutions
- Run the endpoint or the producing code on a worker thread: annotate the resource method with @Blocking (SmallRye) so blocking writes happen off the event loop.
- Use a reactive/async output path (return Uni/Multi or CompletionStage) instead of blocking writes.
- If you control threading, dispatch to a worker via vertx.executeBlocking or workerDispatcher before writing.
- Reduce payload so writes complete without filling the write queue while on the event loop (workaround, not a fix).
Example fix
// before
@GET
@Path("/file")
public File download() { ... } // blocking write on event loop
// after
import io.smallrye.common.annotation.Blocking;
@GET
@Blocking
@Path("/file")
public File download() { ... } Defensive patterns
Strategy: validation
Validate before calling
import io.vertx.core.Context;
if (Context.isOnEventLoopThread() && willBlockOnWrite()) {
throw new IllegalStateException("Dispatch to a worker thread (@Blocking) before blocking writes");
} Try / catch
try {
blockingWrite();
} catch (io.quarkus.runtime.BlockingOperationNotAllowedException e) {
// re-dispatch to worker thread and retry
vertx.executeBlocking(p -> { doWrite(); p.complete(); });
} Prevention
- Annotate endpoints doing blocking IO with @Blocking.
- Never call blocking output from reactive handlers or event-loop code.
- Prefer reactive return types (Uni/Multi) for large payloads.
- Enable quarkus.vertx blocking checks in dev mode to catch violations early.
When it happens
Trigger: A synchronous/blocking write path (VertxBlockingOutput.write / awaitWriteable) executes while Context.isOnEventLoopThread() is true — e.g. a method annotated to run on the event loop (non-blocking route or default threading) performs blocking output until writeQueueFull().
Common situations: Returning large payloads from a non-blocking JAX-RS endpoint, calling blocking output inside a Vert.x route handler or @RunOnVirtualLoop-misconfigured bean, forgetting @Blocking / @ActivateRequestContext(threading) annotations after migrating to reactive handlers.
Related errors
- You have attempted to perform a blocking operation on a IO t
- You have attempted to inject AuthzClient on a IO thread. Thi
- VertxContextSupport#subscribeAndAwait() must not be called o
- Attempting a blocking read on io thread
- Attempting a blocking read on io thread
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/bf931b24a7b79bbf.
Report an issue: GitHub.