{"record":{"id":"6b6723f3274e23c4","repo":"apache/pulsar","slug":"s-is-a-non-partitioned-topic-instead-of-calling","errorCode":null,"errorMessage":"%s is a non-partitioned topic. Instead of calling delete-partitioned-topic please call delete.","messagePattern":"(.+?) is a non-partitioned topic\\. Instead of calling delete-partitioned-topic please call delete\\.","errorType":"http","errorClass":"RestException","httpStatus":409,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java","lineNumber":831,"sourceCode":"                        throw new RestException(Status.NOT_FOUND, getTopicNotFoundErrorMessage(topicName.toString()));\n                    }\n                });\n    }\n\n    protected void internalDeletePartitionedTopic(AsyncResponse asyncResponse,\n                                                  boolean authoritative,\n                                                  boolean force) {\n        validateNamespaceOperationAsync(topicName.getNamespaceObject(), NamespaceOperation.DELETE_TOPIC)\n                .thenCompose(__ -> validateTopicOwnershipAsync(topicName, authoritative))\n                .thenCompose(__ -> pulsar().getBrokerService()\n                        .fetchPartitionedTopicMetadataAsync(topicName)\n                        .thenCompose(partitionedMeta -> {\n                            final int numPartitions = partitionedMeta.partitions;\n                            if (numPartitions < 1) {\n                                return pulsar().getNamespaceService().checkNonPartitionedTopicExists(topicName)\n                                        .thenApply(exists -> {\n                                    if (exists) {\n                                        throw new RestException(Response.Status.CONFLICT,\n                                                String.format(\"%s is a non-partitioned topic. Instead of calling\"\n                                                        + \" delete-partitioned-topic please call delete.\", topicName));\n                                    } else {\n                                        throw new RestException(Status.NOT_FOUND,\n                                                String.format(\"Topic %s not found.\", topicName));\n                                    }\n                                });\n                            }\n                            return internalRemovePartitionsAuthenticationPoliciesAsync()\n                                    .thenCompose(unused -> internalRemovePartitionsTopicAsync(numPartitions, force));\n                        })\n                // Only tries to delete the znode for partitioned topic when all its partitions are successfully deleted\n                ).thenCompose(ignore ->\n                        pulsar().getBrokerService().deleteSchema(topicName).exceptionally(ex -> null)\n                ).thenCompose(ignore ->\n                        pulsar().getTopicPoliciesService().deleteTopicPoliciesAsync(topicName).exceptionally(ex -> null)\n                ).thenCompose(__ -> getPulsarResources().getNamespaceResources().getPartitionedTopicResources()\n                        .runWithMarkDeleteAsync(topicName, () -> namespaceResources()","sourceCodeStart":813,"sourceCodeEnd":849,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java#L813-L849","documentation":"HTTP 409 CONFLICT raised in internalDeletePartitionedTopic when the partitioned metadata reports partitions < 1 but the name actually resolves to an existing non-partitioned topic. The delete-partitioned-topic admin endpoint only applies to partitioned topics; calling it on a plain topic would be a mistake, so the broker conflicts and tells the caller to use the normal delete endpoint instead.","triggerScenarios":"DELETE /admin/v2/persistent/{ns}/{topic}/partitionedTopic (or `pulsar-admin topics delete-partitioned-topic`) on a topic created with `topics create` (non-partitioned), i.e. numPartitions is 0 while the topic itself exists.","commonSituations":"Script assumes every topic is partitioned; topic was converted/created non-partitioned by another tool; using delete-partitioned-topic to clean up plain topics; typo where a non-partitioned twin of the name exists.","solutions":["Use the plain delete endpoint instead: `pulsar-admin topics delete <topic>` (DELETE /admin/v2/persistent/{ns}/{topic}).","Confirm the topic's partition count with `pulsar-admin topics get-partitioned-topic-metadata <topic>` before choosing the delete variant.","Fix automation to branch on metadata.partitions > 0 and call the matching delete API.","If a partitioned topic was intended, delete the non-partitioned topic first and recreate it with -p N."],"exampleFix":"// before\npulsar-admin topics delete-partitioned-topic my-tenant/my-ns/my-topic   // 409 non-partitioned\n// after\npulsar-admin topics delete my-tenant/my-ns/my-topic","handlingStrategy":"validation","validationCode":"PartitionedTopicMetadata meta = admin.topics().getPartitionedTopicMetadata(topic);\nif (meta.partitions > 0) {\n    admin.topics().deletePartitionedTopic(topic, force);\n} else {\n    admin.topics().delete(topic, force);\n}","typeGuard":null,"tryCatchPattern":"try {\n    admin.topics().deletePartitionedTopic(topic);\n} catch (PulsarAdminException e) {\n    if (e.getStatusCode() == 409) {\n        admin.topics().delete(topic); // non-partitioned\n    } else throw e;\n}","preventionTips":["Check partitioned metadata before choosing a delete endpoint","Branch automation on partitions > 0","Never assume all topics in a namespace are partitioned"],"tags":["pulsar","rest-api","conflict","wrong-api-endpoint"],"backgroundTag":"wrong-delete-endpoint-for-non-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"}