apache/pulsar · error · RestException
Create subscription on non-persistent topic can only be done
Error message
Create subscription on non-persistent topic can only be done through client
What it means
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.
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/PersistentTopics.java:1989
ResetCursorData resetCursorData,
@Parameter(description = "Is replicated required to perform this operation")
@QueryParam("replicated") boolean replicated
) {
try {
validateTopicName(tenant, namespace, topic);
String decodedSubName = decode(encodedSubName);
// If subscription is as "a/b". The url of HTTP API that defined as
// "{tenant}/{namespace}/{topic}/{subscription}" will be like below:
// "public/default/tp/a/b", then the broker will assume it is a topic that
// using the old rule "{tenant}/{cluster}/{namespace}/{topic}/{subscription}".
// So denied to create a subscription that contains "/".
if (pulsar().getConfig().isStrictlyVerifySubscriptionName()
&& !NamedEntity.isAllowed(decodedSubName)) {
throw new RestException(Response.Status.BAD_REQUEST, "Please let the subscription only contains"
+ " '/w(a-zA-Z_0-9)' or '_', the current value is " + decodedSubName);
}
if (!topicName.isPersistent()) {
throw new RestException(Response.Status.BAD_REQUEST, "Create subscription on non-persistent topic "
+ "can only be done through client");
}
Map<String, String> subscriptionProperties = resetCursorData == null ? null :
resetCursorData.getProperties();
MessageIdImpl messageId = resetCursorData == null ? null :
new MessageIdImpl(resetCursorData.getLedgerId(), resetCursorData.getEntryId(),
resetCursorData.getPartitionIndex());
internalCreateSubscription(asyncResponse, decode(encodedSubName), messageId, authoritative,
replicated, subscriptionProperties);
} catch (WebApplicationException wae) {
asyncResponse.resume(wae);
} catch (Exception e) {
asyncResponse.resume(new RestException(e));
}
}
@POST
@Path("/{tenant}/{namespace}/{topic}/subscription/{subName}/resetcursor/{timestamp}")View on GitHub (pinned to 820761864e)
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
Example fix
// before
admin.topics().createSubscription("non-persistent://public/default/t", "sub", MessageId.earliest);
// after
try (Consumer<byte[]> c = client.newConsumer().topic("non-persistent://public/default/t")
.subscriptionName("sub").subscriptionInitialPosition(InitialPosition.Earliest).subscribe()) {} Defensive patterns
Strategy: validation
Validate before calling
if (topic.startsWith("non-persistent://")) { createSubscriptionViaClient(topic, sub); return; } Type guard
boolean isAdminSubscriptionCreationAllowed(String t) { return t != null && t.startsWith("persistent://"); } Try / catch
try { admin.topics().createSubscription(topic, sub, pos); } catch (PulsarAdminException e) { if (e.getStatusCode() == 400) { /* fall back to client consumer creation */ } } Prevention
- 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
When it happens
Trigger: PUT /admin/v2/{tenant}/{namespace}/{topic}/subscription/{subName} (non-persistent endpoint variant) with topicName.isPersistent() == false.
Common situations: 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.
Related errors
- Namespace does not exist
- Subscription not found
- Subscription has active connected consumers
- getSubNotFoundErrorMessage(topicName.toString(), subName)
- Please let the subscription only contains '/w(a-zA-Z_0-9)' o
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/7e0c1c6d6df64bb9.
Report an issue: GitHub.