quarkusio/quarkus · error · DeploymentException

Method '' of class '' contains both @Blocking and @NonBlocki

Error message

Method '' of class '' contains both @Blocking and @NonBlocking annotations.

What it means

A method cannot be annotated with both @Blocking and @NonBlocking — the desired execution model would be ambiguous. EndpointIndexer detects the conflict and throws this DeploymentException when both annotations target the same method (same annotation target kind METHOD).

Source

Thrown at independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java:922

            if (blockingAnnotation != null) {
                return false;
            } else {
                return defaultValue == BlockingDefault.RUN_ON_VIRTUAL_THREAD;
            }
        }
    }

    private boolean isBlocking(MethodInfo info, BlockingDefault defaultValue) {
        Map.Entry<AnnotationTarget, AnnotationInstance> blockingAnnotation = getInheritableAnnotation(info, BLOCKING);
        Map.Entry<AnnotationTarget, AnnotationInstance> runOnVirtualThreadAnnotation = getInheritableAnnotation(info,
                RUN_ON_VIRTUAL_THREAD);
        Map.Entry<AnnotationTarget, AnnotationInstance> nonBlockingAnnotation = getInheritableAnnotation(info,
                NON_BLOCKING);

        if ((blockingAnnotation != null) && (nonBlockingAnnotation != null)) {
            if (blockingAnnotation.getKey().kind() == nonBlockingAnnotation.getKey().kind()) {
                if (blockingAnnotation.getKey().kind() == AnnotationTarget.Kind.METHOD) {
                    throw new DeploymentException(
                            "Method '" + info.name() + "' of class '" + info.declaringClass().name()
                                    + "' contains both @Blocking and @NonBlocking annotations.");
                } else {
                    throw new DeploymentException("Class '" + info.declaringClass().name()
                            + "' contains both @Blocking and @NonBlocking annotations.");
                }
            }
            if (blockingAnnotation.getKey().kind() == AnnotationTarget.Kind.METHOD) {
                // the most specific annotation was the @Blocking annotation on the method
                return true;
            } else {
                // the most specific annotation was the @NonBlocking annotation on the method
                return false;
            }
        } else if ((blockingAnnotation != null)) {
            return true;
        } else if ((nonBlockingAnnotation != null)) {
            return false;

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove the @NonBlocking annotation from the method, keeping @Blocking
  2. Or remove @Blocking and keep @NonBlocking if the method is reactive
  3. Decide one execution model per method based on whether it performs blocking I/O

Example fix

// before
@Blocking
@NonBlocking
public String get() { ... }
// after
@Blocking
public String get() { ... }
Defensive patterns

Strategy: validation

Validate before calling

Method m = MyResource.class.getMethod("get");
if (m.isAnnotationPresent(Blocking.class) && m.isAnnotationPresent(NonBlocking.class)) {
    throw new IllegalStateException("Both @Blocking and @NonBlocking on " + m);
}

Prevention

When it happens

Trigger: Deploying a resource method that (after annotation inheritance/inheritable lookup) resolves to having both a @Blocking and a @NonBlocking annotation directly on the method.

Common situations: Copy-pasting annotations from another endpoint; a merge conflict leaving both annotations in place; IDE auto-import adding @NonBlocking next to an existing @Blocking while experimenting with threading behavior.

Related errors


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