apache/pulsar · warning · RestException

Reset-cursor at position is not allowed for partitioned-topi

Error message

Reset-cursor at position is not allowed for partitioned-topic

What it means

The broker admin REST API rejects a cursor reset-to-position request issued against the base name of a partitioned topic. Partitioned topics are a logical grouping of individual partition topics, and cursors live on each concrete partition, so a position-based reset has no valid target. The check happens after fetching partitioned-topic metadata: if partitions > 0, a 405 METHOD_NOT_ALLOWED RestException is thrown.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java:2763

            resumeAsyncResponseExceptionally(asyncResponse, ex);
            return null;
        });
    }

    protected void internalResetCursorOnPosition(AsyncResponse asyncResponse, String subName, boolean authoritative,
            MessageIdImpl messageId, boolean isExcluded, int batchIndex) {
        validateTopicOperationAsync(topicName, TopicOperation.RESET_CURSOR, subName)
        .thenCompose(__ -> {
            // If the topic name is a partition name, no need to get partition topic metadata again
            if (!topicName.isPartitioned()) {
                return getPartitionedTopicMetadataAsync(topicName, authoritative, false)
                        .thenCompose(topicMetadata -> {
                            if (topicMetadata.partitions > 0) {
                                log.warn()
                                        .attr("topic", topicName)
                                        .attr("subscription", subName)
                                        .log("Not supported operation on partitioned-topic");
                                throw new CompletionException(new RestException(Status.METHOD_NOT_ALLOWED,
                                        "Reset-cursor at position is not allowed for partitioned-topic"));
                            }
                            return CompletableFuture.completedFuture(null);
                        });
            } else {
                return CompletableFuture.completedFuture(null);
            }
        }).thenCompose(__ -> validateGlobalNamespaceOwnershipAsync(namespaceName))
        .thenCompose(__ -> {
            log.info()
                    .attr("topic", topicName)
                    .attr("subscription", subName)
                    .attr("position", messageId)
                    .log("received reset cursor on subscription to position");
            return validateTopicOwnershipAsync(topicName, authoritative);
        }).thenCompose(ignore -> getTopicReferenceAsync(topicName))
        .thenAccept(topic -> {
            if (topic == null) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Call the reset endpoint on a concrete partition (e.g. my-topic-partition-0) rather than the partitioned topic base name
  2. Or use the non-position reset endpoint POST .../subscription/{sub}/reset_by_timestamp which supports partitioned topics via per-partition fan-out
  3. List partitions via GET /admin/v2/persistent/{ns}/{topic}/partitions and iterate them, resetting each individually

Example fix

// before
admin.topics().resetCursorByPosition("persistent://public/default/my-topic", "my-sub", position);
// after
for (String part : admin.topics().getPartitionedPartitions("persistent://public/default/my-topic")) {
    admin.topics().resetCursorByPosition(part, "my-sub", position);
}
Defensive patterns

Strategy: validation

Validate before calling

PartitionedTopicMetadata md = admin.topics().getPartitionedMetadata(topic);
if (md.partitions > 0) throw new IllegalArgumentException("Use a concrete partition, e.g. " + topic + "-partition-0");
admin.topics().resetCursorByPosition(topic, sub, position);

Type guard

boolean isPartitionedBase(String topic) throws Exception {
    return admin.topics().getPartitionedMetadata(topic).partitions > 0;
}

Prevention

When it happens

Trigger: Calling the admin endpoint POST /admin/v2/persistent/{tenant}/{namespace}/{topic}/subscription/{sub}/reset_by_position (or internalPersistentTopicsBase resetCursorAtPosition) with the parent partitioned topic name instead of a specific partition like topic-partition-0.

Common situations: Scripts that iterate topics and forget to append -partition-N; client libraries (e.g. Java/Python admin clients) passing the cluster-level topic name; tooling that resolves a partitioned topic from a subscription list and reuses it verbatim.

Related errors


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