{"record":{"id":"7e0c1c6d6df64bb9","repo":"apache/pulsar","slug":"create-subscription-on-non-persistent-topic-can-on","errorCode":null,"errorMessage":"Create subscription on non-persistent topic can only be done through client","messagePattern":"Create subscription on non-persistent topic can only be done through client","errorType":"http","errorClass":"RestException","httpStatus":400,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/PersistentTopics.java","lineNumber":1989,"sourceCode":"                    ResetCursorData resetCursorData,\n            @Parameter(description = \"Is replicated required to perform this operation\")\n            @QueryParam(\"replicated\") boolean replicated\n    ) {\n        try {\n            validateTopicName(tenant, namespace, topic);\n            String decodedSubName = decode(encodedSubName);\n            // If subscription is as \"a/b\". The url of HTTP API that defined as\n            // \"{tenant}/{namespace}/{topic}/{subscription}\" will be like below:\n            // \"public/default/tp/a/b\", then the broker will assume it is a topic that\n            // using the old rule \"{tenant}/{cluster}/{namespace}/{topic}/{subscription}\".\n            // So denied to create a subscription that contains \"/\".\n            if (pulsar().getConfig().isStrictlyVerifySubscriptionName()\n                    && !NamedEntity.isAllowed(decodedSubName)) {\n                throw new RestException(Response.Status.BAD_REQUEST, \"Please let the subscription only contains\"\n                    + \" '/w(a-zA-Z_0-9)' or '_', the current value is \" + decodedSubName);\n            }\n            if (!topicName.isPersistent()) {\n                throw new RestException(Response.Status.BAD_REQUEST, \"Create subscription on non-persistent topic \"\n                        + \"can only be done through client\");\n            }\n            Map<String, String> subscriptionProperties = resetCursorData == null ? null :\n                    resetCursorData.getProperties();\n            MessageIdImpl messageId = resetCursorData == null ? null :\n                    new MessageIdImpl(resetCursorData.getLedgerId(), resetCursorData.getEntryId(),\n                            resetCursorData.getPartitionIndex());\n            internalCreateSubscription(asyncResponse, decode(encodedSubName), messageId, authoritative,\n                    replicated, subscriptionProperties);\n        } catch (WebApplicationException wae) {\n            asyncResponse.resume(wae);\n        } catch (Exception e) {\n            asyncResponse.resume(new RestException(e));\n        }\n    }\n\n    @POST\n    @Path(\"/{tenant}/{namespace}/{topic}/subscription/{subName}/resetcursor/{timestamp}\")","sourceCodeStart":1971,"sourceCodeEnd":2007,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/PersistentTopics.java#L1971-L2007","documentation":"createSubscription only works for persistent topics; for non-persistent topics subscriptions must be created through the client (they are ephemeral and not managed via cursors in the metadata store), so the admin endpoint returns HTTP 400.","triggerScenarios":"PUT /admin/v2/{tenant}/{namespace}/{topic}/subscription/{subName} (non-persistent endpoint variant) with topicName.isPersistent() == false.","commonSituations":"Generic admin scripts that use the same subscription-creation code path for all topics; migrations from persistent to non-persistent topics; using PulsarAdmin against a non-persistent topic URL.","solutions":["Create the subscription by connecting a consumer/reader client with that subscription name to the non-persistent topic","Switch to the persistent topic endpoint only for persistent topics","Reconsider topic persistence if durable admin-managed subscriptions are required"],"exampleFix":"// before\nadmin.topics().createSubscription(\"non-persistent://public/default/t\", \"sub\", MessageId.earliest);\n// after\ntry (Consumer<byte[]> c = client.newConsumer().topic(\"non-persistent://public/default/t\")\n        .subscriptionName(\"sub\").subscriptionInitialPosition(InitialPosition.Earliest).subscribe()) {}","handlingStrategy":"validation","validationCode":"if (topic.startsWith(\"non-persistent://\")) { createSubscriptionViaClient(topic, sub); return; }","typeGuard":"boolean isAdminSubscriptionCreationAllowed(String t) { return t != null && t.startsWith(\"persistent://\"); }","tryCatchPattern":"try { admin.topics().createSubscription(topic, sub, pos); } catch (PulsarAdminException e) { if (e.getStatusCode() == 400) { /* fall back to client consumer creation */ } }","preventionTips":["Branch on topic persistence before calling the admin subscription API","Create non-persistent subscriptions with a consumer client","Use readers/consumers with InitialPosition.Earliest as the fallback"],"tags":["rest-api","subscription","non-persistent"],"backgroundTag":"invalid-operation-for-non-persistent-topic","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"}