{"record":{"id":"9de84dbd1d80d1bf","repo":"apache/pulsar","slug":"topic-name-is-not-valid","errorCode":null,"errorMessage":"Topic name is not valid","messagePattern":"Topic name is not valid","errorType":"http","errorClass":"org.apache.pulsar.broker.admin.RestException","httpStatus":412,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java","lineNumber":247,"sourceCode":"                    .attr(\"namespace\", namespaceName)\n                    .exceptionMessage(e)\n                    .log(\"Failed to validate global cluster configuration\");\n            throw new RestException(Status.SERVICE_UNAVAILABLE, \"Failed to validate global cluster configuration\");\n        }\n    }\n    protected void validateTopicName(String tenant, String namespace, String encodedTopic) {\n        String topic = Codec.decode(encodedTopic);\n        try {\n            this.namespaceName = NamespaceName.get(tenant, namespace);\n            this.topicName = TopicName.get(domain(), namespaceName, topic);\n        } catch (IllegalArgumentException e) {\n            log.warn()\n                    .attr(\"domain\", domain())\n                    .attr(\"tenant\", tenant)\n                    .attr(\"namespace\", namespace)\n                    .attr(\"topic\", topic)\n                    .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        }","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java#L229-L265","documentation":"Thrown by validateTopicName when the fully-qualified topic name built from tenant/namespace/localName does not parse as a valid Pulsar topic name. Pulsar enforces naming rules (allowed characters, structure like tenant/namespace/topic) before any topic operation proceeds, returning HTTP 412 Precondition Failed.","triggerScenarios":"Calling persistent/non-persistent topic REST endpoints with a topic local name containing illegal characters (e.g. spaces, '*'), an empty local name, or a malformed encoded topic portion, e.g. PUT /admin/v2/persistent/my-tenant/my-ns/bad topic or topics/... with 'my-topic%2F' escapes that break parsing.","commonSituations":"Client code interpolating un-sanitized user input into topic URLs; missing URL-encoding of special characters; accidentally passing the full topic URL instead of just the local name; legacy clients using characters disallowed by newer Pulsar name validation.","solutions":["Inspect the topic portion of the failing URL and remove/replace invalid characters (allowed: alphanumerics, '-', '_', '.', and proper ':' scheme prefix handling).","URL-encode the topic local name when building the REST request (e.g. URLEncoder.encode).","Pass only the local topic name (tenant and namespace are separate path segments), not a full topic URL.","If using the Java client, use TopicName.get(...) to validate the name client-side before calling admin APIs."],"exampleFix":"// before\nString topic = \"orders/eu 2024\";\nadmin.topics().createPartitionedTopic(topic, 4);\n// after\nString topic = \"orders-eu-2024\";\nadmin.topics().createPartitionedTopic(topic, 4);","handlingStrategy":"validation","validationCode":"// Java: validate the topic name before calling the REST API\nimport org.apache.pulsar.common.naming.TopicName;\npublic static boolean isValidTopicLocalName(String tenant, String ns, String local) {\n    try {\n        TopicName.get(\"persistent\", tenant, ns, local);\n        return true;\n    } catch (IllegalArgumentException e) {\n        return false;\n    }\n}","typeGuard":"static boolean safeTopicName(String t) {\n    return t != null && !t.isBlank() && t.matches(\"[A-Za-z0-9._:-]+\") && !t.contains(\" \");\n}","tryCatchPattern":"try {\n    admin.topics().getStats(fqTopic);\n} catch (PulsarAdminException e) {\n    if (e.getStatusCode() == 412) {\n        log.error(\"Invalid topic name: {}\", fqTopic);\n    }\n}","preventionTips":["URL-encode topic local names when building REST paths.","Reject user-supplied topic names at the application boundary with a regex whitelist.","Never interpolate full topic URIs into admin client calls; pass tenant/ns/local separately.","Add unit tests for topic-name sanitization."],"tags":["http-412","topic-name","validation","admin-api"],"backgroundTag":"invalid-topic-name","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"}