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

Cannot create topic in system topic format!

Error message

Cannot create topic in system topic format!

What it means

Thrown by validateCreateTopic when a client attempts to create a topic whose name collides with an internal system topic (transaction coordinator internal topics such as persistent://<ns>/__transaction_coordinator_assign or __transaction_buffer_snapshot style names) or otherwise violates creation-name rules. Pulsar reserves these names for internal machinery and rejects creation with HTTP 400/412.

Source

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

                    .log("Invalid topic name");
            throw new RestException(Status.PRECONDITION_FAILED, "Topic name is not valid");
        }
    }

    /**
     * Validates that a topic can be created.
     *
     * <p>This is the single source of truth for topic-creation name validation shared by every admin create
     * endpoint (persistent, non-persistent and scalable topics). Rejecting here keeps topics which could never be
     * reached (e.g. because clients trim topic names) from being created. The transaction-internal-name rule is
     * gated on {@link TopicDomain#persistent} so it stays specific to persistent topics, while the whitespace
     * validation applies uniformly to all topic types.
     */
    protected void validateCreateTopic(TopicName topicName) {
        if (topicName.getDomain() == TopicDomain.persistent
                && SystemTopicNames.isTransactionInternalName(topicName)) {
            log.warn().attr("topic", topicName).log("Forbidden to create transaction internal topic");
            throw new RestException(Status.BAD_REQUEST, "Cannot create topic in system topic format!");
        }
        try {
            TopicName.validateTopicNameForCreation(topicName);
        } catch (IllegalArgumentException e) {
            log.warn().attr("topic", topicName).log("Forbidden to create topic with an invalid name");
            throw new RestException(Status.PRECONDITION_FAILED, e.getMessage());
        }
    }

    protected void validatePersistentTopicName(String tenant, String namespace, String encodedTopic) {
        validateTopicName(tenant, namespace, encodedTopic);
        if (topicName.getDomain() != TopicDomain.persistent) {
            throw new RestException(Status.NOT_ACCEPTABLE, "Need to provide a persistent topic name");
        }
    }

    protected void validatePartitionedTopicName(String tenant, String namespace, String encodedTopic) {
        // first, it has to be a validate topic name

View on GitHub (pinned to 820761864e)

Solutions

  1. Exclude topics whose local name starts with the reserved system-topic prefixes (e.g. __transaction_) from your creation script.
  2. Skip system topics when enumerating source topics: filter with SystemTopicNames.isTransactionInternalName or equivalent before create.
  3. Choose a different topic name for your application data.
  4. Do not manually manage transaction internal topics; they are created automatically when transactions are enabled.

Example fix

// before
for (String t : exportedTopics) {
    admin.topics().createNonPartitionedTopic(t); // fails on __transaction_coordinator_assign
}
// after
for (String t : exportedTopics) {
    if (!t.contains("__transaction_")) {
        admin.topics().createNonPartitionedTopic(t);
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Java: skip reserved system topic names before creating
String[] reservedPrefixes = {"__transaction_", "__change_events", "__compaction"};
boolean isReserved(String local) {
    return local.startsWith("__transaction_");
}

Type guard

static boolean isUserTopic(String fqTopic) {
    String local = fqTopic.substring(fqTopic.lastIndexOf('/') + 1);
    return !local.startsWith("__transaction_");
}

Try / catch

try {
    admin.topics().createNonPartitionedTopic(topic);
} catch (PulsarAdminException e) {
    if (e.getStatusCode() == 400 || e.getStatusCode() == 412) {
        log.warn("Skipping reserved/invalid topic name: {}", topic); // don't fail the batch
    }
}

Prevention

When it happens

Trigger: Explicitly creating a persistent topic whose name matches SystemTopicNames.isTransactionInternalName, e.g. PUT /admin/v2/persistent/my-tenant/my-ns/__transaction_coordinator_assign; or creating a topic name rejected by TopicName.validateTopicNameForCreation (412).

Common situations: Scripts replaying topic lists captured from a namespace that include system topics; tooling migrating topics between clusters and blindly recreating everything; users inventing topic names that collide with reserved transaction-internal names.

Related errors


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