apache/pulsar · error · UnsupportedOperationException

Unknown MessageId type: + o.getClass().getName()

Error message

Unknown MessageId type: + o.getClass().getName()

What it means

MessageIdAdv.compareTo is the default Comparable implementation for advanced message IDs. It can only compare another MessageIdAdv; when handed a MessageId implementation from a different (older or custom) class it cannot order the two and throws UnsupportedOperationException naming the foreign class (or 'null' for a null argument).

Source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/client/api/MessageIdAdv.java:105

    /**
     * Get the message ID of the first chunk if the current message ID represents the position of a chunked message.
     *
     * @implNote A chunked message is distributed across different BookKeeper entries. The message ID of a chunked
     * message is composed of two message IDs that represent positions of the first and the last chunk. The message ID
     * itself represents the position of the last chunk.
     *
     * @return null if the message is not a chunked message
     */
    default MessageIdAdv getFirstChunkMessageId() {
        return null;
    }

    /**
     * The default implementation of {@link Comparable#compareTo(Object)}.
     */
    default int compareTo(MessageId o) {
        if (!(o instanceof MessageIdAdv)) {
            throw new UnsupportedOperationException("Unknown MessageId type: "
                    + ((o != null) ? o.getClass().getName() : "null"));
        }
        final MessageIdAdv other = (MessageIdAdv) o;
        int result = Long.compare(this.getLedgerId(), other.getLedgerId());
        if (result != 0) {
            return result;
        }
        result = Long.compare(this.getEntryId(), other.getEntryId());
        if (result != 0) {
            return result;
        }
        // TODO: Correct the following compare logics, see https://github.com/apache/pulsar/pull/18981
        result = Integer.compare(this.getPartitionIndex(), other.getPartitionIndex());
        if (result != 0) {
            return result;
        }
        return Integer.compare(this.getBatchIndex(), other.getBatchIndex());
    }

View on GitHub (pinned to 820761864e)

Solutions

  1. Ensure all MessageId instances in the comparison come from the same client version that produces MessageIdAdv (upgrade/downgrade dependencies so they match).
  2. Convert legacy IDs to the current MessageIdAdv representation (e.g. recreate via TopicMessageId/impl APIs) before comparing.
  3. Write a custom Comparator that handles non-MessageIdAdv instances (e.g. compares ledgerId/entryId reflectively or rejects them) instead of relying on MessageId.compareTo.
  4. Null-check message IDs before sorting/comparing so null never reaches compareTo.

Example fix

// before
ids.sort(Comparator.naturalOrder()); // throws if any legacy MessageId
// after
ids.sort(Comparator.comparingLong((MessageId id) ->
    id instanceof MessageIdAdv ? ((MessageIdAdv) id).getLedgerId() : Long.MAX_VALUE)
    .thenComparingLong(id -> id instanceof MessageIdAdv ? ((MessageIdAdv) id).getEntryId() : 0));
Defensive patterns

Strategy: type-guard

Validate before calling

if (ids.stream().anyMatch(id -> !(id instanceof MessageIdAdv))) {
    throw new IllegalStateException("collection mixes legacy MessageId with MessageIdAdv");
}

Type guard

static boolean isComparable(MessageId id) {
    return id instanceof MessageIdAdv;
}

Try / catch

try {
    ids.sort(Comparator.naturalOrder());
} catch (UnsupportedOperationException e) {
    // log the foreign class from e.getMessage() and fall back to ledgerId/entryId-based comparator
}

Prevention

When it happens

Trigger: Sorting or using a TreeMap/PriorityQueue over a mix of MessageIdAdv instances and legacy MessageId objects; calling acknowledgeCumulative/addPendingFuture paths that compare a MessageId not produced by the current client's MessageIdAdv factory.

Common situations: Mixing message IDs produced by an older client library version with a newer client using MessageIdAdv; custom MessageId implementations passed into V5/adv APIs; storing IDs across client upgrades in one collection; passing null into compareTo via a comparator.

Related errors


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