apache/pulsar · warning · RestException
Get message ID by timestamp on a non-persistent topic is not
Error message
Get message ID by timestamp on a non-persistent topic is not allowed
What it means
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.
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java:3020
return CompletableFuture.completedFuture(null);
} else {
return getPartitionedTopicMetadataAsync(topicName, authoritative, false)
.thenAccept(metadata -> {
if (metadata.partitions > 0) {
throw new RestException(Status.METHOD_NOT_ALLOWED,
"Get message ID by timestamp on a partitioned topic is not allowed, "
+ "please try do it on specific topic partition");
}
});
}
}).thenCompose(__ -> validateTopicOwnershipAsync(topicName, authoritative))
.thenCompose(__ -> getTopicReferenceAsync(topicName))
.thenCompose(topic -> {
if (!(topic instanceof PersistentTopic)) {
log.error()
.attr("topic", topicName)
.log("Not supported operation of non-persistent topic");
throw new RestException(Status.METHOD_NOT_ALLOWED,
"Get message ID by timestamp on a non-persistent topic is not allowed");
}
final PersistentTopic persistentTopic = (PersistentTopic) topic;
final var compactionService = persistentTopic.getTopicCompactionService();
return compactionService.getLastMessagePosition().thenCompose(messagePosition -> {
if (timestamp == messagePosition.publishTime()) {
return CompletableFuture.completedFuture(new MessageIdImpl(messagePosition.ledgerId(),
messagePosition.entryId(), topicName.getPartitionIndex()));
} else if (timestamp < messagePosition.publishTime()) {
return persistentTopic.getTopicCompactionService().findEntryByPublishTime(timestamp)
.thenApply(__ -> new MessageIdImpl(__.getLedgerId(), __.getEntryId(),
topicName.getPartitionIndex()));
} else {
return findMessageIdByPublishTime(timestamp, persistentTopic.getManagedLedger());
}
});
});View on GitHub (pinned to 820761864e)
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
Example fix
// before
String id = admin.topics().getMessageIdByTimestamp("non-persistent://public/default/my-topic", ts);
// after
String id = admin.topics().getMessageIdByTimestamp("persistent://public/default/my-topic", ts); Defensive patterns
Strategy: validation
Validate before calling
if (!topic.startsWith("persistent://")) {
throw new IllegalArgumentException("Timestamp lookup requires a persistent topic");
} Type guard
boolean isPersistentTopic(String topic) { return topic.startsWith("persistent://"); } Try / catch
try { ... getMessageIdByTimestamp(topic, ts); }
catch (PulsarAdminException e) { if (e.getStatusCode() == 405) { /* non-persistent or partitioned */ } } Prevention
- Verify the topic domain prefix before time-based lookups
- Avoid name collisions between persistent and non-persistent topics in one namespace
When it happens
Trigger: Calling getMessagedIdByTimestamp on a topic under the non-persistent:// domain, or on any topic reference that fails the instanceof PersistentTopic check.
Common situations: 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.
Related errors
- Peek messages on a non-persistent topic is not allowed
- Examine messages on a non-persistent topic is not allowed
- Reset-cursor at position is not allowed for partitioned-topi
- GetMessageById is not allowed on partitioned-topic
- Get message ID by timestamp on a partitioned topic is not al
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/b0a40e0eca1de133.
Report an issue: GitHub.