apache/pulsar · error · RestException

Cannot delete non empty bundle

Error message

Cannot delete non empty bundle

What it means

A 409 CONFLICT thrown by internalDeleteNamespaceBundleAsync when the bundle being deleted still owns at least one topic: for each topic in the namespace, the broker resolves its bundle via NamespaceService.getBundleAsync and aborts if any topic maps to the target bundle. This prevents deleting a bundle that would strand live topics.

Source

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

                                });
                    }
                    return future
                            .thenCompose(__ ->
                                    validateNamespaceBundleOwnershipAsync(namespaceName, bundleRange,
                                            authoritative, true))
                            .thenCompose(bundle -> {
                                return pulsar().getNamespaceService().getListOfPersistentTopics(namespaceName)
                                        .thenCompose(topics -> {
                                            CompletableFuture<Void> deleteTopicsFuture =
                                                    CompletableFuture.completedFuture(null);
                                            if (!force) {
                                                List<CompletableFuture<NamespaceBundle>> futures = new ArrayList<>();
                                                for (String topic : topics) {
                                                    futures.add(pulsar().getNamespaceService()
                                                            .getBundleAsync(TopicName.get(topic))
                                                            .thenCompose(topicBundle -> {
                                                                if (bundle.equals(topicBundle)) {
                                                                    throw new RestException(Status.CONFLICT,
                                                                            "Cannot delete non empty bundle");
                                                                }
                                                                return CompletableFuture.completedFuture(null);
                                                            }));

                                                }
                                                deleteTopicsFuture = FutureUtil.waitForAll(futures);
                                            }
                                            return deleteTopicsFuture.thenCompose(
                                                            ___ -> pulsar().getNamespaceService()
                                                                    .removeOwnedServiceUnitAsync(bundle))
                                                    .thenRun(() -> pulsar().getBrokerService().getBundleStats()
                                                            .remove(bundle.toString()));
                                        });
                            });
                });
    }

View on GitHub (pinned to 820761864e)

Solutions

  1. Unload the bundle first: PUT /admin/v2/namespaces/{tenant}/{namespace}/{bundle}/unload, then delete it.
  2. Delete or migrate all topics in the bundle, verify with GET .../namespaces/{tenant}/{namespace}/topics, then delete the bundle.
  3. Use clear-backlog and disconnect-clients on the bundle before deletion to remove active ownership.
  4. Retry after a short delay if topics were just deleted — bundle ownership update may lag.

Example fix

// before
curl -X DELETE http://broker:8080/admin/v2/namespaces/t/ns/0x00000000_0x40000000
// 409 Cannot delete non empty bundle

// after
curl -X PUT http://broker:8080/admin/v2/namespaces/t/ns/0x00000000_0x40000000/unload
curl -X DELETE http://broker:8080/admin/v2/namespaces/t/ns/0x00000000_0x40000000
Defensive patterns

Strategy: try-catch

Validate before calling

const topics = await admin.namespaces().getTopics(tenant, ns); // persistent+non-persistent
for (const t of topics) {
  const bundleOfTopic = await admin.lookups().getBundleRange ? null : null;
}
// simplest pre-check: ensure topic list is empty before deleting the bundle
if (topics.length > 0) throw new Error('topics still present; unload/delete before bundle delete');

Try / catch

try {
  await admin.namespaces().deleteNamespaceBundle(tenant, ns, bundle);
} catch (e) {
  if (e.getStatusCode() === 409) {
    await admin.namespaces().unloadNamespaceBundle(tenant, ns, bundle);
    await admin.namespaces().deleteNamespaceBundle(tenant, ns, bundle);
  } else throw e;
}

Prevention

When it happens

Trigger: DELETE /admin/v2/namespaces/{tenant}/{namespace}/{bundle} while topics are still assigned to that bundle. Also occurs when a topic was just created (auto-creation) or a lookup pinned the topic into the bundle between listing and deletion.

Common situations: Operators trying to force-unload a namespace by deleting bundles without unloading topics first; sticky lookups from active consumers recreating bundle ownership; race in scripts that delete topics then immediately delete bundles before ownership clears.

Related errors


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