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 blocking ExactlyOnceInvoker sends the produced result inside a Kafka transaction that must be driven by the record metadata of the incoming message (IncomingKafkaRecordMetadata). When neither record nor batch metadata is present on the incoming message, no Kafka transaction can be created, so the invoker throws IllegalStateException at runtime.

Source

Thrown at extensions/smallrye-reactive-messaging-kafka/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/ExactlyOnceInvoker.java:53

        Object[] beanArgs = Arrays.copyOf(args, args.length - SYNTHETIC_PARAM_COUNT);

        return invokeBeanInTransaction(kafkaTransactions, beanArgs, recordMeta, batchMeta);
    }

    protected Object invokeBeanInTransaction(KafkaTransactions<Object> tx, Object[] beanArgs,
            IncomingKafkaRecordMetadata<?, ?> recordMeta, IncomingKafkaRecordBatchMetadata<?, ?> batchMeta) {
        if (batchMeta != null) {
            tx.withTransactionAndAwait(batchMeta, emitter -> {
                sendResult(invokeBean(beanArgs), emitter);
                return Uni.createFrom().voidItem();
            });
        } else if (recordMeta != null) {
            tx.withTransactionAndAwait(recordMeta, emitter -> {
                sendResult(invokeBean(beanArgs), emitter);
                return Uni.createFrom().voidItem();
            });
        } else {
            throw new IllegalStateException("No Kafka record metadata found on incoming message");
        }
        return null;
    }

    protected void sendResult(Object result, TransactionalEmitter<Object> emitter) {
        if (result == null) {
            return;
        }
        if (result instanceof Iterable<?> items) {
            for (Object item : items) {
                emitter.send(item);
            }
        } else if (result instanceof Multi<?> multi) {
            for (Object item : multi.subscribe().asIterable()) {
                emitter.send(item);
            }
        } else {
            emitter.send(result);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Ensure messages arriving at the exactly-once method carry IncomingKafkaRecordMetadata (consume directly from the Kafka connector, don't rewrite the Message).
  2. When transforming, use IncomingKafkaRecord/Message.withMetadataWithFallback to preserve metadata.
  3. Do not feed Emitters/in-memory channels into @ExactlyOnce consumers.

Example fix

// before
Message<String> m = Message.of(payload); // metadata lost

// after
Message<String> m = message.withMetadataWithFallback(original.getMetadata()); // preserve Kafka metadata
Defensive patterns

Strategy: try-catch

Validate before calling

// guard before invoking exactly-once logic
IncomingKafkaRecordMetadata<?, ?> meta = msg.getMetadata(IncomingKafkaRecordMetadata.class).orElse(null);
if (meta == null && msg.getMetadata(IncomingKafkaRecordBatchMetadata.class).isEmpty()) {
    throw new IllegalStateException("Message lacks Kafka metadata; cannot do exactly-once");
}

Type guard

Optional<IncomingKafkaRecordMetadata<?, ?>> hasKafkaMeta(Message<?> m) {
    return m.getMetadata(IncomingKafkaRecordMetadata.class);
}

Try / catch

try {
    invoker.invoke(message);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("No Kafka record metadata")) {
        // nack or route to dead-letter; exactly-once cannot proceed
        message.nack(e);
    } else throw e;
}

Prevention

When it happens

Trigger: invoke() reaches invokeBeanInTransaction with a Message lacking IncomingKafkaRecordMetadata and IncomingKafkaRecordBatchMetadata — typically a plain Message created by application code or bridged from another connector into an exactly-once channel.

Common situations: Emitting plain Message payloads via an Emitter into a channel consumed by an @ExactlyOnce method, or transforming messages and dropping Kafka metadata (e.g. Message.of(payload) mapping).

Related errors


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