apache/pulsar · error · org.apache.pulsar.broker.admin.RestException

This topic already exists

Error message

This topic already exists

What it means

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.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java:614

                                                + " in namespace");
                                throw new RestException(Status.PRECONDITION_FAILED,
                                        "Exceed maximum number of topics in namespace.");
                            }
                        });
                    }
                    return CompletableFuture.completedFuture(null);
                })
                .thenCompose(__ -> checkTopicExistsAsync(topicName))
                .thenAccept(topicExistsInfo -> {
                    try {
                        if (topicExistsInfo.isExists()) {
                            if (topicExistsInfo.getTopicType().equals(TopicType.NON_PARTITIONED)
                                    || (topicExistsInfo.getTopicType().equals(TopicType.PARTITIONED)
                                    && !createLocalTopicOnly)) {
                                log.warn()
                                        .attr("topic", topicName)
                                        .log("Failed to create already existing topic");
                                throw new RestException(Status.CONFLICT, "This topic already exists");
                            }
                        }
                    } finally {
                        topicExistsInfo.recycle();
                    }
                })
                .thenCompose(__ -> getMaxPartitionIndex(topicName)
                        .thenAccept(existingMaxPartitionIndex -> {
                            // Case 1: Metadata loss — user tries to recreate the partitioned topic.
                            // Case 2: Non-partitioned topic — user attempts to convert it to a partitioned topic.
                            if (existingMaxPartitionIndex >= numPartitions) {
                                int requiredMinPartitions = existingMaxPartitionIndex + 1;
                                throw new RestException(Status.CONFLICT, String.format(
                                        "The topic has a max partition index of %d, the number of partitions must be "
                                                + "at least %d",
                                        existingMaxPartitionIndex, requiredMinPartitions));
                            }
                        }))

View on GitHub (pinned to 820761864e)

Solutions

  1. Check existence first with topics().getPartitionedTopicMetadata(...) and skip create if it already exists (catch NotFound).
  2. 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).
  3. Use the admin API's allow-auto-create / createLocalTopicOnly semantics or the client's createIfMissing style call for idempotent provisioning.
  4. Treat 409 as success in idempotent provisioning scripts rather than an error.

Example fix

// before
admin.topics().createPartitionedTopic("my-tenant/my-ns/orders", 8); // 409 on redeploy
// after
try {
    admin.topics().createPartitionedTopic("my-tenant/my-ns/orders", 8);
} catch (PulsarAdminException.ConflictException e) {
    // topic already provisioned — ignore
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Java: idempotent create — check before creating
PartitionedTopicMetadata md;
try {
    md = admin.topics().getPartitionedTopicMetadata(fqTopic);
} catch (PulsarAdminException.NotFoundException e) {
    md = null;
}
if (md == null) { admin.topics().createPartitionedTopic(fqTopic, n); }

Try / catch

try {
    admin.topics().createPartitionedTopic(fqTopic, n);
} catch (PulsarAdminException e) {
    if (e.getStatusCode() == 409) {
        // already provisioned — treat as success in idempotent flows
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


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