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
- 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
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
- 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
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
- An event consumer business method must accept exactly one pa
- An event consumer business method that accepts io.vertx.core
- Failed to find any non-blocking provider for startup actions
- Type ${className} must be annotated with @Embeddable, becaus
- No Vert.x instance has been registered in ArC ?
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/ef9ec61654acb744.
Report an issue: GitHub.