quarkusio/quarkus · error · DeploymentException
Method '' of class '' is considered a non blocking method. @
Error message
Method '' of class '' is considered a non blocking method. @RunOnVirtualThread can only be used on methods considered blocking
What it means
@RunOnVirtualThread is only meaningful for methods RESTEasy Reactive considers blocking (it indicates they should run on a virtual thread instead of a worker thread). When a method already carries an explicit @NonBlocking annotation, combining it with @RunOnVirtualThread is a contradiction and the deployment fails with this DeploymentException.
Source
Thrown at independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java:894
value = annotation.value().asString();
}
return value;
}
private boolean isRunOnVirtualThread(MethodInfo info, boolean blocking, BlockingDefault defaultValue) {
Map.Entry<AnnotationTarget, AnnotationInstance> runOnVirtualThreadAnnotation = getInheritableAnnotation(info,
RUN_ON_VIRTUAL_THREAD);
Map.Entry<AnnotationTarget, AnnotationInstance> blockingAnnotation = getInheritableAnnotation(info, BLOCKING);
Map.Entry<AnnotationTarget, AnnotationInstance> nonBlockingAnnotation = getInheritableAnnotation(info, NON_BLOCKING);
if (runOnVirtualThreadAnnotation != null) {
if (!blocking) {
if (blockingAnnotation != null) {
return false;
}
if (nonBlockingAnnotation != null) {
throw new DeploymentException(
"Method '" + info.name() + "' of class '" + info.declaringClass().name()
+ "' is considered a non blocking method. @RunOnVirtualThread can only be used on " +
" methods considered blocking");
}
return true;
} else {
return true;
}
} else {
if (blockingAnnotation != null) {
return false;
} else {
return defaultValue == BlockingDefault.RUN_ON_VIRTUAL_THREAD;
}
}
}
private boolean isBlocking(MethodInfo info, BlockingDefault defaultValue) {View on GitHub (pinned to e1c734241f)
Solutions
- Remove @NonBlocking from the method so it is treated as blocking and can run on a virtual thread
- Or remove @RunOnVirtualThread if the method is genuinely reactive/non-blocking
- Keep @RunOnVirtualThread only on methods doing blocking work where virtual-thread execution is desired
Example fix
// before
@RunOnVirtualThread
@NonBlocking
public String get() { ... }
// after
@RunOnVirtualThread
public String get() { ... } Defensive patterns
Strategy: validation
Validate before calling
Method m = MyResource.class.getMethod("get");
if (m.isAnnotationPresent(RunOnVirtualThread.class) && m.isAnnotationPresent(NonBlocking.class)) {
throw new IllegalStateException("@RunOnVirtualThread conflicts with @NonBlocking on " + m);
} Prevention
- Use @RunOnVirtualThread only for blocking workloads you want on virtual threads
- Never combine @RunOnVirtualThread with @NonBlocking on the same method
- Keep threading annotations consistent per class; annotate methods individually when they differ
- Add a unit test scanning resource classes for contradictory threading annotations
When it happens
Trigger: A resource method annotated with both @RunOnVirtualThread and @NonBlocking is deployed; the class-level/method-level blocking resolution marks the method non-blocking via an explicit @NonBlocking annotation while @RunOnVirtualThread is also present.
Common situations: Copy-pasting threading annotations between reactive endpoints; adding @RunOnVirtualThread to an endpoint that was previously made non-blocking for performance; mixing annotation-driven threading styles on the same class.
Related errors
- Method '' of class '' contains both @Blocking and @NonBlocki
- Class '' contains both @Blocking and @NonBlocking annotation
- Parameter: ${i} of the constructor of class '${resourceDotNa
- Unable to load handled exception type ${i.getProvidedType()}
- '@FormParam' and '@RestForm' cannot be used in a resource me
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/3f36b1a9f9f8023c.
Report an issue: GitHub.