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
- Ensure all MessageId instances in the comparison come from the same client version that produces MessageIdAdv (upgrade/downgrade dependencies so they match).
- Convert legacy IDs to the current MessageIdAdv representation (e.g. recreate via TopicMessageId/impl APIs) before comparing.
- Write a custom Comparator that handles non-MessageIdAdv instances (e.g. compares ledgerId/entryId reflectively or rejects them) instead of relying on MessageId.compareTo.
- 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
- Keep all client dependencies on one version so MessageId implementations are uniform.
- Never mix message IDs from different client library versions in the same collection.
- Null-check IDs before sorting or comparing.
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
- Expected MessageIdV5, got: ${messageId.getClass()}
- Expected MessageIdV5, got: ${messageId.getClass()}
- Class ${entryFilterClass} does not implement entry filter in
- Unknown schema type :
- is not an instance of MetadataStore
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/2fd9a428cca2e068.
Report an issue: GitHub.