apache/pulsar · error · RestException

Cannot delete non empty namespace

Error message

Cannot delete non empty namespace

What it means

This is a 409 CONFLICT thrown by the broker's namespace deletion flow when a namespace still contains non-system topics and the delete was not requested with force=true. The broker protects against accidental data loss: it inventories all topics (including partitioned and system topics) in the namespace and refuses to delete the namespace while regular user topics remain. System topics are exempt, but any non-system topic causes the abort.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/NamespacesBase.java:343

                                            allSystemTopics.add(topic);
                                        }
                                    }
                                }
                                for (String topic : allPartitionedTopics) {
                                    if (!pulsar().getBrokerService().isSystemTopic(TopicName.get(topic))) {
                                        hasNonSystemTopic = true;
                                        allUserCreatedPartitionTopics.add(topic);
                                    } else {
                                        if (SystemTopicNames.isTopicPoliciesSystemTopic(topic)) {
                                            partitionedTopicPolicy.add(topic);
                                        } else {
                                            allPartitionedSystemTopics.add(topic);
                                        }
                                    }
                                }
                                if (!force) {
                                    if (hasNonSystemTopic) {
                                        throw new RestException(Status.CONFLICT, "Cannot delete non empty namespace");
                                    }
                                }
                                final CompletableFuture<Void> markDeleteFuture;
                                if (policies != null && policies.deleted) {
                                    markDeleteFuture = CompletableFuture.completedFuture(null);
                                } else {
                                    markDeleteFuture = namespaceResources().setPoliciesAsync(namespaceName, old -> {
                                        old.deleted = true;
                                        return old;
                                    });
                                }
                                return markDeleteFuture.thenCompose(__ ->
                                                internalDeleteTopicsAsync(allUserCreatedTopics))
                                        .thenCompose(ignore ->
                                                internalDeletePartitionedTopicsAsync(allUserCreatedPartitionTopics))
                                        .thenCompose(ignore ->
                                                internalDeleteTopicsAsync(allSystemTopics))
                                        .thenCompose(ignore ->

View on GitHub (pinned to 820761864e)

Solutions

  1. List and delete all topics in the namespace first: GET /admin/v2/namespaces/{tenant}/{namespace}/topics then DELETE each (use persistent/non-persistent and partitioned endpoints).
  2. Retry the namespace delete with force=true, which requires the broker config isForceDeleteNamespaceAllowed=true (see error 91).
  3. Check for auto-topic-creation being enabled on the namespace and disable it so topics are not recreated between listing and deletion.
  4. Verify no lingering system/non-partitioned topic leftovers by re-listing topics after deletion before retrying.

Example fix

// before
curl -X DELETE http://broker:8080/admin/v2/namespaces/my-tenant/my-ns
// 409 Cannot delete non empty namespace

// after
curl -X DELETE http://broker:8080/admin/v2/namespaces/my-tenant/my-ns/topics
# delete each topic, then:
curl -X DELETE http://broker:8080/admin/v2/namespaces/my-tenant/my-ns
Defensive patterns

Strategy: validation

Validate before calling

const topics = await admin.namespaces().getTopics ? null : null;
const all = [
  ...(await admin.namespaces().getPersistentTopics(tenant, ns)),
  ...(await admin.namespaces().getNonPersistentTopics ? [] : [])
];
const userTopics = all.filter(t => !t.includes('__transaction') && !t.includes('_change_events'));
if (userTopics.length > 0) throw new Error(`namespace ${ns} still has ${userTopics.length} topics`);

Try / catch

try {
  await admin.namespaces().deleteNamespace(tenant, ns);
} catch (e) {
  if (e instanceof PulsarAdminException && e.getStatusCode() === 409) {
    // drain topics then retry
  } else throw e;
}

Prevention

When it happens

Trigger: Calling DELETE /admin/v2/namespaces/{tenant}/{namespace} (NamespacesBase.internalDeleteNamespaceAsync -> internalRetryableDeleteNamespaceAsync0) while the namespace still has non-system topics, with force=false (the default). Also occurs when force delete is retried but topic deletion has not fully propagated yet.

Common situations: Developers tearing down tenants in CI and forgetting to delete topics first; lingering topics from producers/consumers that auto-created topics; partitioned topics whose partitions were deleted but metadata remains; testing force-delete locally without realizing isForceDeleteNamespaceAllowed=false on the broker.

Related errors


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