{"record":{"id":"2fd9a428cca2e068","repo":"apache/pulsar","slug":"unknown-messageid-type-o-getclass-getname","errorCode":null,"errorMessage":"Unknown MessageId type: + o.getClass().getName()","messagePattern":"Unknown MessageId type: \\+ o\\.getClass\\(\\)\\.getName\\(\\)","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"pulsar-common/src/main/java/org/apache/pulsar/client/api/MessageIdAdv.java","lineNumber":105,"sourceCode":"    /**\n     * Get the message ID of the first chunk if the current message ID represents the position of a chunked message.\n     *\n     * @implNote A chunked message is distributed across different BookKeeper entries. The message ID of a chunked\n     * message is composed of two message IDs that represent positions of the first and the last chunk. The message ID\n     * itself represents the position of the last chunk.\n     *\n     * @return null if the message is not a chunked message\n     */\n    default MessageIdAdv getFirstChunkMessageId() {\n        return null;\n    }\n\n    /**\n     * The default implementation of {@link Comparable#compareTo(Object)}.\n     */\n    default int compareTo(MessageId o) {\n        if (!(o instanceof MessageIdAdv)) {\n            throw new UnsupportedOperationException(\"Unknown MessageId type: \"\n                    + ((o != null) ? o.getClass().getName() : \"null\"));\n        }\n        final MessageIdAdv other = (MessageIdAdv) o;\n        int result = Long.compare(this.getLedgerId(), other.getLedgerId());\n        if (result != 0) {\n            return result;\n        }\n        result = Long.compare(this.getEntryId(), other.getEntryId());\n        if (result != 0) {\n            return result;\n        }\n        // TODO: Correct the following compare logics, see https://github.com/apache/pulsar/pull/18981\n        result = Integer.compare(this.getPartitionIndex(), other.getPartitionIndex());\n        if (result != 0) {\n            return result;\n        }\n        return Integer.compare(this.getBatchIndex(), other.getBatchIndex());\n    }","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/client/api/MessageIdAdv.java#L87-L123","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nids.sort(Comparator.naturalOrder()); // throws if any legacy MessageId\n// after\nids.sort(Comparator.comparingLong((MessageId id) ->\n    id instanceof MessageIdAdv ? ((MessageIdAdv) id).getLedgerId() : Long.MAX_VALUE)\n    .thenComparingLong(id -> id instanceof MessageIdAdv ? ((MessageIdAdv) id).getEntryId() : 0));","handlingStrategy":"type-guard","validationCode":"if (ids.stream().anyMatch(id -> !(id instanceof MessageIdAdv))) {\n    throw new IllegalStateException(\"collection mixes legacy MessageId with MessageIdAdv\");\n}","typeGuard":"static boolean isComparable(MessageId id) {\n    return id instanceof MessageIdAdv;\n}","tryCatchPattern":"try {\n    ids.sort(Comparator.naturalOrder());\n} catch (UnsupportedOperationException e) {\n    // log the foreign class from e.getMessage() and fall back to ledgerId/entryId-based comparator\n}","preventionTips":["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."],"tags":["messageid","comparable","type-mismatch","version-compatibility"],"backgroundTag":"incompatible-messageid-type","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}