{"record":{"id":"ef9ec61654acb744","repo":"quarkusio/quarkus","slug":"an-event-consumer-business-method-that-cannot-use","errorCode":null,"errorMessage":"An event consumer business method that cannot use @RunOnVirtualThread and set the ordered attribute to true [method: %s, bean:%s]","messagePattern":"An event consumer business method that cannot use @RunOnVirtualThread and set the ordered attribute to true \\[method: (.+?), bean:(.+?)\\]","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java","lineNumber":196,"sourceCode":"                        } else if (isMessage(params.get(1).name())) {\n                            throw new IllegalStateException(String.format(\n                                    \"An event consumer business method with two parameters must not accept io.vertx.core.eventbus.Message or io.vertx.mutiny.core.eventbus.Message: %s [method: %s, bean:%s]\",\n                                    params, method, bean));\n                        }\n                    } else if (parametersCount != 1) {\n                        throw new IllegalStateException(String.format(\n                                \"An event consumer business method must accept exactly one parameter: %s [method: %s, bean:%s]\",\n                                params, method, bean));\n                    }\n                    if (method.returnType().kind() != Kind.VOID && VertxConstants.isMessage(params.get(0).name())\n                            && !KotlinUtils.isKotlinSuspendMethod(method)) {\n                        throw new IllegalStateException(String.format(\n                                \"An event consumer business method that accepts io.vertx.core.eventbus.Message or io.vertx.mutiny.core.eventbus.Message must return void [method: %s, bean:%s]\",\n                                method, bean));\n                    }\n                    if (method.hasAnnotation(RunOnVirtualThread.class) && consumeEvent.value(\"ordered\") != null\n                            && consumeEvent.value(\"ordered\").asBoolean()) {\n                        throw new IllegalStateException(String.format(\n                                \"An event consumer business method that cannot use @RunOnVirtualThread and set the ordered attribute to true [method: %s, bean:%s]\",\n                                method, bean));\n                    }\n\n                    InvokerBuilder builder = invokerFactory.createInvoker(bean, method)\n                            .withInstanceLookup();\n\n                    if (parametersCount == 1 && method.parameterType(0).name().equals(MESSAGE)) {\n                        // io.vertx.core.eventbus.Message\n                        // no transformation required\n                    } else if (parametersCount == 1 && method.parameterType(0).name().equals(MUTINY_MESSAGE)) {\n                        // io.vertx.mutiny.core.eventbus.Message\n                        builder.withArgumentTransformer(0, io.vertx.mutiny.core.eventbus.Message.class, \"newInstance\");\n                    } else if (parametersCount == 1) {\n                        // parameter is payload\n                        builder.withArgumentTransformer(0, io.vertx.core.eventbus.Message.class, \"body\");\n                    }\n","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java#L178-L214","documentation":"@ConsumeEvent(ordered = true) routes all messages for a consumer through a single serialized context, while @RunOnVirtualThread dispatches each invocation onto potentially different virtual threads. The two are mutually exclusive, so Quarkus fails the build when both are combined.","triggerScenarios":"Annotating the same method with both @RunOnVirtualThread and @ConsumeEvent(ordered = \"true\") (attribute present and boolean-true).","commonSituations":"Adding @RunOnVirtualThread for non-blocking style to an existing ordered consumer; enabling ordered delivery for thread-safety after already adopting virtual threads; copying annotations from two different consumers onto one method.","solutions":["Remove @RunOnVirtualThread from the method if strict ordering matters (it will run on the event loop / worker as configured)","Set @ConsumeEvent(ordered = false) (or drop the ordered attribute) if virtual-thread execution matters more than ordering","Ensure ordering yourself inside the virtual-thread path (e.g. via a serialized queue or Mutiny Uni chaining)","Split into two consumers if both behaviors are genuinely needed"],"exampleFix":"// before\n@RunOnVirtualThread\n@ConsumeEvent(value = \"jobs\", ordered = true)\nvoid consume(String job) { ... }\n\n// after\n@ConsumeEvent(value = \"jobs\", ordered = false)\n@RunOnVirtualThread\nvoid consume(String job) { ... }","handlingStrategy":"validation","validationCode":"Method m = ...;\nif (m.isAnnotationPresent(RunOnVirtualThread.class)) {\n    ConsumeEvent ce = m.getAnnotation(ConsumeEvent.class);\n    if (ce != null && ce.ordered()) {\n        throw new IllegalStateException(\"@RunOnVirtualThread cannot be combined with @ConsumeEvent(ordered=true)\");\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Choose one concurrency model per consumer: ordered event-loop or virtual threads","Keep ordered=true only when strict serialization is a hard requirement","Document why a consumer is ordered to avoid later annotation additions","Build after adding either annotation so the conflict fails fast"],"tags":["quarkus","vertx","virtual-threads","ordering","build-time-validation"],"backgroundTag":"conflicting-concurrency-annotations","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}