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

  1. Pass the original MessageId received from consumer.receive() (always MessageIdAdv in current clients) to isDuplicate()
  2. 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
  3. Guard with instanceof MessageIdAdv and skip the duplicate check for non-adv ids
  4. 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

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


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