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

  1. Only peek on persistent topics
  2. Verify the topic domain prefix before invoking peek
  3. 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

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


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/e5e27f22e1aea583. Report an issue: GitHub.