{"record":{"id":"d32c2ad5c247d244","repo":"apache/pulsar","slug":"broker-is-forbidden-to-do-read-write-operations","errorCode":null,"errorMessage":"Broker is forbidden to do read-write operations","messagePattern":"Broker is forbidden to do read-write operations","errorType":"http","errorClass":"org.apache.pulsar.broker.admin.RestException","httpStatus":403,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java","lineNumber":149,"sourceCode":"     * @throws WebApplicationException\n     *             if broker has a read only access if broker is not connected to the configuration metadata-store\n     */\n\n    public void validatePoliciesReadOnlyAccess() {\n        try {\n            validatePoliciesReadOnlyAccessAsync().join();\n        } catch (CompletionException ce) {\n            throw new RestException(ce.getCause());\n        }\n    }\n\n    public CompletableFuture<Void> validatePoliciesReadOnlyAccessAsync() {\n\n        return pulsar().getPulsarResources().getNamespaceResources().getPoliciesReadOnlyAsync()\n                .thenAccept(arePoliciesReadOnly -> {\n                    if (arePoliciesReadOnly) {\n                            log.debug(\"Policies are read-only. Broker cannot do read-write operations\");\n                                                throw new RestException(Status.FORBIDDEN,\n                                                        \"Broker is forbidden to do read-write operations\");\n                    } else {\n                        // Do nothing, just log the message.\n                            log.debug(\"Broker is allowed to make read-write operations\");\n                                            }\n                });\n    }\n\n    protected CompletableFuture<Void> tryCreatePartitionsAsync(int numPartitions) {\n        if (!topicName.isPersistent()) {\n            for (int i = 0; i < numPartitions; i++) {\n                pulsar().getBrokerService().getTopicEventsDispatcher()\n                        .notify(topicName.getPartition(i).toString(), TopicEvent.CREATE, EventStage.SUCCESS);\n            }\n            return CompletableFuture.completedFuture(null);\n        }\n        List<CompletableFuture<Void>> futures = new ArrayList<>(numPartitions);\n        for (int i = 0; i < numPartitions; i++) {","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java#L131-L167","documentation":"When a broker is configured with read-only policies (e.g. the cluster is in a read-only/migrating state where namespace policies are marked read-only), AdminResource.validatePoliciesReadOnlyAccessAsync rejects any mutating admin operation with HTTP 403 'Broker is forbidden to do read-write operations'. This protects a broker whose policy store must not be modified (commonly during center/global policy synchronization setups).","triggerScenarios":"Any write admin API call (create/update namespace policies, set permissions, etc.) while the namespace policies are flagged read-only in the local policy store (policiesReadOnly), typically when the broker reads policies from a global/read-only configuration store.","commonSituations":"Clusters configured with a read-only global zookeeper/configuration store (e.g. geo-replication control plane setups); operating against a broker intended only to serve, not administer; running admin scripts against the wrong cluster after a failover.","solutions":["Perform the write operation on a broker/cluster whose configuration store is writable (the correct administrative cluster).","Change broker configuration so the policies store is writable (point configurationStore/policies to a writable store and remove read-only mode), then restart brokers.","If the read-only state is stale, fix the underlying policies read-only flag in the configuration store.","For read-only deployments, use the designated management endpoint rather than this broker."],"exampleFix":"// before (broker.conf)\nconfigurationStore=zk1:2181/global-readonly\n# broker cannot write policies\n\n// after (broker.conf)\nconfigurationStore=zk-admin:2181/global\n# writable configuration store; restart brokers","handlingStrategy":"try-catch","validationCode":"// Check cluster writability before admin writes\nboolean readOnly = admin.namespaces().getPolicies(tenant + \"/\" + ns) != null\n        && brokerConfig.isConfigurationStoreReadOnly(); // from your deployment config\nif (readOnly) throw new IllegalStateException(\"Broker policies are read-only; perform writes elsewhere\");","typeGuard":null,"tryCatchPattern":"try {\n    admin.namespaces().createNamespace(ns);\n} catch (PulsarAdminException e) {\n    if (e.getStatusCode() == 403 && e.getMessage().contains(\"read-write operations\")) {\n        log.error(\"Broker is in read-only policies mode; run admin ops against the writable cluster\");\n    }\n}","preventionTips":["Point admin tooling at the cluster with a writable configuration store","Track which brokers are read-only in deployment config and gate writes in scripts","After failover, re-point admin clients before running mutations"],"tags":["admin-api","authorization","read-only","configuration"],"backgroundTag":"broker-read-only-mode","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}