apache/pulsar · warning · RestException
Peek messages on a non-persistent topic is not allowed
Error message
Peek messages on a non-persistent topic is not allowed
What it means
Peeking messages requires a PersistentTopic; non-persistent topics do not store entries in a durable ledger that can be read by position. After ownership, if the topic reference fails the instanceof PersistentTopic check the broker throws 405 METHOD_NOT_ALLOWED 'Peek messages on a non-persistent topic is not allowed'.
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java:3091
if (topicMetadata.partitions > 0) {
throw new RestException(Status.METHOD_NOT_ALLOWED,
"Peek messages on a partitioned topic is not allowed");
}
return CompletableFuture.completedFuture(null);
});
} else {
return CompletableFuture.completedFuture(null);
}
}).thenCompose(__ -> validateTopicOwnershipAsync(topicName, authoritative))
.thenCompose(__ -> getTopicReferenceAsync(topicName))
.thenCompose(topic -> {
CompletableFuture<Entry> entry;
if (!(topic instanceof PersistentTopic)) {
log.error()
.attr("topic", topicName)
.attr("subscription", subName)
.log("Not supported operation of non-persistent topic");
throw new RestException(Status.METHOD_NOT_ALLOWED,
"Peek messages on a non-persistent topic is not allowed");
} else {
if (subName.startsWith(((PersistentTopic) topic).getReplicatorPrefix())) {
PersistentReplicator repl = getReplicatorReference(subName, (PersistentTopic) topic);
entry = repl.peekNthMessage(messagePosition);
} else {
entry = findOrCreateSubscriptionAsync(subName, (PersistentTopic) topic)
.thenCompose(sub -> sub.peekNthMessage(messagePosition));
}
}
return entry.thenApply(e -> Pair.of(e, (PersistentTopic) topic));
}).thenCompose(entryTopicPair -> {
Entry entry = entryTopicPair.getLeft();
PersistentTopic persistentTopic = entryTopicPair.getRight();
try {
Response response = generateResponseWithEntry(entry, persistentTopic);
return CompletableFuture.completedFuture(response);
} catch (NullPointerException npe) {View on GitHub (pinned to 820761864e)
Solutions
- Only peek on persistent topics
- Verify the topic domain prefix before invoking peek
- For non-persistent topics, use an active consumer with the reader/producer API to observe messages in-flight
Example fix
// before
Messages m = admin.topics().peekMessages("non-persistent://public/default/my-topic", "sub", 1);
// after
Messages m = admin.topics().peekMessages("persistent://public/default/my-topic", "sub", 1); Defensive patterns
Strategy: validation
Validate before calling
if (!topic.startsWith("persistent://")) {
throw new IllegalArgumentException("Peek requires a persistent topic");
} Type guard
boolean isPersistentTopic(String topic) { return topic.startsWith("persistent://"); } Prevention
- Filter namespace topic listings by domain before peeping
- Never assume a topic is persistent based on name alone
When it happens
Trigger: Calling the peek/preview REST endpoint (peekNthMessage path) with a topic that resolves to a non-persistent topic reference.
Common situations: Pointing debug tooling at topics in the non-persistent:// domain; clusters where the same namespace mixes persistent and non-persistent topics and scripts assume persistent.
Related errors
- Get message ID by timestamp on a non-persistent topic is not
- 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/e5e27f22e1aea583.
Report an issue: GitHub.