apache/pulsar · error · RestException

${duplicateBrokers} already exists in ${domainName}

Error message

${duplicateBrokers} already exists in ${domainName}

What it means

HTTP 409 (CONFLICT) thrown by setFailureDomain when one or more brokers requested for a new/updated failure domain are already members of another failure domain in the same cluster. The handler loads the other domain (domainName), intersects its broker list with the requested brokers, and reports the duplicates in the message. A broker may belong to only one failure domain per cluster.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/ClustersBase.java:1202

        if (inputDomain == null || inputDomain.brokers == null) {
            return CompletableFuture.completedFuture(null);
        }
        return clusterResources().getFailureDomainResources()
                .listFailureDomainsAsync(cluster)
                .thenCompose(domainNames -> {
                    List<CompletableFuture<Void>> futures = domainNames.stream()
                            .filter(domainName -> !domainName.equals(inputDomainName))
                            .map(domainName -> clusterResources()
                                    .getFailureDomainResources().getFailureDomainAsync(cluster, domainName)
                                    .thenAccept(failureDomainOpt -> {
                                        if (failureDomainOpt.isPresent()
                                                && CollectionUtils.isNotEmpty(failureDomainOpt.get().getBrokers())) {
                                            List<String> duplicateBrokers = failureDomainOpt.get()
                                                    .getBrokers().stream().parallel()
                                                    .filter(inputDomain.brokers::contains)
                                                    .collect(Collectors.toList());
                                            if (CollectionUtils.isNotEmpty(duplicateBrokers)) {
                                                throw new RestException(Status.CONFLICT,
                                                        duplicateBrokers + " already exists in " + domainName);
                                            }
                                        }
                                    }).exceptionally(ex -> {
                                        Throwable realCause = FutureUtil.unwrapCompletionException(ex);
                                        if (realCause instanceof WebApplicationException) {
                                            throw FutureUtil.wrapToCompletionException(ex);
                                        }
                                        if (realCause instanceof NotFoundException) {
                                                log.debug()
                                                        .exception(ex)
                                                        .log("Domain is not configured for cluster");
                                                                                        return null;
                                        }
                                        log.warn().attr("domain", domainName).exception(ex).log("Failed to get domain");
                                        return null;
                                    })
                            ).collect(Collectors.toList());

View on GitHub (pinned to 820761864e)

Solutions

  1. Remove the listed brokers from their current failure domain (POST the old domain without those brokers) before assigning them.
  2. Update the target domain's broker list to exclude brokers that already belong to another domain, then move them one step at a time.
  3. Inspect existing domains via GET /admin/v3/clusters/{cluster}/failureDomains and each domain's brokers to plan a non-overlapping assignment.
  4. If the old domain should no longer exist, delete it (DELETE .../failureDomains/{oldDomain}) then re-submit the new domain.

Example fix

// before
FailureDomainData d = FailureDomainData.builder()
        .brokers(List.of("b1:8080", "b2:8080")).build(); // b1 already in domainA -> 409
admin.clusters().setFailureDomain("c1", "domainB", d);
// after
FailureDomainData old = admin.clusters().getFailureDomain("c1", "domainA");
admin.clusters().setFailureDomain("c1", "domainA", FailureDomainData.builder()
        .brokers(old.getBrokers().stream().filter(b -> !b.equals("b1:8080")).collect(Collectors.toList())).build());
admin.clusters().setFailureDomain("c1", "domainB", d);
Defensive patterns

Strategy: validation

Validate before calling

// build the set of brokers already assigned to other domains
Set<String> assigned = new HashSet<>();
for (String domain : admin.clusters().getFailureDomains(cluster)) {
    if (!domain.equals(targetDomain)) {
        assigned.addAll(admin.clusters().getFailureDomain(cluster, domain).getBrokers());
    }
}
List<String> dupes = requestedBrokers.stream().filter(assigned::contains).collect(Collectors.toList());
if (!dupes.isEmpty()) {
    throw new IllegalArgumentException("Brokers already in another domain: " + dupes);
}
admin.clusters().setFailureDomain(cluster, targetDomain,
        FailureDomainData.builder().brokers(requestedBrokers).build());

Try / catch

try {
    admin.clusters().setFailureDomain(cluster, domainName, domainData);
} catch (PulsarAdminException.ConflictException e) {
    // message lists duplicate brokers: detach them from their current domain first
}

Prevention

When it happens

Trigger: POST /admin/v3/clusters/{cluster}/failureDomains/{domainName} whose brokers list overlaps brokers already assigned to a different domain; moving brokers between domains without first removing them from the old domain; partial prior update that left brokers registered elsewhere.

Common situations: Reorganization of failure domains during hardware rebalancing; scripts re-adding brokers without deduplicating against existing domains; recovery after an aborted domain reconfiguration.

Related errors


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