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

  1. Remove @NonBlocking from the method so it is treated as blocking and can run on a virtual thread
  2. Or remove @RunOnVirtualThread if the method is genuinely reactive/non-blocking
  3. 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

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


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