quarkusio/quarkus · error · IllegalArgumentException
@ExactlyOnce on method ${methodName} does not support Kotlin
Error message
@ExactlyOnce on method ${methodName} does not support Kotlin suspend functions What it means
Kotlin suspend functions compile to methods taking a Continuation parameter and return an opaque Object/Coroutine return type; the exactly-once invoker and its transactional wrapper are not implemented against Kotlin coroutines, so the extension rejects suspend functions at build time.
Source
Thrown at extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java:209
}
if (method.hasAnnotation(DotNames.WITH_TRANSACTION) && !reactive) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " cannot combine @WithTransaction with a synchronous return type"
+ ", use @Transactional instead");
}
if (method.hasAnnotation(DotNames.TRANSACTIONAL) && method.hasAnnotation(DotNames.WITH_TRANSACTION)) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " cannot combine @Transactional with @WithTransaction");
}
boolean isSuspend = method.parameterTypes().stream()
.anyMatch(t -> t.name().equals(DotNames.CONTINUATION));
if (isSuspend) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ " does not support Kotlin suspend functions");
}
String incomingChannel = incoming.value().asString();
String outgoingChannel = outgoing.value().asString();
if (!discoveryState.isKafkaConnector(channelsManagedByConnectors, true, incomingChannel)) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ ": incoming channel '" + incomingChannel + "' is not managed by the Kafka connector");
}
if (!discoveryState.isKafkaConnector(channelsManagedByConnectors, false, outgoingChannel)) {
throw new IllegalArgumentException(
"@ExactlyOnce on method " + methodName
+ ": outgoing channel '" + outgoingChannel + "' is not managed by the Kafka connector");
}
View on GitHub (pinned to e1c734241f)
Solutions
- Remove the `suspend` modifier and return Uni/Multi (or a plain type) instead; use kotlinx-coroutines-reactor/mutiny bridges (e.g. uni.await) inside the non-suspend method.
- Handle coroutine work inside the method body rather than making the signature suspend.
- Drop @ExactlyOnce if suspend semantics are essential.
Example fix
// before
@ExactlyOnce
@Incoming("in") @Outgoing("out")
suspend fun process(p: String): String { ... }
// after
@ExactlyOnce
@Incoming("in") @Outgoing("out")
fun process(p: String): Uni<String> = uni { ... } // bridge coroutines internally Defensive patterns
Strategy: validation
Validate before calling
// Kotlin: do not declare suspend on @ExactlyOnce functions
// (compile-time check in JVM terms: Continuation must not appear in parameter types)
require(!method.parameters.any { it.type == Continuation::class }) { "suspend not supported with @ExactlyOnce" } Prevention
- Avoid `suspend` on @ExactlyOnce functions; bridge coroutines inside a normal fun returning Uni
- Keep Kafka processors in Kotlin non-suspend when using exactly-once
When it happens
Trigger: An @ExactlyOnce method written in Kotlin is declared as `suspend fun`, detected via a Continuation parameter in the method's parameter types.
Common situations: Kotlin developers writing suspend processors as they would for plain reactive messaging, then adding @ExactlyOnce for transactional semantics.
Related errors
- @ExactlyOnce on method ${methodName} requires both @Incoming
- @ExactlyOnce on method ${methodName} must return a value to
- @ExactlyOnce on method ${methodName} cannot be combined with
- @ExactlyOnce on method ${methodName} cannot combine @WithTra
- @ExactlyOnce on method ${methodName} cannot combine @Transac
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/667812a37de616bc.
Report an issue: GitHub.