{"record":{"id":"b0a40e0eca1de133","repo":"apache/pulsar","slug":"get-message-id-by-timestamp-on-a-non-persistent-to","errorCode":null,"errorMessage":"Get message ID by timestamp on a non-persistent topic is not allowed","messagePattern":"Get message ID by timestamp on a non-persistent topic is not allowed","errorType":"http","errorClass":"RestException","httpStatus":405,"severity":"warning","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java","lineNumber":3020,"sourceCode":"                    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\n                return compactionService.getLastMessagePosition().thenCompose(messagePosition -> {\n                    if (timestamp == messagePosition.publishTime()) {\n                        return CompletableFuture.completedFuture(new MessageIdImpl(messagePosition.ledgerId(),\n                                messagePosition.entryId(), topicName.getPartitionIndex()));\n                    } else if (timestamp < messagePosition.publishTime()) {\n                        return persistentTopic.getTopicCompactionService().findEntryByPublishTime(timestamp)\n                                .thenApply(__ -> new MessageIdImpl(__.getLedgerId(), __.getEntryId(),\n                                        topicName.getPartitionIndex()));\n                    } else {\n                        return findMessageIdByPublishTime(timestamp, persistentTopic.getManagedLedger());\n                    }\n                });\n            });","sourceCodeStart":3002,"sourceCodeEnd":3038,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java#L3002-L3038","documentation":"The timestamp-to-message-ID lookup requires the target topic to be a PersistentTopic backed by a managed ledger; non-persistent topics have no durable entries to search. When getTopicReferenceAsync returns a topic that is not PersistentTopic, the broker logs 'Not supported operation of non-persistent topic' and throws 405 METHOD_NOT_ALLOWED.","triggerScenarios":"Calling getMessagedIdByTimestamp on a topic under the non-persistent:// domain, or on any topic reference that fails the instanceof PersistentTopic check.","commonSituations":"Reusing the same topic name string but switching the namespace/topic policy from persistent to non-persistent; tooling that hardcodes persistent:// but the cluster routes to a non-persistent topic of the same name.","solutions":["Use a persistent topic for time-based message-ID lookup","Check the topic domain in the URL (persistent:// vs non-persistent://) before calling","Enable persistent bookkeeper storage for the topic if durability is required"],"exampleFix":"// before\nString id = admin.topics().getMessageIdByTimestamp(\"non-persistent://public/default/my-topic\", ts);\n// after\nString id = admin.topics().getMessageIdByTimestamp(\"persistent://public/default/my-topic\", ts);","handlingStrategy":"validation","validationCode":"if (!topic.startsWith(\"persistent://\")) {\n    throw new IllegalArgumentException(\"Timestamp lookup requires a persistent topic\");\n}","typeGuard":"boolean isPersistentTopic(String topic) { return topic.startsWith(\"persistent://\"); }","tryCatchPattern":"try { ... getMessageIdByTimestamp(topic, ts); }\ncatch (PulsarAdminException e) { if (e.getStatusCode() == 405) { /* non-persistent or partitioned */ } }","preventionTips":["Verify the topic domain prefix before time-based lookups","Avoid name collisions between persistent and non-persistent topics in one namespace"],"tags":["rest-api","non-persistent-topic","method-not-allowed","admin"],"backgroundTag":"operation-not-supported-on-non-persistent-topic","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}