{"record":{"id":"6e5967af0da42c70","repo":"apache/pulsar","slug":"different-messageid-in-map-get-different-compare-r","errorCode":null,"errorMessage":"Different MessageId in Map get different compare result","messagePattern":"Different MessageId in Map get different compare result","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"warning","filePath":"pulsar-client/src/main/java/org/apache/pulsar/client/impl/MultiMessageIdImpl.java","lineNumber":86,"sourceCode":"        if (otherMap == null || map == null || otherMap.size() != map.size()) {\n            throw new IllegalArgumentException(\"Current size and other size not equals\");\n        }\n\n        int result = 0;\n        for (Entry<String, MessageId> entry : map.entrySet()) {\n            MessageId otherMessage = otherMap.get(entry.getKey());\n            if (otherMessage == null) {\n                throw new IllegalArgumentException(\n                    \"Other MessageId not have topic \" + entry.getKey());\n            }\n\n            int currentResult = entry.getValue().compareTo(otherMessage);\n            if (result == 0) {\n                result = currentResult;\n            } else if (currentResult == 0) {\n                continue;\n            } else if (result != currentResult) {\n                throw new IllegalArgumentException(\n                    \"Different MessageId in Map get different compare result\");\n            } else {\n                continue;\n            }\n        }\n\n        return result;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj instanceof MultiMessageIdImpl) {\n            MultiMessageIdImpl other = (MultiMessageIdImpl) obj;\n\n            try {\n                return compareTo(other) == 0;\n            } catch (IllegalArgumentException e) {\n                return false;","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client/src/main/java/org/apache/pulsar/client/impl/MultiMessageIdImpl.java#L68-L104","documentation":"MultiMessageIdImpl.compareTo demands a consistent total ordering: every per-topic comparison must yield the same sign (all greater, all equal, or all smaller). If per-partition comparisons disagree (some topics ahead, some behind), no valid ordering exists and it throws IllegalArgumentException('Different MessageId in Map get different compare result'). The class documents itself as only returning a value when all ids are uniformly bigger/smaller.","triggerScenarios":"Comparing two multi-partition cursors where the partitions have advanced unevenly — e.g. one consumer is ahead on partition 0 but behind on partition 1. This is the normal state for independent partitions, so direct compareTo between arbitrary cursors frequently hits this.","commonSituations":"Checkpoint/resume logic assuming a total order over partitioned cursors; comparing live cursors of two consumers on a partitioned topic with skewed consumption; sorting a list of multi-partition message ids.","solutions":["Don't compare multi-partition cursors with compareTo for ordering decisions; instead compare per-partition MessageIdImpl individually and define your own policy (e.g. min/max per topic)","If you need 'has this cursor reached X', check per-partition containment (every partition's id >= target for that partition) rather than compareTo","Store per-partition cursors and compare each independently on resume","Catch IllegalArgumentException and fall back to per-partition comparison"],"exampleFix":"// before\nboolean ahead = myCursor.compareTo(otherCursor) > 0; // often throws on skewed progress\n// after\nboolean ahead = true;\nfor (Map.Entry<String, MessageId> e : myCursor.getMap().entrySet()) {\n    MessageId otherId = otherCursor.getMap().get(e.getKey());\n    if (e.getValue().compareTo(otherId) < 0) { ahead = false; break; }\n}","handlingStrategy":"try-catch","validationCode":"// per-partition skew check before compareTo\nboolean uniformlyOrdered(MultiMessageIdImpl a, MultiMessageIdImpl b, int sign) {\n    return a.getMap().entrySet().stream()\n        .allMatch(e -> sign * e.getValue().compareTo(b.getMap().get(e.getKey())) >= 0);\n}","typeGuard":"boolean hasConsistentOrder(MultiMessageIdImpl a, MultiMessageIdImpl b) {\n    // true only if every per-topic comparison has the same sign\n    return uniformlyOrdered(a, b, 1) || uniformlyOrdered(a, b, -1);\n}","tryCatchPattern":"try {\n    int r = a.compareTo(b);\n} catch (IllegalArgumentException e) {\n    // skewed progress: fall back to per-partition comparison with your own policy (min/max)\n}","preventionTips":["Don't assume a total order across independent partitions; compare per-partition ids","Implement 'reached target' checks as per-partition containment, not compareTo","Catch IllegalArgumentException wherever partitioned cursors are compared"],"tags":["pulsar","messageid","partitioned","compareto","illegal-argument","ordering"],"backgroundTag":"messageid-incomparable-cursors","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"}