apache/pulsar · warning · IllegalArgumentException

expected MultiMessageIdImpl object. Got instance of

Error message

expected MultiMessageIdImpl object. Got instance of 

What it means

MultiMessageIdImpl.compareTo only knows how to compare against another MultiMessageIdImpl. Passing any other MessageId implementation (MessageIdImpl, BatchMessageIdImpl, TopicMessageIdImpl, etc.) throws IllegalArgumentException naming the offending class. Comparison across the single-partition and multi-partition id types is undefined in this class.

Source

Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/MultiMessageIdImpl.java:57

    }

    // TODO: Add support for Serialization and Deserialization
    //  https://github.com/apache/pulsar/issues/4940
    @Override
    public byte[] toByteArray() {
        throw new UnsupportedOperationException();
    }

    @Override
    public int hashCode() {
        return Objects.hash(map);
    }

    // If all messageId in map are same size, and all bigger/smaller than the other, return valid value.
    @Override
    public int compareTo(MessageId o) {
        if (!(o instanceof MultiMessageIdImpl)) {
            throw new IllegalArgumentException(
                "expected MultiMessageIdImpl object. Got instance of " + o.getClass().getName());
        }

        MultiMessageIdImpl other = (MultiMessageIdImpl) o;
        Map<String, MessageId> otherMap = other.getMap();

        if ((map == null || map.isEmpty()) && (otherMap == null || otherMap.isEmpty())) {
            return 0;
        }

        if (otherMap == null || map == null || otherMap.size() != map.size()) {
            throw new IllegalArgumentException("Current size and other size not equals");
        }

        int result = 0;
        for (Entry<String, MessageId> entry : map.entrySet()) {
            MessageId otherMessage = otherMap.get(entry.getKey());
            if (otherMessage == null) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Only compare MultiMessageIdImpl with MultiMessageIdImpl; unwrap TopicMessageIdImpl via getInnerMessageId() to compare at the partition level
  2. If you need cross-type comparison, convert the single id into a map keyed by topic and build a MultiMessageIdImpl, or compare ledgerId/entryId explicitly
  3. Guard with instanceof before calling compareTo
  4. Catch IllegalArgumentException around compareTo when ids come from mixed sources

Example fix

// before
int r = multiId.compareTo(singleId); // IllegalArgumentException
// after
if (multiId instanceof MultiMessageIdImpl && singleId instanceof MultiMessageIdImpl) {
    int r = multiId.compareTo(singleId);
} else {
    // compare per-topic inner ids instead
    MessageId inner = ((TopicMessageIdImpl) singleId).getInnerMessageId();
    ...
}
Defensive patterns

Strategy: type-guard

Validate before calling

Integer compareSafely(MessageId a, MessageId b) {
    if (a instanceof MultiMessageIdImpl && b instanceof MultiMessageIdImpl) return a.compareTo(b);
    return null; // cross-type comparison unsupported
}

Type guard

boolean comparableMultiIds(MessageId a, MessageId b) {
    return a instanceof MultiMessageIdImpl && b instanceof MultiMessageIdImpl;
}

Try / catch

try {
    int r = a.compareTo(b);
} catch (IllegalArgumentException e) {
    // unwrap TopicMessageIdImpl / compare per-topic inner ids instead
}

Prevention

When it happens

Trigger: Calling compareTo (directly or via equals/sorting) between a MultiMessageIdImpl and any non-MultiMessageIdImpl MessageId — e.g. comparing a partitioned-topic cursor to a message id from a single-partition consumer.

Common situations: Generic MessageId comparison/sorting code that mixes ids from partitioned and non-partitioned consumers; test utilities (testMultiMessageIdCompareto) passing the wrong type; mapping TopicMessageIdImpl results back without unwrapping.

Related errors


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