apache/pulsar · error · RestException

%s is a non-partitioned topic. Instead of calling delete-par

Error message

%s is a non-partitioned topic. Instead of calling delete-partitioned-topic please call delete.

What it means

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.

Source

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

                        throw new RestException(Status.NOT_FOUND, getTopicNotFoundErrorMessage(topicName.toString()));
                    }
                });
    }

    protected void internalDeletePartitionedTopic(AsyncResponse asyncResponse,
                                                  boolean authoritative,
                                                  boolean force) {
        validateNamespaceOperationAsync(topicName.getNamespaceObject(), NamespaceOperation.DELETE_TOPIC)
                .thenCompose(__ -> validateTopicOwnershipAsync(topicName, authoritative))
                .thenCompose(__ -> pulsar().getBrokerService()
                        .fetchPartitionedTopicMetadataAsync(topicName)
                        .thenCompose(partitionedMeta -> {
                            final int numPartitions = partitionedMeta.partitions;
                            if (numPartitions < 1) {
                                return pulsar().getNamespaceService().checkNonPartitionedTopicExists(topicName)
                                        .thenApply(exists -> {
                                    if (exists) {
                                        throw new RestException(Response.Status.CONFLICT,
                                                String.format("%s is a non-partitioned topic. Instead of calling"
                                                        + " delete-partitioned-topic please call delete.", topicName));
                                    } else {
                                        throw new RestException(Status.NOT_FOUND,
                                                String.format("Topic %s not found.", topicName));
                                    }
                                });
                            }
                            return internalRemovePartitionsAuthenticationPoliciesAsync()
                                    .thenCompose(unused -> internalRemovePartitionsTopicAsync(numPartitions, force));
                        })
                // Only tries to delete the znode for partitioned topic when all its partitions are successfully deleted
                ).thenCompose(ignore ->
                        pulsar().getBrokerService().deleteSchema(topicName).exceptionally(ex -> null)
                ).thenCompose(ignore ->
                        pulsar().getTopicPoliciesService().deleteTopicPoliciesAsync(topicName).exceptionally(ex -> null)
                ).thenCompose(__ -> getPulsarResources().getNamespaceResources().getPartitionedTopicResources()
                        .runWithMarkDeleteAsync(topicName, () -> namespaceResources()

View on GitHub (pinned to 820761864e)

Solutions

  1. Use the plain delete endpoint instead: `pulsar-admin topics delete <topic>` (DELETE /admin/v2/persistent/{ns}/{topic}).
  2. Confirm the topic's partition count with `pulsar-admin topics get-partitioned-topic-metadata <topic>` before choosing the delete variant.
  3. Fix automation to branch on metadata.partitions > 0 and call the matching delete API.
  4. If a partitioned topic was intended, delete the non-partitioned topic first and recreate it with -p N.

Example fix

// before
pulsar-admin topics delete-partitioned-topic my-tenant/my-ns/my-topic   // 409 non-partitioned
// after
pulsar-admin topics delete my-tenant/my-ns/my-topic
Defensive patterns

Strategy: validation

Validate before calling

PartitionedTopicMetadata meta = admin.topics().getPartitionedTopicMetadata(topic);
if (meta.partitions > 0) {
    admin.topics().deletePartitionedTopic(topic, force);
} else {
    admin.topics().delete(topic, force);
}

Try / catch

try {
    admin.topics().deletePartitionedTopic(topic);
} catch (PulsarAdminException e) {
    if (e.getStatusCode() == 409) {
        admin.topics().delete(topic); // non-partitioned
    } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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