quarkusio/quarkus · error · IllegalStateException
Not a coroutine invoker
Error message
Not a coroutine invoker
What it means
CoroutineInvocationHandler.handle runs after the endpoint method has been invoked. The generated invoker must implement CoroutineEndpointInvoker for Kotlin suspend endpoints; if the cached invoker is a different type, the handler flags IllegalStateException via handleException instead of crashing the request loop. This is an internal invariant violation, typically a codegen/dispatch mismatch.
Source
Thrown at extensions/resteasy-reactive/rest-kotlin/runtime/src/main/kotlin/org/jboss/resteasy/reactive/server/runtime/kotlin/CoroutineInvocationHandler.kt:28
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler
import org.slf4j.LoggerFactory
private val logger = LoggerFactory.getLogger(CoroutineInvocationHandler::class.java)
class CoroutineInvocationHandler(
private val invoker: EndpointInvoker,
private val coroutineScope: CoroutineScope,
) : ServerRestHandler {
private val originalTCCL: ClassLoader = Thread.currentThread().contextClassLoader
override fun handle(requestContext: ResteasyReactiveRequestContext) {
if (requestContext.result != null) {
return
}
if (invoker !is CoroutineEndpointInvoker) {
requestContext.handleException(IllegalStateException("Not a coroutine invoker"), true)
return
}
val requestScope = requestContext.captureCDIRequestScope()
val dispatcher: CoroutineDispatcher =
Vertx.currentContext()?.let { VertxDispatcher(it, requestScope, requestContext) }
?: throw IllegalStateException("No Vertx context found")
logger.trace("Handling request with dispatcher {}", dispatcher)
requestContext.suspend()
val done = AtomicBoolean()
val canceled = AtomicBoolean()
val job =
coroutineScope.launch(context = dispatcher) {
// ensure the proper CL is not lost in dev-mode
Thread.currentThread().contextClassLoader = originalTCCL
try {View on GitHub (pinned to e1c734241f)
Solutions
- Clean and rebuild the project (./mvnw clean install) to regenerate endpoint invokers
- Verify the endpoint method is actually a Kotlin suspend fun and the rest-kotlin extension is on the classpath
- Check for mixed deployment artifacts — remove stale target/ classes from previous builds
Defensive patterns
Strategy: try-catch
Type guard
fun isCoroutineInvoker(invoker: Any): Boolean = invoker is CoroutineEndpointInvoker
Try / catch
try {
chain.next(requestContext)
} catch (e: IllegalStateException) {
if (e.message == "Not a coroutine invoker") {
// rebuild artifacts: ./mvnw clean install and redeploy
}
throw e
} Prevention
- Run ./mvnw clean after changing Kotlin endpoint signatures
- Keep the rest-kotlin extension consistent across modules
- Avoid mixing stale generated classes with new source
When it happens
Trigger: At runtime, requestContext.result is null and the `invoker` field is not an instance of CoroutineEndpointInvoker — i.e. a non-coroutine invoker was wired into a coroutine-handled endpoint.
Common situations: Mixed Kotlin/Java endpoint compilation where the Kotlin coroutine handler is applied to a Java-generated invoker; stale generated classes after adding/removing `suspend`; build cache corruption.
Related errors
- Suspendable @Blocking methods are not supported yet: %s.%s
- No Vertx context found
- No Vertx context found
- Unknown type '${type}' used as an annotation in constructor
- Suspendable rest client method{jandexMethod} is present on c
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/ebdc823ec73dd3cc.
Report an issue: GitHub.