{"record":{"id":"61f6f128dfbf8666","repo":"apache/pulsar","slug":"partitioned-topic-name-should-not-contain-partit-61f6f1","errorCode":null,"errorMessage":"Partitioned Topic Name should not contain '-partition-'","messagePattern":"Partitioned Topic Name should not contain '-partition-'","errorType":"http","errorClass":"RestException","httpStatus":412,"severity":"warning","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/NonPersistentTopics.java","lineNumber":279,"sourceCode":"            @Parameter(description = \"Get per partition stats\")\n            @QueryParam(\"perPartition\") @DefaultValue(\"true\") boolean perPartition,\n            @Parameter(description = \"Whether leader broker redirected this call to this broker. For internal use.\")\n            @QueryParam(\"authoritative\") @DefaultValue(\"false\") boolean authoritative,\n            @Parameter(description = \"If return precise backlog or imprecise backlog\")\n            @QueryParam(\"getPreciseBacklog\") @DefaultValue(\"false\") boolean getPreciseBacklog,\n            @Parameter(description = \"If return backlog size for each subscription, require locking on ledger so be \"\n                    + \"careful not to use when there's heavy traffic.\")\n            @QueryParam(\"subscriptionBacklogSize\") @DefaultValue(\"false\") boolean subscriptionBacklogSize,\n            @Parameter(description = \"If return the earliest time in backlog\")\n            @QueryParam(\"getEarliestTimeInBacklog\") @DefaultValue(\"false\") boolean getEarliestTimeInBacklog,\n            @Parameter(description = \"If exclude the publishers\")\n            @QueryParam(\"excludePublishers\") @DefaultValue(\"false\") boolean excludePublishers,\n            @Parameter(description = \"If exclude the consumers\")\n            @QueryParam(\"excludeConsumers\") @DefaultValue(\"false\") boolean excludeConsumers) {\n        try {\n            validateTopicName(tenant, namespace, encodedTopic);\n            if (topicName.isPartitioned()) {\n                throw new RestException(Response.Status.PRECONDITION_FAILED,\n                        \"Partitioned Topic Name should not contain '-partition-'\");\n            }\n            try {\n                validateGlobalNamespaceOwnership(namespaceName);\n            } catch (Exception e) {\n                log.error()\n                        .attr(\"topic\", topicName)\n                        .exception(e)\n                        .log(\"Failed to get partitioned stats\");\n                resumeAsyncResponseExceptionally(asyncResponse, e);\n                return;\n            }\n            getPartitionedTopicMetadataAsync(topicName,\n                    authoritative, false).thenAccept(partitionMetadata -> {\n                if (partitionMetadata.partitions == 0) {\n                    asyncResponse.resume(new RestException(Status.NOT_FOUND,\n                            String.format(\"Partitioned topic not found %s\", topicName.toString())));\n                    return;","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/NonPersistentTopics.java#L261-L297","documentation":"The broker admin REST endpoint getPartitionedStats for non-persistent topics rejects requests whose topic name contains the '-partition-' segment (i.e. the caller passed an individual partition of a partitioned topic instead of the base partitioned topic name). Partition-level stats are only reachable via the parent partitioned topic, so the broker fails fast with HTTP 412 PRECONDITION_FAILED.","triggerScenarios":"Calling GET /admin/v2/namespaces/{tenant}/{namespace}/{non-persistent-topic}/partitioned-stats with encodedTopic containing '-partition-N' (e.g. persistent:// is not used here; my-topic-partition-0).","commonSituations":"Scripts or dashboards that iterate over individual partitions discovered from stats output and then query partitioned stats per partition; client code that confuses topicName.toString() of a partition with the partitioned topic name.","solutions":["Use the base partitioned topic name (strip the '-partition-N' suffix) when calling the partitioned-stats endpoint","To inspect a single partition, use the regular per-topic stats endpoint instead of the partitioned-stats one","Strip suffix client-side, e.g. name.replaceAll(\"-partition-\\\\d+$\", \"\") before the call"],"exampleFix":"// before\nGET /admin/v2/namespaces/public/default/my-topic-partition-0/partitioned-stats\n// after\nGET /admin/v2/namespaces/public/default/my-topic/partitioned-stats","handlingStrategy":"validation","validationCode":"if (topic.contains(\"-partition-\")) throw new IllegalArgumentException(\"Use base partitioned topic name, not a partition: \" + topic);\nString base = topic.replaceAll(\"-partition-\\\\d+$\", \"\");","typeGuard":"boolean isBasePartitionedTopic(String t) { return t != null && !t.matches(\".*-partition-\\\\d+$\"); }","tryCatchPattern":"try { stats = admin.topics().getPartitionedStats(baseTopic, ...); } catch (PulsarAdminException e) { if (e.getStatusCode() == 412) { /* fix topic name */ } throw e; }","preventionTips":["Always derive the partitioned-topic handle by stripping '-partition-N' suffixes","Keep partition-level queries on the per-topic stats endpoint","Validate topic names before calling partitioned admin APIs"],"tags":["rest-api","pulsar-admin","topic-partitions"],"backgroundTag":"partitioned-topic-name-rejected","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"}