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

  1. Preserve Kafka metadata when transforming messages (keep IncomingKafkaRecord, or copy its metadata).
  2. Consume exactly-once channels only from the Kafka connector.
  3. 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

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


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