{"record":{"id":"ba4deea4a2944644","repo":"apache/pulsar","slug":"topic-is-not-partitioned-topic","errorCode":null,"errorMessage":"Topic is not partitioned topic","messagePattern":"Topic is not partitioned topic","errorType":"http","errorClass":"org.apache.pulsar.broker.admin.RestException","httpStatus":409,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java","lineNumber":295,"sourceCode":"            throw new RestException(Status.NOT_ACCEPTABLE, \"Need to provide a persistent topic name\");\n        }\n    }\n\n    protected void validatePartitionedTopicName(String tenant, String namespace, String encodedTopic) {\n        // first, it has to be a validate topic name\n        validateTopicName(tenant, namespace, encodedTopic);\n        // second, \"-partition-\" is not allowed\n        if (encodedTopic.contains(TopicName.PARTITIONED_TOPIC_SUFFIX)) {\n            throw new RestException(Status.PRECONDITION_FAILED,\n                    \"Partitioned Topic Name should not contain '-partition-'\");\n        }\n    }\n\n    protected CompletableFuture<Void> validatePartitionedTopicMetadataAsync() {\n        return pulsar().getBrokerService().fetchPartitionedTopicMetadataAsync(topicName)\n                .thenAccept(metadata -> {\n                    if (metadata.partitions < 1) {\n                        throw new RestException(Status.CONFLICT, \"Topic is not partitioned topic\");\n                    }\n                });\n    }\n\n    protected WorkerService validateAndGetWorkerService() {\n        try {\n            return pulsar().getWorkerService();\n        } catch (UnsupportedOperationException e) {\n            throw new RestException(Status.CONFLICT, e.getMessage());\n        }\n    }\n\n    /**\n     * @deprecated Use {@link #getNamespacePoliciesAsync(NamespaceName)} instead.\n     */\n    @Deprecated\n    protected Policies getNamespacePolicies(NamespaceName namespaceName) {\n        try {","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java#L277-L313","documentation":"Returned by validatePartitionedTopicMetadataAsync when the addressed topic exists in metadata but has partitions < 1 — i.e. it is a non-partitioned topic (or a partitioned topic with zero partitions). Endpoints that only operate on partitioned topics (e.g. partition-level admin ops like getPartitions, update-partitions) reject the request with HTTP 409 Conflict.","triggerScenarios":"Calling partitioned-only admin endpoints (GET .../partitions, POST .../updatePartitions, DELETE partitioned topic) against a topic created as a plain non-partitioned topic, or after partitions were set to 0.","commonSituations":"Assuming every topic is partitioned in shared tooling; a topic was re-created as non-partitioned after an earlier cleanup; code paths that must handle both topic kinds but only guard one.","solutions":["Check the topic's metadata first (GET /admin/v2/persistent/tenant/ns/topic/partitions or namespaces getPartitionedTopics list) and only call partitioned APIs on partitioned topics.","If the topic should be partitioned, delete the non-partitioned topic and create it with the desired number of partitions (or use updatePartitionedTopic if it exists with partitions).","Update calling code to branch between partitioned and non-partitioned handling using the topic's metadata.","Use the admin client's topics().getPartitionedTopicMetadata(...) which handles this check gracefully."],"exampleFix":"// before\nadmin.topics().getPartitionedTopicMetadata(\"tenant/ns/orders\"); // 409 if not partitioned\n// after\nPartitionedTopicMetadata md = admin.topics().getPartitionedTopicMetadata(\"tenant/ns/orders\");\nif (md.partitions < 1) {\n    admin.topics().createPartitionedTopic(\"tenant/ns/orders\", 4);\n}","handlingStrategy":"type-guard","validationCode":"// Java: check partition metadata before partitioned-only calls\nPartitionedTopicMetadata md = admin.topics().getPartitionedTopicMetadata(fqTopic);\nif (md.partitions >= 1) { /* safe to call partitioned APIs */ }","typeGuard":"boolean isPartitioned(PartitionedTopicMetadata md) { return md != null && md.partitions >= 1; }","tryCatchPattern":"try {\n    admin.topics().getPartitionedInfo(fqTopic);\n} catch (PulsarAdminException e) {\n    if (e.getStatusCode() == 409) {\n        // not partitioned: fall back to non-partitioned handling\n    }\n}","preventionTips":["Query partition metadata before any partitioned-only admin call.","Handle both topic kinds in generic topic tooling.","Track topic kind in your provisioning inventory to avoid drift."],"tags":["http-409","partitioned-topic","admin-api"],"backgroundTag":"topic-not-partitioned","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"}