{"record":{"id":"b8d2cee42d957778","repo":"apache/pulsar","slug":"topic-name-is-not-valid-b8d2ce","errorCode":null,"errorMessage":"Topic name is not valid","messagePattern":"Topic name is not valid","errorType":"http","errorClass":"RestException","httpStatus":412,"severity":"warning","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/TransactionsBase.java","lineNumber":541,"sourceCode":"           throw new RestException(SERVICE_UNAVAILABLE,\n                    \"This Broker is not configured with transactionCoordinatorEnabled=true.\");\n        }\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(TopicDomain.persistent.toString(), 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                    .exception(e)\n                    .log(\"Failed to validate topic name\");\n            throw new RestException(Response.Status.PRECONDITION_FAILED, \"Topic name is not valid\");\n        }\n    }\n\n    protected CompletableFuture<Void> internalScaleTransactionCoordinators(int replicas) {\n        return validateSuperUserAccessAsync()\n                .thenCompose((ignore) -> namespaceResources().getPartitionedTopicResources()\n                        .updatePartitionedTopicAsync(SystemTopicNames.TRANSACTION_COORDINATOR_ASSIGN, p -> {\n                            if (p.partitions >= replicas) {\n                                throw new RestException(Response.Status.NOT_ACCEPTABLE,\n                                        \"Number of transaction coordinators should \"\n                                                + \"be more than the current number of transaction coordinator\");\n                            }\n                            return new PartitionedTopicMetadata(replicas);\n                        }));\n    }\n\n    protected CompletableFuture<PositionInPendingAckStats> internalGetPositionStatsPendingAckStats(\n            boolean authoritative, String subName, Position position, Integer batchIndex) {","sourceCodeStart":523,"sourceCodeEnd":559,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/TransactionsBase.java#L523-L559","documentation":"HTTP 412 PRECONDITION_FAILED from validateTopicName: the encoded topic string supplied to a transaction admin endpoint could not be parsed into a valid topic (CompleteTopicName/TopicName parsing threw). The broker logs 'Failed to validate topic name' with tenant/namespace/topic details.","triggerScenarios":"Transaction admin calls (e.g. transaction pending-ack stats, coordinator endpoints taking a topic parameter) where the topic argument is malformed: missing persistent/non-persistent domain, missing local-name, bad tenant/namespace segment, or not URL-encoded special characters.","commonSituations":"Passing a short topic name (topic-only) where a fully-qualified persistent://tenant/namespace/topic is expected; URL-encoding issues with '/' in path parameters; partitioned vs non-partitioned confusion (appending -partition-N); mixing up tenants/namespaces in the topic string.","solutions":["Use the fully qualified topic name: persistent://tenant/namespace/topic (or non-persistent://...), URL-encoded when in a path.","Validate the topic name with TopicName.get(topic) client-side or org.apache.pulsar.common.naming utilities before calling the API.","Check for encoding problems: encode '/' as %2F if the endpoint expects a single path segment, or use the query/REST variant that takes separate tenant/ns/topic.","Remove any partition suffix if the endpoint expects the base topic, or add it if it expects the specific partition."],"exampleFix":"// before\nString topic = \"my-topic\"; // not fully qualified\nadmin.transactions().getPendingAckStats(\"tenant\", \"ns\", topic);\n// after\nString topic = \"persistent://tenant/ns/my-topic\";\nadmin.transactions().getPendingAckStats(\"tenant\", \"ns\", topic);","handlingStrategy":"validation","validationCode":"import org.apache.pulsar.common.naming.TopicName;\n\nstatic boolean isValidTopicName(String topic) {\n    try {\n        TopicName.get(topic);\n        return true;\n    } catch (Exception e) {\n        return false;\n    }\n}\n// use: isValidTopicName(\"persistent://tenant/ns/my-topic\")","typeGuard":"static boolean isFullyQualifiedTopic(String topic) {\n    return topic != null && (topic.startsWith(\"persistent://\") || topic.startsWith(\"non-persistent://\"))\n        && topic.split(\"://\", 2)[1].split(\"/\").length >= 3;\n}","tryCatchPattern":"try {\n    admin.transactions().getPendingAckStats(tenant, ns, topic);\n} catch (PulsarAdminException e) {\n    if (e.getStatusCode() == 412 && e.getMessage().contains(\"Topic name is not valid\")) {\n        String fqTopic = \"persistent://\" + tenant + \"/\" + ns + \"/\" + topic.replaceFirst(\"^persistent://[^/]+/[^/]+/\", \"\");\n        // retry with fully qualified, URL-encoded name\n    } else {\n        throw e;\n    }\n}","preventionTips":["Always pass fully qualified persistent://tenant/namespace/topic names to transaction admin APIs.","URL-encode topic parameters when embedding them in REST paths.","Validate with TopicName.get() (org.apache.pulsar.common.naming) before calling the API.","Watch for accidental partition suffixes like -partition-0 in the topic argument."],"tags":["validation","topic-name","rest-api","transactions"],"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-14T00:17:10.932Z"}