apache/pulsar · warning · RestException
Examine messages on a non-persistent topic is not allowed
Error message
Examine messages on a non-persistent topic is not allowed
What it means
Examine messages requires a PersistentTopic because it inspects the managed ledger (entry count, last entries). If the topic reference is not PersistentTopic, the broker logs 'Not supported operation of non-persistent topic' and throws 405 METHOD_NOT_ALLOWED with this message.
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java:3154
return getPartitionedTopicMetadataAsync(topicName, authoritative, false)
.thenCompose(partitionedTopicMetadata -> {
if (partitionedTopicMetadata.partitions > 0) {
throw new RestException(Status.METHOD_NOT_ALLOWED,
"Examine messages on a partitioned topic is not allowed, "
+ "please try examine message on specific topic partition");
} else {
return CompletableFuture.completedFuture(null);
}
});
}
return CompletableFuture.completedFuture(null);
}).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,
"Examine messages on a non-persistent topic is not allowed");
}
try {
PersistentTopic persistentTopic = (PersistentTopic) topic;
long totalMessage = persistentTopic.getNumberOfEntries();
if (totalMessage <= 0) {
throw new RestException(Status.PRECONDITION_FAILED,
"Could not examine messages due to the total message is zero");
}
Position startPosition = persistentTopic.getFirstPosition();
long messageToSkip = initialPositionLocal.equals("earliest") ? messagePositionLocal :
totalMessage - messagePositionLocal + 1;
CompletableFuture<Entry> future = new CompletableFuture<>();
Position readPosition = persistentTopic.getPositionAfterN(startPosition, messageToSkip);
persistentTopic.asyncReadEntry(readPosition, new AsyncCallbacks.ReadEntryCallback() {
@Override
public void readEntryComplete(Entry entry, Object ctx) {View on GitHub (pinned to 820761864e)
Solutions
- Examine only persistent topics; filter out non-persistent:// topics in tooling
- If examination of message flow is needed for non-persistent topics, attach a reader/consumer instead
- Verify the topic's domain in its full URL before invoking examine
Example fix
// before
admin.topics().examineMessages("non-persistent://public/default/my-topic");
// after
if (topicName.startsWith("persistent://")) { admin.topics().examineMessages(topicName); } Defensive patterns
Strategy: validation
Validate before calling
if (!topic.startsWith("persistent://")) {
throw new IllegalArgumentException("Examine requires a persistent topic");
} Type guard
boolean isPersistentTopic(String topic) { return topic.startsWith("persistent://"); } Prevention
- Filter out non-persistent topics before bulk examination
- Use consumer/reader APIs to validate non-persistent topic health instead
When it happens
Trigger: Calling the examineMessages REST endpoint on a topic under the non-persistent:// domain, or any topic reference failing the instanceof PersistentTopic check after ownership validation.
Common situations: Bulk examination scripts iterating all topics in a namespace that includes non-persistent topics; misconfigured topic domains where a non-persistent topic shares a name with an expected persistent one.
Related errors
- Get message ID by timestamp on a non-persistent topic is not
- Peek 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/74c5b0ebfc410b8e.
Report an issue: GitHub.