quarkusio/quarkus · error · IllegalStateException
The @Blocking, @NonBlocking and @RunOnVirtualThread annotati
Error message
The @Blocking, @NonBlocking and @RunOnVirtualThread annotations may only be used on "entrypoint" methods (methods invoked by various frameworks in Quarkus) Using the @Blocking, @NonBlocking and @RunOnVirtualThread annotations on methods that can only be invoked by application code is invalid
What it means
Quarkus's execution-model annotation processor verifies that @Blocking, @NonBlocking and @RunOnVirtualThread appear only on entrypoint methods (those invoked by frameworks: CDI observers, HTTP endpoints, scheduled tasks, etc.). If it finds them on methods only callable from application code, it logs a failure message; in strict mode it throws IllegalStateException.
Source
Thrown at core/deployment/src/main/java/io/quarkus/deployment/execannotations/ExecutionModelAnnotationsProcessor.java:53
if (config.detectionMode() == ExecutionModelAnnotationsConfig.Mode.DISABLED) {
return;
}
StringBuilder message = new StringBuilder("\n");
doCheck(message, index.getIndex(), predicates, BLOCKING);
doCheck(message, index.getIndex(), predicates, NON_BLOCKING);
doCheck(message, index.getIndex(), predicates, RUN_ON_VIRTUAL_THREAD);
if (message.length() > 1) {
message.append("The @Blocking, @NonBlocking and @RunOnVirtualThread annotations may only be used "
+ "on \"entrypoint\" methods (methods invoked by various frameworks in Quarkus)\n");
message.append("Using the @Blocking, @NonBlocking and @RunOnVirtualThread annotations on methods "
+ "that can only be invoked by application code is invalid");
if (config.detectionMode() == ExecutionModelAnnotationsConfig.Mode.WARN) {
log.warn(message);
} else {
throw new IllegalStateException(message.toString());
}
}
}
private void doCheck(StringBuilder message, IndexView index,
List<ExecutionModelAnnotationsAllowedBuildItem> predicates, DotName annotationName) {
List<String> badMethods = new ArrayList<>();
for (AnnotationInstance annotation : index.getAnnotations(annotationName)) {
// these annotations may be put on classes too, but we'll ignore that for now
if (annotation.target() != null && annotation.target().kind() == AnnotationTarget.Kind.METHOD) {
MethodInfo method = annotation.target().asMethod();
boolean allowed = false;
for (ExecutionModelAnnotationsAllowedBuildItem predicate : predicates) {
if (predicate.matches(method)) {
allowed = true;
break;
}View on GitHub (pinned to e1c734241f)
Solutions
- Move the annotation to the actual entrypoint (resource method, @Scheduled method, observer) and let the execution model propagate to helpers via context
- Remove the annotation from non-entrypoint methods — it has no effect there anyway
- Set quarkus.execution-model-detection-mode=warn temporarily to downgrade to a warning while migrating
- Split logic: keep an entrypoint method annotated, call unannotated internal methods from it
Example fix
// before
@RunOnVirtualThread
void helper() { ... } // only called from application code
// after
void helper() { ... } // annotate only the entrypoint, e.g. the JAX-RS method or @Scheduled method Defensive patterns
Strategy: validation
Validate before calling
// Build-time check habit: annotate only methods that Quarkus invokes itself
// Entry points: JAX-RS resource methods, @Scheduled, CDI observers (@Observes), messaging consumers
if (!isQuarkusEntryPoint(method)) throw new IllegalStateException("@Blocking/@NonBlocking/@RunOnVirtualThread only on entrypoints: " + method); Type guard
static boolean isExecutionModelAnnotation(AnnotationInstance a) {
return a.name().equals(DotName.createSimple("io.smallrye.common.annotation.Blocking"))
|| a.name().equals(DotName.createSimple("io.smallrye.common.annotation.NonBlocking"))
|| a.name().equals(DotName.createSimple("io.smallrye.common.annotation.RunOnVirtualThread"));
} Try / catch
try {
quarkusBuild();
} catch (IllegalStateException e) {
if (e.getMessage().contains("entrypoint\" methods")) {
// locate the annotated non-entrypoint method named in the message and remove/move the annotation
}
throw e;
} Prevention
- Annotate only framework-invoked methods; execution model propagates transitively to helpers
- Search the codebase for stray @Blocking/@RunOnVirtualThread on private/internal methods in code review
- Use quarkus.execution-model-detection-mode=warn while migrating to catch violations without failing the build
When it happens
Trigger: quarkus-execution-model-annotation build step 'check' during augmentation finds the annotations on a private/internal method or a method invoked solely by other application code, with quarkus.execution-model-detection-mode=strict (default).
Common situations: Developer annotates a helper method with @Blocking or @RunOnVirtualThread intending to influence the caller; annotation inherited onto internal methods via a class-level/inheritable placement mistake; refactor moves an annotated entrypoint method into internal-only code.
Related errors
- TransformedAnnotationsBuildItem#queryForMethodParam needs to
- AnnotationInstance ${instance} is an invalid target. Only Cl
- AnnotationInstance <annotationInstance> is an invalid target
- Value not set for ${param}
- Unsupported injection point target: <injectionPoint>
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/3c6d78ca76174764.
Report an issue: GitHub.