{"record":{"id":"78b4d2e083ee5377","repo":"apache/pulsar","slug":"this-topic-already-exists","errorCode":null,"errorMessage":"This topic already exists","messagePattern":"This topic already exists","errorType":"http","errorClass":"org.apache.pulsar.broker.admin.RestException","httpStatus":409,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java","lineNumber":614,"sourceCode":"                                                + \" in namespace\");\n                                throw new RestException(Status.PRECONDITION_FAILED,\n                                        \"Exceed maximum number of topics in namespace.\");\n                            }\n                        });\n                    }\n                    return CompletableFuture.completedFuture(null);\n                })\n                .thenCompose(__ -> checkTopicExistsAsync(topicName))\n                .thenAccept(topicExistsInfo -> {\n                    try {\n                        if (topicExistsInfo.isExists()) {\n                            if (topicExistsInfo.getTopicType().equals(TopicType.NON_PARTITIONED)\n                                    || (topicExistsInfo.getTopicType().equals(TopicType.PARTITIONED)\n                                    && !createLocalTopicOnly)) {\n                                log.warn()\n                                        .attr(\"topic\", topicName)\n                                        .log(\"Failed to create already existing topic\");\n                                throw new RestException(Status.CONFLICT, \"This topic already exists\");\n                            }\n                        }\n                    } finally {\n                        topicExistsInfo.recycle();\n                    }\n                })\n                .thenCompose(__ -> getMaxPartitionIndex(topicName)\n                        .thenAccept(existingMaxPartitionIndex -> {\n                            // Case 1: Metadata loss — user tries to recreate the partitioned topic.\n                            // Case 2: Non-partitioned topic — user attempts to convert it to a partitioned topic.\n                            if (existingMaxPartitionIndex >= numPartitions) {\n                                int requiredMinPartitions = existingMaxPartitionIndex + 1;\n                                throw new RestException(Status.CONFLICT, String.format(\n                                        \"The topic has a max partition index of %d, the number of partitions must be \"\n                                                + \"at least %d\",\n                                        existingMaxPartitionIndex, requiredMinPartitions));\n                            }\n                        }))","sourceCodeStart":596,"sourceCodeEnd":632,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java#L596-L632","documentation":"Returned by internalCreatePartitionedTopic when the topic already exists as a non-partitioned topic, or as a partitioned topic while createLocalTopicOnly is false — i.e. the requested create would conflict with an existing topic of incompatible kind. Mapped to HTTP 409 Conflict. Pulsar refuses to silently convert between non-partitioned and partitioned topics.","triggerScenarios":"createPartitionedTopic on a name that already exists as a non-partitioned topic; re-running createPartitionedTopic with createLocalTopicOnly=false when the partitioned topic already exists (idempotent re-create attempts without the create-if-missing flag).","commonSituations":"Automation that re-creates topics on deploy without checking existence; a topic was previously created non-partitioned and now must be partitioned; retry storms after a partially-failed create that actually succeeded.","solutions":["Check existence first with topics().getPartitionedTopicMetadata(...) and skip create if it already exists (catch NotFound).","If it exists as non-partitioned but should be partitioned, migrate: drain and delete the non-partitioned topic, then create it partitioned (there is no in-place conversion).","Use the admin API's allow-auto-create / createLocalTopicOnly semantics or the client's createIfMissing style call for idempotent provisioning.","Treat 409 as success in idempotent provisioning scripts rather than an error."],"exampleFix":"// before\nadmin.topics().createPartitionedTopic(\"my-tenant/my-ns/orders\", 8); // 409 on redeploy\n// after\ntry {\n    admin.topics().createPartitionedTopic(\"my-tenant/my-ns/orders\", 8);\n} catch (PulsarAdminException.ConflictException e) {\n    // topic already provisioned — ignore\n}","handlingStrategy":"try-catch","validationCode":"// Java: idempotent create — check before creating\nPartitionedTopicMetadata md;\ntry {\n    md = admin.topics().getPartitionedTopicMetadata(fqTopic);\n} catch (PulsarAdminException.NotFoundException e) {\n    md = null;\n}\nif (md == null) { admin.topics().createPartitionedTopic(fqTopic, n); }","typeGuard":null,"tryCatchPattern":"try {\n    admin.topics().createPartitionedTopic(fqTopic, n);\n} catch (PulsarAdminException e) {\n    if (e.getStatusCode() == 409) {\n        // already provisioned — treat as success in idempotent flows\n    } else {\n        throw e;\n    }\n}","preventionTips":["Make topic provisioning idempotent: check-then-create or swallow 409.","Never silently convert a non-partitioned topic to partitioned — migrate explicitly.","Record provisioning state so retries don't re-create with different settings."],"tags":["http-409","topic-exists","partitioned-topic","admin-api"],"backgroundTag":"topic-already-exists","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"}