quarkusio/quarkus · error · IllegalStateException
Suspendable @Blocking methods are not supported yet: %s.%s
Error message
Suspendable @Blocking methods are not supported yet: %s.%s
What it means
KotlinCoroutineIntegrationProcessor scans REST endpoint methods at build time. A Kotlin suspend fun annotated with @Blocking cannot be supported by the coroutine integration, so the deployment fails fast with IllegalStateException. Combining suspension with blocking execution semantics is inherently contradictory in this framework.
Source
Thrown at extensions/resteasy-reactive/rest-kotlin/deployment/src/main/java/io/quarkus/resteasy/reactive/kotlin/deployment/KotlinCoroutineIntegrationProcessor.java:92
recorder));
if (methodContext.containsKey(EndpointIndexer.METHOD_CONTEXT_CUSTOM_RETURN_TYPE_KEY)) {
Type methodReturnType = (Type) methodContext.get(EndpointIndexer.METHOD_CONTEXT_CUSTOM_RETURN_TYPE_KEY);
if (methodReturnType != null) {
if (methodReturnType.name().equals(FLOW)) {
return List.of(processor, flowCustomizer());
}
}
}
return Collections.singletonList(processor);
}
return Collections.emptyList();
}
private void ensureNotBlocking(MethodInfo method) {
if (method.annotation(BLOCKING_ANNOTATION) != null) {
String format = String.format("Suspendable @Blocking methods are not supported yet: %s.%s",
method.declaringClass().name(), method.name());
throw new IllegalStateException(format);
}
}
@Override
public ParameterExtractor handleCustomParameter(Type paramType, Map<DotName, AnnotationInstance> annotations,
boolean field, Map<String, Object> methodContext) {
//look for methods that take a Continuation, these are suspendable and need to be handled differently
if (paramType.name().equals(CONTINUATION)) {
methodContext.put(NAME, true);
if (paramType.kind() == Type.Kind.PARAMETERIZED_TYPE) {
Type firstGenericType = paramType.asParameterizedType().arguments().get(0);
if (firstGenericType.kind() == Type.Kind.WILDCARD_TYPE) {
methodContext.put(EndpointIndexer.METHOD_CONTEXT_CUSTOM_RETURN_TYPE_KEY,
firstGenericType.asWildcardType().superBound());
}
}
return new NullParamExtractor();View on GitHub (pinned to e1c734241f)
Solutions
- Remove the @Blocking annotation from the suspend method — suspend methods are dispatched on the coroutine dispatcher automatically
- If blocking work is required, move it into a non-suspend method annotated with @Blocking and call it via Dispatchers.IO or a separate bean
- Restructure the endpoint as a non-suspend method returning CompletionStage/Uni if coroutine support is not needed
Example fix
// before
@Blocking
@GET
suspend fun list(): List<Item> = repo.findAll()
// after
@GET
suspend fun list(): List<Item> = withContext(Dispatchers.IO) { repo.findAll() } Defensive patterns
Strategy: validation
Validate before calling
// Build-time / code-review guard
if (method.isSuspend && method.hasAnnotation(Blocking::class)) {
error("@Blocking is not allowed on suspend functions")
} Type guard
fun MethodInfo.isSuspendAndBlocking(): Boolean =
this.annotation(BLOCKING_ANNOTATION) != null && parameters.any { it.name() == null } == false // check suspend flag in Jandex Prevention
- Never combine @Blocking with suspend fun in Kotlin endpoints
- Use withContext(Dispatchers.IO) for blocking calls inside coroutines
- Review Kotlin resource methods for accidental @Blocking annotations
When it happens
Trigger: Declaring a JAX-RS resource method in Kotlin as `suspend fun` and annotating it with io.smallrye.common.annotation.Blocking.
Common situations: Developers assuming they need @Blocking because the coroutine runs on a worker thread, or copy-pasting @Blocking annotations from Java endpoints onto suspend functions.
Related errors
- Not a coroutine invoker
- No Vertx context found
- No Vertx context found
- Something went wrong during parameter type resolution - expe
- Suspend method '${targetMethod.name()} of class '${targetMet
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/0ddb37bf3fdbcc3f.
Report an issue: GitHub.