quarkusio/quarkus · error · IllegalStateException

An event consumer business method that cannot use @RunOnVirt

Error message

An event consumer business method that cannot use @RunOnVirtualThread and set the ordered attribute to true [method: %s, bean:%s]

What it means

@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.

Source

Thrown at extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java:196

                        } else if (isMessage(params.get(1).name())) {
                            throw new IllegalStateException(String.format(
                                    "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]",
                                    params, method, bean));
                        }
                    } else if (parametersCount != 1) {
                        throw new IllegalStateException(String.format(
                                "An event consumer business method must accept exactly one parameter: %s [method: %s, bean:%s]",
                                params, method, bean));
                    }
                    if (method.returnType().kind() != Kind.VOID && VertxConstants.isMessage(params.get(0).name())
                            && !KotlinUtils.isKotlinSuspendMethod(method)) {
                        throw new IllegalStateException(String.format(
                                "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]",
                                method, bean));
                    }
                    if (method.hasAnnotation(RunOnVirtualThread.class) && consumeEvent.value("ordered") != null
                            && consumeEvent.value("ordered").asBoolean()) {
                        throw new IllegalStateException(String.format(
                                "An event consumer business method that cannot use @RunOnVirtualThread and set the ordered attribute to true [method: %s, bean:%s]",
                                method, bean));
                    }

                    InvokerBuilder builder = invokerFactory.createInvoker(bean, method)
                            .withInstanceLookup();

                    if (parametersCount == 1 && method.parameterType(0).name().equals(MESSAGE)) {
                        // io.vertx.core.eventbus.Message
                        // no transformation required
                    } else if (parametersCount == 1 && method.parameterType(0).name().equals(MUTINY_MESSAGE)) {
                        // io.vertx.mutiny.core.eventbus.Message
                        builder.withArgumentTransformer(0, io.vertx.mutiny.core.eventbus.Message.class, "newInstance");
                    } else if (parametersCount == 1) {
                        // parameter is payload
                        builder.withArgumentTransformer(0, io.vertx.core.eventbus.Message.class, "body");
                    }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove @RunOnVirtualThread from the method if strict ordering matters (it will run on the event loop / worker as configured)
  2. Set @ConsumeEvent(ordered = false) (or drop the ordered attribute) if virtual-thread execution matters more than ordering
  3. Ensure ordering yourself inside the virtual-thread path (e.g. via a serialized queue or Mutiny Uni chaining)
  4. Split into two consumers if both behaviors are genuinely needed

Example fix

// before
@RunOnVirtualThread
@ConsumeEvent(value = "jobs", ordered = true)
void consume(String job) { ... }

// after
@ConsumeEvent(value = "jobs", ordered = false)
@RunOnVirtualThread
void consume(String job) { ... }
Defensive patterns

Strategy: validation

Validate before calling

Method m = ...;
if (m.isAnnotationPresent(RunOnVirtualThread.class)) {
    ConsumeEvent ce = m.getAnnotation(ConsumeEvent.class);
    if (ce != null && ce.ordered()) {
        throw new IllegalStateException("@RunOnVirtualThread cannot be combined with @ConsumeEvent(ordered=true)");
    }
}

Prevention

When it happens

Trigger: Annotating the same method with both @RunOnVirtualThread and @ConsumeEvent(ordered = "true") (attribute present and boolean-true).

Common situations: 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.

Related errors


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