{"record":{"id":"c97a07e7855eb28c","repo":"apache/pulsar","slug":"get-message-id-by-timestamp-on-a-partitioned-topic","errorCode":null,"errorMessage":"Get message ID by timestamp on a partitioned topic is not allowed, please try do it on specific topic partition","messagePattern":"Get message ID by timestamp on a partitioned topic is not allowed, please try do it on specific topic partition","errorType":"http","errorClass":"RestException","httpStatus":405,"severity":"warning","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java","lineNumber":3007,"sourceCode":"                            return String.format(\"Topic [%s] internal get message by id\",\n                                    PersistentTopicsBase.this.topicName);\n                        }\n                    }, null);\n            return results;\n        });\n    }\n\n    protected CompletableFuture<MessageId> internalGetMessageIdByTimestampAsync(long timestamp, 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(metadata -> {\n                            if (metadata.partitions > 0) {\n                                throw new RestException(Status.METHOD_NOT_ALLOWED,\n                                    \"Get message ID by timestamp on a partitioned topic is not allowed, \"\n                                        + \"please try do it on specific topic partition\");\n                            }\n                        });\n                }\n            }).thenCompose(__ -> validateTopicOwnershipAsync(topicName, authoritative))\n            .thenCompose(__ -> getTopicReferenceAsync(topicName))\n            .thenCompose(topic -> {\n                if (!(topic instanceof PersistentTopic)) {\n                    log.error()\n                            .attr(\"topic\", topicName)\n                            .log(\"Not supported operation of non-persistent topic\");\n                    throw new RestException(Status.METHOD_NOT_ALLOWED,\n                        \"Get message ID by timestamp on a non-persistent topic is not allowed\");\n                }\n                final PersistentTopic persistentTopic = (PersistentTopic) topic;\n                final var compactionService = persistentTopic.getTopicCompactionService();\n","sourceCodeStart":2989,"sourceCodeEnd":3025,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java#L2989-L3025","documentation":"The 'get message ID by timestamp' admin operation rejects partitioned topics with a 405. Timestamp-to-position lookup requires scanning a single managed ledger; on a partitioned topic there are many ledgers and no single answer, so the caller is told to query a specific partition. The check is skipped entirely if the topic name is already a partition name.","triggerScenarios":"GET /admin/v2/persistent/{tenant}/{namespace}/{topic}/messageid/{timestamp} (or getMessagedIdByTimestamp) with a partitioned topic base name and partitions > 0.","commonSituations":"Time-travel/datetime-seek tooling resolving a topic alias; users migrating from timestamp-based cursor reset (which supports partitioned topics) to the messageid-by-timestamp lookup assuming identical semantics.","solutions":["Call the endpoint on a concrete partition, e.g. my-topic-partition-2","Iterate all partitions, collecting each partition's message ID at the timestamp","If the goal is seek-to-time on a consumer, use cursor reset by timestamp instead, which works on partitioned topics"],"exampleFix":"// before\nString msgId = admin.topics().getMessageIdByTimestamp(\"persistent://public/default/my-topic\", ts);\n// after\nString msgId = admin.topics().getMessageIdByTimestamp(\"persistent://public/default/my-topic-partition-0\", ts);","handlingStrategy":"validation","validationCode":"PartitionedTopicMetadata md = admin.topics().getPartitionedMetadata(topic);\nif (md.partitions > 0) { /* query each md.partitions partition instead */ }","typeGuard":"boolean isPartitionName(String topic) { return topic.matches(\".*-partition-\\\\d+$\"); }","tryCatchPattern":null,"preventionTips":["Use cursor reset-by-timestamp when you need partitioned-topic time seek","Fan out timestamp lookups across partitions and merge results"],"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"}