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

  1. Only pass MessageId values returned by this v5 consumer's receive methods
  2. Convert foreign ids with MessageIdV5 (wrap the v4 id in a MessageIdV5 with the correct segmentId)
  3. Check message.getSource() / consumer origin before acking in fan-out code
  4. 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

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


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/5312deb678d4bf3b. Report an issue: GitHub.