{"record":{"id":"13e0ddc3ebb5e855","repo":"apache/pulsar","slug":"reset-cursor-at-position-is-not-allowed-for-partit","errorCode":null,"errorMessage":"Reset-cursor at position is not allowed for partitioned-topic","messagePattern":"Reset-cursor at position is not allowed for partitioned-topic","errorType":"http","errorClass":"RestException","httpStatus":405,"severity":"warning","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java","lineNumber":2763,"sourceCode":"            resumeAsyncResponseExceptionally(asyncResponse, ex);\n            return null;\n        });\n    }\n\n    protected void internalResetCursorOnPosition(AsyncResponse asyncResponse, String subName, boolean authoritative,\n            MessageIdImpl messageId, boolean isExcluded, int batchIndex) {\n        validateTopicOperationAsync(topicName, TopicOperation.RESET_CURSOR, subName)\n        .thenCompose(__ -> {\n            // If the topic name is a partition name, no need to get partition topic metadata again\n            if (!topicName.isPartitioned()) {\n                return getPartitionedTopicMetadataAsync(topicName, authoritative, false)\n                        .thenCompose(topicMetadata -> {\n                            if (topicMetadata.partitions > 0) {\n                                log.warn()\n                                        .attr(\"topic\", topicName)\n                                        .attr(\"subscription\", subName)\n                                        .log(\"Not supported operation on partitioned-topic\");\n                                throw new CompletionException(new RestException(Status.METHOD_NOT_ALLOWED,\n                                        \"Reset-cursor at position is not allowed for partitioned-topic\"));\n                            }\n                            return CompletableFuture.completedFuture(null);\n                        });\n            } else {\n                return CompletableFuture.completedFuture(null);\n            }\n        }).thenCompose(__ -> validateGlobalNamespaceOwnershipAsync(namespaceName))\n        .thenCompose(__ -> {\n            log.info()\n                    .attr(\"topic\", topicName)\n                    .attr(\"subscription\", subName)\n                    .attr(\"position\", messageId)\n                    .log(\"received reset cursor on subscription to position\");\n            return validateTopicOwnershipAsync(topicName, authoritative);\n        }).thenCompose(ignore -> getTopicReferenceAsync(topicName))\n        .thenAccept(topic -> {\n            if (topic == null) {","sourceCodeStart":2745,"sourceCodeEnd":2781,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java#L2745-L2781","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call the reset endpoint on a concrete partition (e.g. my-topic-partition-0) rather than the partitioned topic base name","Or use the non-position reset endpoint POST .../subscription/{sub}/reset_by_timestamp which supports partitioned topics via per-partition fan-out","List partitions via GET /admin/v2/persistent/{ns}/{topic}/partitions and iterate them, resetting each individually"],"exampleFix":"// before\nadmin.topics().resetCursorByPosition(\"persistent://public/default/my-topic\", \"my-sub\", position);\n// after\nfor (String part : admin.topics().getPartitionedPartitions(\"persistent://public/default/my-topic\")) {\n    admin.topics().resetCursorByPosition(part, \"my-sub\", position);\n}","handlingStrategy":"validation","validationCode":"PartitionedTopicMetadata md = admin.topics().getPartitionedMetadata(topic);\nif (md.partitions > 0) throw new IllegalArgumentException(\"Use a concrete partition, e.g. \" + topic + \"-partition-0\");\nadmin.topics().resetCursorByPosition(topic, sub, position);","typeGuard":"boolean isPartitionedBase(String topic) throws Exception {\n    return admin.topics().getPartitionedMetadata(topic).partitions > 0;\n}","tryCatchPattern":null,"preventionTips":["Always resolve partitioned topics to their partition list before per-partition admin ops","Prefer reset-by-timestamp for partitioned subscriptions","Keep the full partition suffix in any persisted topic reference"],"tags":["rest-api","partitioned-topic","method-not-allowed","admin"],"backgroundTag":"operation-not-supported-on-partitioned-topic","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}