apache/pulsar · error · java.lang.IllegalArgumentException
Expected MessageIdV5, got: ${messageId.getClass()}
Error message
Expected MessageIdV5, got: ${messageId.getClass()} What it means
ScalableQueueConsumer.acknowledge(MessageId) only accepts MessageIdV5 instances because acks must be routed to the owning segment via id.segmentId(). Passing any other MessageId implementation (v4 TopicMessageIdImpl, MessageIdImpl, etc.) throws an IllegalArgumentException immediately, before any broker interaction.
Source
Thrown at pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/ScalableQueueConsumer.java:225
@Override
public String consumerName() {
return consumerConf.getConsumerName();
}
@Override
public Message<T> receive() throws PulsarClientException {
return receiveQueue.take();
}
@Override
public Message<T> receive(Duration timeout) throws PulsarClientException {
return receiveQueue.poll(timeout);
}
@Override
public void acknowledge(MessageId messageId) {
if (!(messageId instanceof MessageIdV5 id)) {
throw new IllegalArgumentException("Expected MessageIdV5, got: " + messageId.getClass());
}
var future = segmentConsumers.get(id.segmentId());
if (future != null) {
future.thenAccept(c -> c.acknowledgeAsync(id.v4MessageId()));
}
}
@Override
public void acknowledge(MessageId messageId, Transaction txn) {
if (!(messageId instanceof MessageIdV5 id)) {
throw new IllegalArgumentException("Expected MessageIdV5, got: " + messageId.getClass());
}
var future = segmentConsumers.get(id.segmentId());
if (future != null) {
future.thenAccept(c -> c.acknowledgeAsync(id.v4MessageId(), TransactionV5.unwrap(txn)));
}
}
View on GitHub (pinned to 820761864e)
Solutions
- Only pass MessageId values returned by this v5 consumer's receive methods
- Convert foreign ids with MessageIdV5 (wrap the v4 id in a MessageIdV5 with the correct segmentId)
- Check message.getSource() / consumer origin before acking in fan-out code
- Add an instanceof check in your pipeline before calling acknowledge
Example fix
// before
consumer.acknowledge(someV4MessageId);
// after
if (messageId instanceof MessageIdV5 id) {
consumer.acknowledge(id);
} else {
consumer.acknowledge(MessageIdV5.from(messageId)); // convert
} Defensive patterns
Strategy: type-guard
Validate before calling
if (!(messageId instanceof MessageIdV5)) {
throw new IllegalArgumentException("ack requires MessageIdV5");
} Type guard
static boolean isV5MessageId(MessageId id) {
return id instanceof MessageIdV5;
} Try / catch
try {
consumer.acknowledge(messageId);
} catch (IllegalArgumentException e) {
log.error("wrong MessageId type for v5 consumer", e);
} Prevention
- Only ack with ids from the same v5 consumer
- Convert foreign ids via MessageIdV5 before acking
- Type-check ids in shared helper methods
- Avoid mixing v4 and v5 clients in one pipeline
When it happens
Trigger: Calling acknowledge() with a MessageId obtained from a different (v4) consumer or client, a MessageId deserialized from bytes via the v4 MessageId.fromByteArray, or a hand-constructed MessageIdImpl instead of the MessageIdV5 returned by this consumer's receive().
Common situations: Mixing v4 and v5 clients in one application (e.g. after a partial migration); forwarding messages across consumers and acking with the original MessageId; caching MessageIds from an older client version.
Related errors
- Expected MessageIdV5, got: ${messageId.getClass()}
- Class ${entryFilterClass} does not implement entry filter in
- Class ${className} does not implement additional servlet int
- Unsupported additional servlet type ${servletType}
- expected MultiMessageIdImpl object. Got instance of
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/5312deb678d4bf3b.
Report an issue: GitHub.