{"record":{"id":"bf931b24a7b79bbf","repo":"quarkusio/quarkus","slug":"attempting-a-blocking-write-on-io-thread","errorCode":null,"errorMessage":"Attempting a blocking write on io thread","messagePattern":"Attempting a blocking write on io thread","errorType":"exception","errorClass":"BlockingOperationNotAllowedException","httpStatus":null,"severity":"error","filePath":"extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxBlockingOutput.java","lineNumber":150,"sourceCode":"            if (res.succeeded())\n                ret.complete(null);\n            else\n                ret.completeExceptionally(res.cause());\n        };\n    }\n\n    private void awaitWriteable() throws IOException {\n        if (first) {\n            first = false;\n            return;\n        }\n        assert Thread.holdsLock(request.connection());\n        while (request.response().writeQueueFull()) {\n            if (throwable != null) {\n                throw new IOException(throwable);\n            }\n            if (Context.isOnEventLoopThread()) {\n                throw new BlockingOperationNotAllowedException(\"Attempting a blocking write on io thread\");\n            }\n            if (request.response().closed()) {\n                throw new IOException(\"Connection has been closed\");\n            }\n            if (!drainHandlerRegistered) {\n                drainHandlerRegistered = true;\n                Handler<Void> handler = new Handler<Void>() {\n                    @Override\n                    public void handle(Void event) {\n                        if (waitingForDrain) {\n                            HttpConnection connection = request.connection();\n                            synchronized (connection) {\n                                connection.notifyAll();\n                            }\n                        }\n                    }\n                };\n                request.response().drainHandler(handler);","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxBlockingOutput.java#L132-L168","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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)."],"exampleFix":"// before\n@GET\n@Path(\"/file\")\npublic File download() { ... } // blocking write on event loop\n\n// after\nimport io.smallrye.common.annotation.Blocking;\n\n@GET\n@Blocking\n@Path(\"/file\")\npublic File download() { ... }","handlingStrategy":"validation","validationCode":"import io.vertx.core.Context;\n\nif (Context.isOnEventLoopThread() && willBlockOnWrite()) {\n    throw new IllegalStateException(\"Dispatch to a worker thread (@Blocking) before blocking writes\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    blockingWrite();\n} catch (io.quarkus.runtime.BlockingOperationNotAllowedException e) {\n    // re-dispatch to worker thread and retry\n    vertx.executeBlocking(p -> { doWrite(); p.complete(); });\n}","preventionTips":["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."],"tags":["vertx","event-loop","blocking","threading"],"backgroundTag":"blocking-operation-on-io-thread","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}