{"record":{"id":"c001b8dedd63e918","repo":"apache/pulsar","slug":"clusterids-should-not-be-null-or-empty","errorCode":null,"errorMessage":"ClusterIds should not be null or empty","messagePattern":"ClusterIds should not be null or empty","errorType":"http","errorClass":"RestException","httpStatus":412,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/NamespacesBase.java","lineNumber":857,"sourceCode":"    }\n\n    /**\n     * Directly get the replication clusters for a namespace, without checking allowed clusters.\n     */\n    protected CompletableFuture<Set<String>> internalGetNamespaceReplicationClustersAsync() {\n        return validateNamespacePolicyOperationAsync(namespaceName, PolicyName.REPLICATION, PolicyOperation.READ)\n                .thenCompose(__ -> getNamespacePoliciesAsync(namespaceName))\n                .thenApply(policies -> policies.replication_clusters);\n    }\n\n    @SuppressWarnings(\"checkstyle:WhitespaceAfter\")\n    protected CompletableFuture<Void> internalSetNamespaceReplicationClusters(List<String> clusterIds,\n                                                                              boolean compareTopicPartitions) {\n        return validateNamespacePolicyOperationAsync(namespaceName, PolicyName.REPLICATION, PolicyOperation.WRITE)\n                .thenCompose(__ -> validatePoliciesReadOnlyAccessAsync())\n                .thenApply(__ -> {\n                    if (CollectionUtils.isEmpty(clusterIds)) {\n                        throw new RestException(Status.PRECONDITION_FAILED, \"ClusterIds should not be null or empty\");\n                    }\n                    return Sets.newHashSet(clusterIds);\n                }).thenCompose(replicationClusterSet -> clustersAsync()\n                        .thenCompose(clusters -> {\n                            List<CompletableFuture<Void>> futures =\n                                    replicationClusterSet.stream().map(clusterId -> {\n                                        if (!clusters.contains(clusterId)) {\n                                            throw new RestException(Status.FORBIDDEN,\n                                                    \"Invalid cluster id: \" + clusterId);\n                                        }\n                                        return validatePeerClusterConflictAsync(clusterId, replicationClusterSet)\n                                            .thenCompose(__ -> getNamespacePoliciesAsync(this.namespaceName)\n                                                .thenCompose(nsPolicies -> {\n                                                    if (!Policies.checkNewReplicationClusters(nsPolicies,\n                                                            replicationClusterSet)) {\n                                                        String msg = String.format(\"Cluster [%s] is not in the \"\n                                                                + \"list of allowed clusters list for namespace \"\n                                                                + \"[%s]\", clusterId, namespaceName.toString());","sourceCodeStart":839,"sourceCodeEnd":875,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/NamespacesBase.java#L839-L875","documentation":"HTTP 412 (PRECONDITION_FAILED) returned by the set-replication-clusters admin API when the clusterIds list is null or empty. A namespace must always replicate to at least one cluster, so clearing the list entirely is rejected before any policy write occurs.","triggerScenarios":"POST /namespaces/{tenant}/{namespace}/replication with a null body field or an empty JSON array for clusterIds, via internalSetNamespaceReplicationClusters.","commonSituations":"Automation templating that renders an empty cluster list (unset config value); code that computes clusters dynamically and produces []; attempting to 'disable' replication by sending an empty list.","solutions":["Pass at least one valid cluster id in clusterIds.","Fix the template/config that resolved to an empty list — set a default local cluster.","To change replication, supply the new full list (e.g. [local]) rather than emptying it.","Add client-side preflight: reject empty clusterIds before calling the API."],"exampleFix":"// before\nsetReplicationClusters(ns, clusters); // clusters = []\n// after\nif (clusters == null || clusters.isEmpty()) {\n    clusters = List.of(localClusterId); // never empty\n}\nsetReplicationClusters(ns, clusters);","handlingStrategy":"validation","validationCode":"if (clusterIds == null || clusterIds.isEmpty()) {\n    throw new IllegalArgumentException(\"clusterIds must contain at least one cluster\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    admin.namespaces().setNamespaceReplicationClusters(ns, clusterIds);\n} catch (PulsarAdminException.PreconditionFailedException e) {\n    log.error(\"Refusing empty replication cluster set for {}\", ns);\n}","preventionTips":["Always include at least the local cluster in replication config","Add non-empty assertions in automation before calling the API","Fix templates that render empty cluster lists from unset config values","Never use an empty list to 'disable' replication — set a fallback cluster instead"],"tags":["rest-api","admin","validation","replication"],"backgroundTag":"invalid-parameter-empty","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}