quarkusio/quarkus · error · DeploymentException

${exceptionMsgSupplier.get()} (nonBlocking/virtual-thread an

Error message

${exceptionMsgSupplier.get()} (nonBlocking/virtual-thread annotations conflict)

What it means

A gRPC method declares both @NonBlocking and @RunOnVirtualThread. @RunOnVirtualThread already implies a non-blocking, virtual-thread execution model, so combining it with @NonBlocking is contradictory and rejected in nonInheritedBlockingMode.

Source

Thrown at extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java:501

        IMPLICIT(true);

        final boolean blocking;

        BlockingMode(boolean blocking) {
            this.blocking = blocking;
        }
    }

    private static BlockingMode nonInheritedBlockingMode(Predicate<DotName> checker,
            Supplier<String> exceptionMsgSupplier) {
        boolean blocking = checker.test(BLOCKING);
        boolean nonBlocking = checker.test(NON_BLOCKING);
        boolean vt = checker.test(RUN_ON_VIRTUAL_THREAD);
        if (blocking && nonBlocking) {
            throw new DeploymentException(exceptionMsgSupplier.get());
        }
        if (nonBlocking && vt) {
            throw new DeploymentException(exceptionMsgSupplier.get());
        }
        if (blocking && !vt) {
            return BlockingMode.BLOCKING;
        }
        if (vt) {
            return BlockingMode.VIRTUAL_THREAD;
        }
        if (nonBlocking) {
            return BlockingMode.NON_BLOCKING;
        }
        boolean transactional = checker.test(TRANSACTIONAL);
        if (transactional) { // Cannot be on a virtual thread here.
            return BlockingMode.IMPLICIT;
        }
        return BlockingMode.UNDEFINED;
    }

    /**

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove @NonBlocking and keep only @RunOnVirtualThread
  2. Or remove @RunOnVirtualThread if classic @NonBlocking worker-thread execution is desired
  3. Verify superclass/interface annotations are not contributing the duplicate

Example fix

// before
@NonBlocking
@RunOnVirtualThread
public void greet(...) { ... }

// after
@RunOnVirtualThread
public void greet(...) { ... }
Defensive patterns

Strategy: validation

Validate before calling

if (m.isAnnotationPresent(NonBlocking.class) && m.isAnnotationPresent(RunOnVirtualThread.class)) {
    throw new IllegalStateException("@RunOnVirtualThread already implies non-blocking: " + m);
}

Prevention

When it happens

Trigger: @NonBlocking and @RunOnVirtualThread are both found by the checker on the same method (directly or inherited), when getMethodBlockingMode resolves the method's blocking mode.

Common situations: Migrating methods from @NonBlocking worker threads to virtual threads and keeping the old annotation; documentation examples mixing both; class-level @NonBlocking plus method-level @RunOnVirtualThread.

Related errors


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