quarkusio/quarkus · error · IllegalStateException
No Kafka record metadata found on incoming message
Error message
No Kafka record metadata found on incoming message
What it means
The reactive ExactlyOnceInvoker.invokeBeanInTransaction wraps the exactly-once processing in a Kafka transaction using either IncomingKafkaRecordBatchMetadata (batch consumption) or IncomingKafkaRecordMetadata (single record). If the incoming Message carries neither, no Kafka transaction can be opened and the method throws IllegalStateException, propagating a failure on the reactive pipeline.
Source
Thrown at extensions/smallrye-reactive-messaging-kafka/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/ReactiveExactlyOnceInvoker.java:26
import io.smallrye.reactive.messaging.kafka.api.IncomingKafkaRecordMetadata;
import io.smallrye.reactive.messaging.kafka.transactions.KafkaTransactions;
import io.smallrye.reactive.messaging.kafka.transactions.TransactionalEmitter;
public abstract class ReactiveExactlyOnceInvoker extends ExactlyOnceInvoker {
protected ReactiveExactlyOnceInvoker(String outgoingChannel) {
super(outgoingChannel);
}
@Override
protected Object invokeBeanInTransaction(KafkaTransactions<Object> tx, Object[] beanArgs,
IncomingKafkaRecordMetadata<?, ?> recordMeta, IncomingKafkaRecordBatchMetadata<?, ?> batchMeta) {
if (batchMeta != null) {
return tx.withTransaction(batchMeta, emitter -> handleResult(invokeBean(beanArgs), emitter));
} else if (recordMeta != null) {
return tx.withTransaction(recordMeta, emitter -> handleResult(invokeBean(beanArgs), emitter));
} else {
throw new IllegalStateException("No Kafka record metadata found on incoming message");
}
}
private Uni<Void> handleResult(Object result, TransactionalEmitter<Object> emitter) {
if (result == null) {
return Uni.createFrom().voidItem();
}
if (result instanceof Uni<?> uni) {
return uni.onItem().invoke(item -> sendResult(item, emitter))
.replaceWithVoid();
}
if (result instanceof CompletionStage<?> cs) {
return Uni.createFrom().completionStage(cs)
.onItem().invoke(item -> sendResult(item, emitter))
.replaceWithVoid();
}
if (result instanceof Multi<?> multi) {
return multi.onItem().invoke(emitter::send)View on GitHub (pinned to e1c734241f)
Solutions
- Preserve Kafka metadata when transforming messages (keep IncomingKafkaRecord, or copy its metadata).
- Consume exactly-once channels only from the Kafka connector.
- For batch consumption ensure the batch metadata is retained on the message.
Example fix
// before return Message.of(transform(record.getPayload())); // drops metadata // after return record.withPayload(transform(record.getPayload())); // keeps IncomingKafkaRecordMetadata
Defensive patterns
Strategy: type-guard
Validate before calling
if (msg.getMetadata(IncomingKafkaRecordMetadata.class).isEmpty()
&& msg.getMetadata(IncomingKafkaRecordBatchMetadata.class).isEmpty()) {
throw new IllegalStateException("Missing Kafka metadata for exactly-once");
} Type guard
boolean isKafkaMessage(Message<?> m) {
return m.getMetadata(IncomingKafkaRecordMetadata.class).isPresent()
|| m.getMetadata(IncomingKafkaRecordBatchMetadata.class).isPresent();
} Try / catch
return uni.invokeBeanInTransaction(beanArgs)
.onFailure(IllegalStateException.class)
.recoverWithItem(t -> { log.warn("non-Kafka message reached exactly-once channel"); return null; }); Prevention
- Use record.withPayload(...) instead of Message.of(...) when transforming
- Keep exactly-once channels exclusively Kafka-connector-fed
- Test batch consumers retain IncomingKafkaRecordBatchMetadata
When it happens
Trigger: invokeBeanInTransaction is called with recordMeta == null and batchMeta == null — i.e. a Message without Kafka metadata reaches the exactly-once invoker (plain Message, emitter-produced, or metadata-stripped).
Common situations: Mapping IncomingKafkaRecord to a plain Message and losing metadata; feeding in-memory Emitters into exactly-once channels; test harnesses sending bare Messages.
Related errors
- No Kafka record metadata found on incoming message
- @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
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/4b03feadaa5d377d.
Report an issue: GitHub.