quarkusio/quarkus · error · IllegalStateException

No Vertx context found. Consider using @NonBlocking on the c

Error message

No Vertx context found. Consider using @NonBlocking on the caller method, or make sure the upstream emits items on the Vert.x context

What it means

The coroutine invoker for a suspend Reactive Messaging method must run on a Vert.x context: it derives its dispatcher from Vertx.currentContext(). When invoked with no current Vertx context it throws IllegalStateException, advising @NonBlocking or ensuring upstream items are emitted on the Vert.x context.

Source

Thrown at extensions/smallrye-reactive-messaging/kotlin/src/main/kotlin/io/quarkus/smallrye/reactivemessaging/runtime/kotlin/AbstractSubscribingCoroutineInvoker.kt:16

package io.quarkus.smallrye.reactivemessaging.runtime.kotlin

import io.quarkus.arc.Arc
import io.smallrye.reactive.messaging.Invoker
import io.vertx.core.Vertx
import java.util.concurrent.CompletableFuture
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture

abstract class AbstractSubscribingCoroutineInvoker(private val beanInstance: Any) : Invoker {

    override fun invoke(vararg args: Any?): CompletableFuture<Any?> {
        val coroutineScope = Arc.container().instance(ApplicationCoroutineScope::class.java).get()
        val dispatcher =
            Vertx.currentContext()?.let(::VertxDispatcher)
                ?: throw IllegalStateException(
                    "No Vertx context found. Consider using @NonBlocking on the caller method, or make sure the upstream emits items on the Vert.x context"
                )

        return coroutineScope
            .async(context = dispatcher) {
                try {
                    invokeBean(beanInstance, args)
                } finally {
                    dispatcher.cleanup()
                }
            }
            .asCompletableFuture()
    }

    abstract suspend fun invokeBean(beanInstance: Any, args: Array<out Any?>): Any?
}

View on GitHub (pinned to e1c734241f)

Solutions

  1. Annotate the calling method with @NonBlocking so it runs on the Vert.x (event-loop) context.
  2. Make sure the upstream publisher emits on a Vert.x context (use runSubscriptionOn / emit on event loop).
  3. Remove @Blocking from the suspend method; @Blocking runs it on a worker thread without Vert.x context.

Example fix

// before
@Blocking
@Incoming("in")
suspend fun handle(p: String) { ... }
// after
@NonBlocking
@Incoming("in")
suspend fun handle(p: String) { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Guard in the invoker before starting the coroutine:
if (Vertx.currentContext() == null) {
    throw IllegalStateException("invoke must be called on a Vert.x context")
}

Try / catch

try {
    invoker.invoke(args).get()
} catch (e: ExecutionException) {
    val cause = e.cause
    if (cause is IllegalStateException && cause.message?.contains("No Vertx context") == true) {
        // rerun on a Vert.x duplicated context
        vertx.runOnContext { invoker.invoke(args) }
    } else throw cause
}

Prevention

When it happens

Trigger: A suspend @Incoming mediator invoked from a thread without a Vert.x duplicated context, e.g. a custom executor, plain worker thread, or non-blocking requirement violated by @Blocking upstream.

Common situations: Calling the invoker manually in tests; upstream emitter running on a non-Vert.x thread (e.g. timer/scheduled executor); misusing @Blocking on the coroutine method.

Related errors


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