{"record":{"id":"0329c653a8301348","repo":"apache/pulsar","slug":"cannot-create-topic-in-system-topic-format","errorCode":null,"errorMessage":"Cannot create topic in system topic format!","messagePattern":"Cannot create topic in system topic format!","errorType":"http","errorClass":"org.apache.pulsar.broker.admin.RestException","httpStatus":400,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java","lineNumber":264,"sourceCode":"                    .log(\"Invalid topic name\");\n            throw new RestException(Status.PRECONDITION_FAILED, \"Topic name is not valid\");\n        }\n    }\n\n    /**\n     * Validates that a topic can be created.\n     *\n     * <p>This is the single source of truth for topic-creation name validation shared by every admin create\n     * endpoint (persistent, non-persistent and scalable topics). Rejecting here keeps topics which could never be\n     * reached (e.g. because clients trim topic names) from being created. The transaction-internal-name rule is\n     * gated on {@link TopicDomain#persistent} so it stays specific to persistent topics, while the whitespace\n     * validation applies uniformly to all topic types.\n     */\n    protected void validateCreateTopic(TopicName topicName) {\n        if (topicName.getDomain() == TopicDomain.persistent\n                && SystemTopicNames.isTransactionInternalName(topicName)) {\n            log.warn().attr(\"topic\", topicName).log(\"Forbidden to create transaction internal topic\");\n            throw new RestException(Status.BAD_REQUEST, \"Cannot create topic in system topic format!\");\n        }\n        try {\n            TopicName.validateTopicNameForCreation(topicName);\n        } catch (IllegalArgumentException e) {\n            log.warn().attr(\"topic\", topicName).log(\"Forbidden to create topic with an invalid name\");\n            throw new RestException(Status.PRECONDITION_FAILED, e.getMessage());\n        }\n    }\n\n    protected void validatePersistentTopicName(String tenant, String namespace, String encodedTopic) {\n        validateTopicName(tenant, namespace, encodedTopic);\n        if (topicName.getDomain() != TopicDomain.persistent) {\n            throw new RestException(Status.NOT_ACCEPTABLE, \"Need to provide a persistent topic name\");\n        }\n    }\n\n    protected void validatePartitionedTopicName(String tenant, String namespace, String encodedTopic) {\n        // first, it has to be a validate topic name","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java#L246-L282","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Exclude topics whose local name starts with the reserved system-topic prefixes (e.g. __transaction_) from your creation script.","Skip system topics when enumerating source topics: filter with SystemTopicNames.isTransactionInternalName or equivalent before create.","Choose a different topic name for your application data.","Do not manually manage transaction internal topics; they are created automatically when transactions are enabled."],"exampleFix":"// before\nfor (String t : exportedTopics) {\n    admin.topics().createNonPartitionedTopic(t); // fails on __transaction_coordinator_assign\n}\n// after\nfor (String t : exportedTopics) {\n    if (!t.contains(\"__transaction_\")) {\n        admin.topics().createNonPartitionedTopic(t);\n    }\n}","handlingStrategy":"validation","validationCode":"// Java: skip reserved system topic names before creating\nString[] reservedPrefixes = {\"__transaction_\", \"__change_events\", \"__compaction\"};\nboolean isReserved(String local) {\n    return local.startsWith(\"__transaction_\");\n}","typeGuard":"static boolean isUserTopic(String fqTopic) {\n    String local = fqTopic.substring(fqTopic.lastIndexOf('/') + 1);\n    return !local.startsWith(\"__transaction_\");\n}","tryCatchPattern":"try {\n    admin.topics().createNonPartitionedTopic(topic);\n} catch (PulsarAdminException e) {\n    if (e.getStatusCode() == 400 || e.getStatusCode() == 412) {\n        log.warn(\"Skipping reserved/invalid topic name: {}\", topic); // don't fail the batch\n    }\n}","preventionTips":["Filter SystemTopicNames-prefixed topics out of any topic-replay/migration script.","Treat names starting with '__' as broker-reserved in your naming convention.","Never hand-create transaction coordinator topics; enable transactions instead.","Document reserved prefixes for your platform users."],"tags":["http-400","system-topic","validation","admin-api"],"backgroundTag":"reserved-system-topic-name","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"}