{"record":{"id":"71cec5cb8b9f41a7","repo":"apache/pulsar","slug":"need-to-provide-a-persistent-topic-name","errorCode":null,"errorMessage":"Need to provide a persistent topic name","messagePattern":"Need to provide a persistent topic name","errorType":"http","errorClass":"org.apache.pulsar.broker.admin.RestException","httpStatus":406,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java","lineNumber":277,"sourceCode":"     */\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\n        validateTopicName(tenant, namespace, encodedTopic);\n        // second, \"-partition-\" is not allowed\n        if (encodedTopic.contains(TopicName.PARTITIONED_TOPIC_SUFFIX)) {\n            throw new RestException(Status.PRECONDITION_FAILED,\n                    \"Partitioned Topic Name should not contain '-partition-'\");\n        }\n    }\n\n    protected CompletableFuture<Void> validatePartitionedTopicMetadataAsync() {\n        return pulsar().getBrokerService().fetchPartitionedTopicMetadataAsync(topicName)\n                .thenAccept(metadata -> {\n                    if (metadata.partitions < 1) {\n                        throw new RestException(Status.CONFLICT, \"Topic is not partitioned topic\");","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java#L259-L295","documentation":"Thrown by validatePersistentTopicName when the addressed topic parses correctly but its domain is non-persistent (or otherwise not TopicDomain.persistent). The REST resource was created for persistent topics only, so a non-persistent:// name is rejected with HTTP 406 Not Acceptable.","triggerScenarios":"Calling a persistent-topic admin endpoint (e.g. GET /admin/v2/persistent/tenant/ns/topic stats or subscriptions) while passing the topic under a path or with a name resolving to domain non-persistent, e.g. mixing up /admin/v2/persistent/ with a topic that actually is non-persistent://tenant/ns/topic.","commonSituations":"Applications configured with 'non-persistent' topic URIs but using persistent admin endpoints; copy-paste of topic URIs between persistent and non-persistent sections of admin tooling; clients that omit the scheme and get a default mismatch.","solutions":["Use the /admin/v2/non-persistent/... endpoint family for non-persistent topics instead of /admin/v2/persistent/...","Check the topic URI in your configuration: the domain must be 'persistent' for these endpoints.","If the topic was intended to be persistent, create/access it with the persistent:// domain prefix.","Verify client config keys (e.g. topic names in properties/YAML) don't prefix 'non-persistent://'."],"exampleFix":"// before\nadmin.topics().getStats(\"non-persistent://my-tenant/my-ns/orders\");\n// after\nadmin.topics().getStats(\"persistent://my-tenant/my-ns/orders\");","handlingStrategy":"validation","validationCode":"// Java: ensure domain matches the endpoint family\nboolean isPersistent(String fq) { return fq.startsWith(\"persistent://\"); }\n// only call admin.topics() (persistent endpoints) if isPersistent(fq)","typeGuard":"static String toPersistentUri(String fq) {\n    return fq.startsWith(\"non-persistent://\")\n        ? fq.replace(\"non-persistent://\", \"persistent://\") : fq;\n}","tryCatchPattern":"try {\n    admin.topics().getStats(fqTopic);\n} catch (PulsarAdminException e) {\n    if (e.getStatusCode() == 406) {\n        // wrong domain: reroute to non-persistent API or fix the URI\n    }\n}","preventionTips":["Store the topic domain explicitly in config and derive the endpoint family from it.","Avoid mixing persistent/non-persistent topic URIs in shared config keys.","Use the admin client's domain-scoped interfaces rather than hand-built REST paths."],"tags":["http-406","topic-domain","validation","admin-api"],"backgroundTag":"wrong-topic-domain","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"}