{"record":{"id":"ed5c68434eb461e9","repo":"apache/pulsar","slug":"topic-does-not-exist-persistentbase","errorCode":null,"errorMessage":"Topic does not exist: ${persistentBase}","messagePattern":"Topic does not exist: (.+?)","errorType":"http","errorClass":"RestException","httpStatus":404,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/ScalableTopics.java","lineNumber":331,"sourceCode":"     *       watch);</li>\n     *   <li>terminate the old topics so no further v4 writes can land — they become the\n     *       drainable sealed parent segments.</li>\n     * </ol>\n     */\n    private CompletableFuture<Void> doMigrateToScalableAsync(TopicName scalableName,\n                                                             TopicName persistentBase, boolean force) {\n        return resources().getScalableTopicMetadataAsync(scalableName).thenCompose(existing -> {\n            if (existing.isPresent()) {\n                throw new RestException(Response.Status.CONFLICT,\n                        \"Topic is already scalable: \" + scalableName);\n            }\n            return pulsar().getNamespaceService().checkTopicExistsAsync(persistentBase);\n        }).thenCompose(existsInfo -> {\n            boolean exists = existsInfo.isExists();\n            int partitions = existsInfo.getPartitions();\n            existsInfo.recycle();\n            if (!exists) {\n                throw new RestException(Response.Status.NOT_FOUND,\n                        \"Topic does not exist: \" + persistentBase);\n            }\n            CompletableFuture<Void> precheck = force\n                    ? CompletableFuture.completedFuture(null)\n                    : checkNoLegacyConnectionsAsync(persistentBase, partitions);\n            return precheck.thenApply(__ -> partitions);\n        }).thenCompose(partitions -> {\n            ScalableTopicMetadata metadata =\n                    ScalableTopicController.createMigratedMetadata(persistentBase, partitions,\n                            pulsar().getConfiguration().getScalableTopicEntryBucketBudget(),\n                            pulsar().getConfiguration().getScalableTopicEntryBucketMaxPerSegment());\n            return createMigratedChildTopicsAsync(scalableName, metadata)\n                    .thenCompose(__ -> resources().createScalableTopicAsync(scalableName, metadata))\n                    .thenCompose(__ -> terminateLegacyTopicsAsync(persistentBase, partitions));\n        });\n    }\n\n    /**","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/ScalableTopics.java#L313-L349","documentation":"HTTP 404 raised during migration when the source regular (persistent) topic named by the path does not exist. Migration transforms an existing regular topic into a scalable one, so the source must exist before the prechecks can run.","triggerScenarios":"Calling POST /admin/v2/scalable-topics/{tenant}/{namespace}/{topic}/migrate when the underlying persistent topic was never created, was deleted, or the name is misspelled/mis-partitioned (e.g. wrong partition index).","commonSituations":"Typos in topic names in migration scripts; migrating a topic whose producers never connected (auto-creation disabled so nothing materialized); migrating after a cleanup job deleted the topic; wrong namespace/tenant in the URL.","solutions":["Verify the topic exists with GET /admin/v2/persistent/{tenant}/{namespace}/{topic} (or topics list) before migrating.","Correct the topic name, partition suffix, tenant, or namespace in the migration call.","Create the topic first if it never existed, or skip migration entirely for non-existent sources."],"exampleFix":"// before\nawait admin.scalableTopics().migrateToScalable(tenant, ns, 'my-topc');\n// after\nconst exists = await admin.topics().getStatsAsync('persistent://tn/ns/my-topic');\nawait admin.scalableTopics().migrateToScalable(tenant, ns, 'my-topic');","handlingStrategy":"validation","validationCode":"try {\n  await admin.topics().getStatsAsync(`persistent://${tenant}/${ns}/${topic}`);\n} catch (e) {\n  if (e.status === 404) throw new Error(`Source topic ${topic} does not exist; fix name or skip`);\n  throw e;\n}","typeGuard":"const topicExists = async (tn) => { try { await admin.topics().getStatsAsync(tn); return true; } catch (e) { return e.status !== 404; } };","tryCatchPattern":"try {\n  await admin.scalableTopics().migrateToScalable(tenant, ns, topic);\n} catch (e) {\n  if (e.status === 404) { /* verify source topic name/existence before retry */ }\n  throw e;\n}","preventionTips":["Resolve topic names from the namespace's topic list rather than hand-typing them.","Verify tenant/namespace/topic spelling and partition suffixes in migration scripts.","Skip non-existent source topics explicitly instead of letting the migration fail."],"tags":["rest","not-found","migration","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-14T00:17:10.932Z"}