apache/pulsar · error · java.lang.IllegalArgumentException
Expected MessageIdV5, got: ${messageId.getClass()}
Error message
Expected MessageIdV5, got: ${messageId.getClass()} What it means
Thrown by MultiTopicQueueConsumer.routeAck when the acked MessageId is not a MessageIdV5 instance. Multi-topic routing requires the id to carry per-topic metadata that only MessageIdV5 exposes; any other MessageId implementation cannot be routed to the correct underlying topic consumer.
Source
Thrown at pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/MultiTopicQueueConsumer.java:331
@Override
public void acknowledge(MessageId messageId) {
routeAck(messageId, ptc -> ptc.acknowledge(messageId));
}
@Override
public void acknowledge(MessageId messageId, Transaction txn) {
routeAck(messageId, ptc -> ptc.acknowledge(messageId, txn));
}
@Override
public void negativeAcknowledge(MessageId messageId) {
routeAck(messageId, ptc -> ptc.negativeAcknowledge(messageId));
}
/** Look up the per-topic consumer via the parent topic tag and delegate. */
private void routeAck(MessageId messageId, java.util.function.Consumer<QueueConsumer<T>> action) {
if (!(messageId instanceof MessageIdV5 id)) {
throw new IllegalArgumentException("Expected MessageIdV5, got: " + messageId.getClass());
}
String parent = id.parentTopic();
if (parent == null) {
throw new IllegalStateException("MessageIdV5 missing parent topic — was the message"
+ " delivered through a multi-topic consumer?");
}
PerTopicState<T> state = perTopic.get(parent);
if (state == null) {
// Topic was removed between deliver and ack. Fine — broker has dropped the
// session for that topic. Drop the ack silently.
log.debug().attr("topic", parent)
.log("Ack for removed topic; dropping");
return;
}
action.accept(state.consumer);
}
@OverrideView on GitHub (pinned to 820761864e)
Solutions
- Ack only with MessageId instances returned by the same multi-topic consumer's receive()
- Check messageId instanceof MessageIdV5 before passing it to acknowledge/negativeAcknowledge
- If migrating from another client version, re-receive the message rather than reusing old ids
Example fix
// before
consumer.acknowledge(oldMessageId); // MessageIdV3 from another consumer
// after
if (oldMessageId instanceof MessageIdV5 id) {
consumer.acknowledge(id);
} Defensive patterns
Strategy: type-guard
Type guard
static boolean isAckable(MessageId id) { return id instanceof MessageIdV5; } Try / catch
try { consumer.acknowledge(id); } catch (IllegalArgumentException e) { log.error("wrong id type for multi-topic ack", e); } Prevention
- Only ack ids from the same consumer instance
- Avoid mixing MessageId implementations across consumers
When it happens
Trigger: Calling acknowledge(messageId) or negativeAcknowledge(messageId) on a MultiTopicQueueConsumer with a MessageId obtained from a different consumer type (single-topic consumer, MessageIdV3, or a deserialized id).
Common situations: Mixing message ids between a regular topic consumer and a multi-topic consumer; replaying ids persisted from an older client version; constructing an id manually.
Related errors
- Expected MessageIdV5, got: ${messageId.getClass()}
- MessageIdV5 missing parent topic — was the message delivered
- MessageIdV5 missing multi-topic vector — was the message del
- Class ${entryFilterClass} does not implement entry filter in
- Class ${className} does not implement additional servlet int
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/7a2336e0e2e945f8.
Report an issue: GitHub.