apache/pulsar · warning · IllegalArgumentException
isDuplicated cannot accept
Error message
isDuplicated cannot accept
What it means
PersistentAcknowledgmentsGroupingTracker.isDuplicate() only accepts MessageIdAdv instances (the advanced id interface exposing bit-set/ack-state fields). Passing a plain MessageId (e.g. a deserialized or non-adv id) throws IllegalArgumentException. The tracker relies on MessageIdAdv internals for its best-effort duplicate detection of already-acked messages being resent after reconnection.
Source
Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/PersistentAcknowledgmentsGroupingTracker.java:121
this.currentCumulativeAckFuture = new TimedCompletableFuture<>();
if (acknowledgementGroupTimeMicros > 0) {
scheduledTask = eventLoopGroup.next().scheduleWithFixedDelay(catchingAndLoggingThrowables(this::flush),
acknowledgementGroupTimeMicros,
acknowledgementGroupTimeMicros, TimeUnit.MICROSECONDS);
} else {
scheduledTask = null;
}
}
/**
* Since the ack are delayed, we need to do some best-effort duplicate check to discard messages that are being
* resent after a disconnection and for which the user has already sent an acknowledgement.
*/
@Override
public boolean isDuplicate(MessageId messageId) {
if (!(messageId instanceof MessageIdAdv)) {
throw new IllegalArgumentException("isDuplicated cannot accept "
+ messageId.getClass().getName() + ": " + messageId);
}
final MessageIdAdv messageIdAdv = (MessageIdAdv) messageId;
if (lastCumulativeAck.compareTo(messageIdAdv) >= 0) {
// Already included in a cumulative ack
return true;
} else {
// If "batchIndexAckEnabled" is false, the batched messages acknowledgment will be traced by
// pendingIndividualAcks. So no matter what type the message ID is, check with "pendingIndividualAcks"
// first.
MessageIdAdv key = MessageIdAdvUtils.discardBatch(messageIdAdv);
if (pendingIndividualAcks.contains(key)) {
return true;
}
if (messageIdAdv.getBatchIndex() >= 0) {
ConcurrentBitSet bitSet = pendingIndividualBatchIndexAcks.get(key);
return bitSet != null && !bitSet.get(messageIdAdv.getBatchIndex());
}View on GitHub (pinned to 820761864e)
Solutions
- Pass the original MessageId received from consumer.receive() (always MessageIdAdv in current clients) to isDuplicate()
- If starting from serialized bytes, use the consumer/broker round-trip rather than a bare MessageId.fromByteArray result, or reconstruct via the tracking layer that yields adv ids
- Guard with instanceof MessageIdAdv and skip the duplicate check for non-adv ids
- Catch IllegalArgumentException and treat the message as not-duplicate (safe default: let normal ack flow proceed)
Example fix
// before
boolean dup = tracker.isDuplicate(MessageId.fromByteArray(bytes)); // IllegalArgumentException
// after
MessageId id = MessageId.fromByteArray(bytes);
if (id instanceof MessageIdAdv) {
boolean dup = tracker.isDuplicate(id);
} else {
// non-adv id: skip best-effort duplicate detection
boolean dup = false;
} Defensive patterns
Strategy: type-guard
Validate before calling
boolean duplicateCheckable(MessageId id) {
return id instanceof MessageIdAdv;
} Type guard
boolean isAdv(MessageId id) {
return id instanceof MessageIdAdv;
} Try / catch
try {
boolean dup = tracker.isDuplicate(id);
} catch (IllegalArgumentException e) {
// non-adv id: treat as not duplicate and proceed with normal ack flow
} Prevention
- Only pass MessageIds received directly from consumer.receive() into ack/duplicate APIs
- Avoid round-tripping ids through fromByteArray without preserving the adv implementation
- Don't wrap MessageId in custom subclasses that drop the MessageIdAdv interface
When it happens
Trigger: Calling isDuplicate() with a MessageId that is not MessageIdAdv — commonly an id obtained via MessageId.fromByteArray/fromByteArrayWithTopic, a copied id, or a custom id implementation.
Common situations: Deserializing message ids from checkpoints and feeding them back into ack/duplicate checks; wrapping or transforming ids in connector code; version mismatches where an older client's id class lacks the MessageIdAdv interface.
Related errors
- expected MultiMessageIdImpl object. Got instance of
- Current size and other size not equals
- Other MessageId not have topic
- Different MessageId in Map get different compare result
- ResourceGroupCreate: Invalid null ResourceGroup config
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/a9ccd56825ed1441.
Report an issue: GitHub.