{"record":{"id":"3ad4da8f826e7cac","repo":"apache/pulsar","slug":"getmessagebyid-is-not-allowed-on-partitioned-topic","errorCode":null,"errorMessage":"GetMessageById is not allowed on partitioned-topic","messagePattern":"GetMessageById is not allowed on partitioned-topic","errorType":"http","errorClass":"RestException","httpStatus":405,"severity":"warning","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java","lineNumber":2951,"sourceCode":"            seekPosition = isExcluded ? seekPosition.getNext() : seekPosition;\n        }\n        return seekPosition;\n    }\n\n    protected CompletableFuture<Response> internalGetMessageById(long ledgerId, long entryId, boolean authoritative) {\n        return validateTopicOperationAsync(topicName, TopicOperation.PEEK_MESSAGES)\n        .thenCompose(__ -> validateGlobalNamespaceOwnershipAsync(namespaceName))\n        .thenCompose(__ -> {\n            if (topicName.isPartitioned()) {\n                return CompletableFuture.completedFuture(null);\n            } else {\n                return getPartitionedTopicMetadataAsync(topicName, authoritative, false)\n                        .thenAccept(topicMetadata -> {\n                            if (topicMetadata.partitions > 0) {\n                                log.warn()\n                                        .attr(\"topic\", topicName)\n                                        .log(\"Not supported getMessageById operation on partitioned-topic\");\n                                throw new RestException(Status.METHOD_NOT_ALLOWED,\n                                        \"GetMessageById is not allowed on partitioned-topic\");\n                            }\n                        });\n            }\n        }).thenCompose(ignore -> validateTopicOwnershipAsync(topicName, authoritative))\n        .thenCompose(__ -> getTopicReferenceAsync(topicName))\n        .thenCompose(topic -> {\n            CompletableFuture<Response> results = new CompletableFuture<>();\n            ManagedLedger ledger = ((PersistentTopic) topic).getManagedLedger();\n            ledger.asyncReadEntry(PositionFactory.create(ledgerId, entryId),\n                    new AsyncCallbacks.ReadEntryCallback() {\n                        @Override\n                        public void readEntryFailed(ManagedLedgerException exception,\n                                                    Object ctx) {\n                            if (exception instanceof ManagedLedgerException.LedgerNotExistException) {\n                                results.completeExceptionally(\n                                        new RestException(Status.NOT_FOUND, \"Message id not found\"));\n                                return;","sourceCodeStart":2933,"sourceCodeEnd":2969,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java#L2933-L2969","documentation":"The getMessageById admin REST operation refuses to run on a partitioned topic's base name. A message ID (ledgerId/entryId) is only meaningful within a single persistent backing topic (a partition), so the broker cannot look up a message by ID across all partitions. It is raised as 405 METHOD_NOT_ALLOWED after the partitioned-topic metadata check.","triggerScenarios":"GET /admin/v2/persistent/{tenant}/{namespace}/{topic}/ledger/{ledgerId}/entry/{entryId} with the partitioned topic's base name; the metadata check sees partitions > 0 and throws before ownership validation.","commonSituations":"Copying a ledgerId/entryId from broker logs and trying to fetch it via the partitioned topic name; debugging tools that accept a topic URL but don't resolve partitions.","solutions":["Determine which partition owns the ledger and call the endpoint with that partition name (my-topic-partition-N)","If the partition is unknown, iterate all partitions and try getMessageById on each","Avoid the ambiguity by persisting the full partition topic name alongside any message ID"],"exampleFix":"// before\nString msg = admin.topics().getMessageById(\"persistent://public/default/my-topic\", ledgerId, entryId);\n// after\nString msg = admin.topics().getMessageById(\"persistent://public/default/my-topic-partition-0\", ledgerId, entryId);","handlingStrategy":"validation","validationCode":"PartitionedTopicMetadata md = admin.topics().getPartitionedMetadata(topic);\nif (md.partitions > 0) throw new IllegalArgumentException(\"getMessageById requires a partition topic name\");","typeGuard":"boolean isPartitionName(String topic) { return topic.matches(\".*-partition-\\\\d+$\"); }","tryCatchPattern":"try { admin.topics().getMessageById(topic, ledgerId, entryId); }\ncatch (PulsarAdminException e) {\n    if (e.getStatusCode() == 405) { /* retry on partitions */ }\n}","preventionTips":["Record the exact partition topic name with every ledgerId/entryId","If only the base name is known, iterate partitions when looking up by ID"],"tags":["rest-api","partitioned-topic","method-not-allowed","admin"],"backgroundTag":"operation-not-supported-on-partitioned-topic","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"}