{"record":{"id":"e5e56eea704af604","repo":"apache/pulsar","slug":"scalable-topic-not-found-tn","errorCode":null,"errorMessage":"Scalable topic not found: ${tn}","messagePattern":"Scalable topic not found: (.+?)","errorType":"http","errorClass":"RestException","httpStatus":404,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/ScalableTopics.java","lineNumber":668,"sourceCode":"            @ApiResponse(responseCode = \"500\", description = \"Internal server error\")})\n    public void deleteScalableTopic(\n            @Suspended final AsyncResponse asyncResponse,\n            @Parameter(description = \"Specify the tenant\", required = true)\n            @PathParam(\"tenant\") String tenant,\n            @Parameter(description = \"Specify the namespace\", required = true)\n            @PathParam(\"namespace\") String namespace,\n            @Parameter(description = \"Specify topic name\", required = true)\n            @PathParam(\"topic\") @Encoded String encodedTopic,\n            @Parameter(description = \"Force deletion even if topic has active subscriptions\")\n            @QueryParam(\"force\") @DefaultValue(\"false\") boolean force) {\n        validateNamespaceName(tenant, namespace);\n        TopicName tn = TopicName.get(TopicDomain.topic.value(), namespaceName, encodedTopic);\n\n        validateNamespaceOperationAsync(namespaceName, NamespaceOperation.DELETE_TOPIC)\n                .thenCompose(__ -> resources().getScalableTopicMetadataAsync(tn))\n                .thenCompose(optMd -> {\n                    if (optMd.isEmpty()) {\n                        throw new RestException(Response.Status.NOT_FOUND,\n                                \"Scalable topic not found: \" + tn);\n                    }\n                    // Delete metadata first, then best-effort clean up segment topics\n                    return resources().deleteScalableTopicAsync(tn)\n                            .thenCompose(__ -> deleteSegmentTopics(tn, optMd.get(), force));\n                })\n                .thenAccept(__ -> {\n                    log.info().attr(\"clientAppId\", clientAppId()).attr(\"topic\", tn)\n                            .log(\"Deleted scalable topic\");\n                    asyncResponse.resume(Response.noContent().build());\n                })\n                .exceptionally(ex -> {\n                    log.error().attr(\"clientAppId\", clientAppId()).attr(\"topic\", tn)\n                            .exception(ex).log(\"Failed to delete scalable topic\");\n                    resumeAsyncResponseExceptionally(asyncResponse, ex);\n                    return null;\n                });\n    }","sourceCodeStart":650,"sourceCodeEnd":686,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/ScalableTopics.java#L650-L686","documentation":"HTTP 404 thrown by the scalable-topic delete endpoint when no scalable-topic metadata exists for the named topic — meaning the topic is not a scalable topic (or was already deleted). Deletion only proceeds on topics registered as scalable in the metadata store.","triggerScenarios":"Calling DELETE /admin/v2/scalable-topics/{tenant}/{namespace}/{topic} for a topic that was never created as scalable, an already-deleted scalable topic (double delete), or a regular/partitioned topic passed by mistake.","commonSituations":"Cleanup scripts deleting a whole namespace's topics using the wrong admin resource (scalable-topics vs topics); idempotent deletion retries after a first successful delete; typos in the topic name.","solutions":["Verify the topic is a scalable topic (GET its scalable metadata) before deleting, and skip/short-circuit if absent.","Use the regular topics delete API for non-scalable topics.","Treat 404 as success in idempotent cleanup automation."],"exampleFix":"// before\nawait admin.scalableTopics().deleteScalableTopic(tenant, ns, topic); // throws 404 on retry\n// after\nif (await admin.scalableTopics().getScalableTopicMetadataAsync(tenant, ns, topic).isPresent()) {\n    await admin.scalableTopics().deleteScalableTopic(tenant, ns, topic);\n}","handlingStrategy":"validation","validationCode":"const md = await admin.scalableTopics().getScalableTopicMetadataAsync(tenant, ns, topic);\nif (md.isEmpty()) return; // not a scalable topic / already deleted — nothing to delete","typeGuard":"const isScalableTopic = (metadataOpt) => metadataOpt != null && metadataOpt.isPresent();","tryCatchPattern":"try {\n  await admin.scalableTopics().deleteScalableTopic(tenant, ns, topic, /*force*/ true);\n} catch (e) {\n  if (e.status === 404) return; // idempotent delete\n  throw e;\n}","preventionTips":["Route deletions by topic kind: scalable-topics API for scalable topics, topics API for regular ones.","Implement cleanup as idempotent (treat 404 as success).","Confirm the topic name/tenant/namespace before bulk deletes."],"tags":["rest","not-found","scalable-topics","http-404"],"backgroundTag":"topic-not-found","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"}