apache/pulsar · error · org.apache.pulsar.broker.admin.RestException
Topic is not partitioned topic
Error message
Topic is not partitioned topic
What it means
Returned by validatePartitionedTopicMetadataAsync when the addressed topic exists in metadata but has partitions < 1 — i.e. it is a non-partitioned topic (or a partitioned topic with zero partitions). Endpoints that only operate on partitioned topics (e.g. partition-level admin ops like getPartitions, update-partitions) reject the request with HTTP 409 Conflict.
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/AdminResource.java:295
throw new RestException(Status.NOT_ACCEPTABLE, "Need to provide a persistent topic name");
}
}
protected void validatePartitionedTopicName(String tenant, String namespace, String encodedTopic) {
// first, it has to be a validate topic name
validateTopicName(tenant, namespace, encodedTopic);
// second, "-partition-" is not allowed
if (encodedTopic.contains(TopicName.PARTITIONED_TOPIC_SUFFIX)) {
throw new RestException(Status.PRECONDITION_FAILED,
"Partitioned Topic Name should not contain '-partition-'");
}
}
protected CompletableFuture<Void> validatePartitionedTopicMetadataAsync() {
return pulsar().getBrokerService().fetchPartitionedTopicMetadataAsync(topicName)
.thenAccept(metadata -> {
if (metadata.partitions < 1) {
throw new RestException(Status.CONFLICT, "Topic is not partitioned topic");
}
});
}
protected WorkerService validateAndGetWorkerService() {
try {
return pulsar().getWorkerService();
} catch (UnsupportedOperationException e) {
throw new RestException(Status.CONFLICT, e.getMessage());
}
}
/**
* @deprecated Use {@link #getNamespacePoliciesAsync(NamespaceName)} instead.
*/
@Deprecated
protected Policies getNamespacePolicies(NamespaceName namespaceName) {
try {View on GitHub (pinned to 820761864e)
Solutions
- Check the topic's metadata first (GET /admin/v2/persistent/tenant/ns/topic/partitions or namespaces getPartitionedTopics list) and only call partitioned APIs on partitioned topics.
- If the topic should be partitioned, delete the non-partitioned topic and create it with the desired number of partitions (or use updatePartitionedTopic if it exists with partitions).
- Update calling code to branch between partitioned and non-partitioned handling using the topic's metadata.
- Use the admin client's topics().getPartitionedTopicMetadata(...) which handles this check gracefully.
Example fix
// before
admin.topics().getPartitionedTopicMetadata("tenant/ns/orders"); // 409 if not partitioned
// after
PartitionedTopicMetadata md = admin.topics().getPartitionedTopicMetadata("tenant/ns/orders");
if (md.partitions < 1) {
admin.topics().createPartitionedTopic("tenant/ns/orders", 4);
} Defensive patterns
Strategy: type-guard
Validate before calling
// Java: check partition metadata before partitioned-only calls
PartitionedTopicMetadata md = admin.topics().getPartitionedTopicMetadata(fqTopic);
if (md.partitions >= 1) { /* safe to call partitioned APIs */ } Type guard
boolean isPartitioned(PartitionedTopicMetadata md) { return md != null && md.partitions >= 1; } Try / catch
try {
admin.topics().getPartitionedInfo(fqTopic);
} catch (PulsarAdminException e) {
if (e.getStatusCode() == 409) {
// not partitioned: fall back to non-partitioned handling
}
} Prevention
- Query partition metadata before any partitioned-only admin call.
- Handle both topic kinds in generic topic tooling.
- Track topic kind in your provisioning inventory to avoid drift.
When it happens
Trigger: Calling partitioned-only admin endpoints (GET .../partitions, POST .../updatePartitions, DELETE partitioned topic) against a topic created as a plain non-partitioned topic, or after partitions were set to 0.
Common situations: Assuming every topic is partitioned in shared tooling; a topic was re-created as non-partitioned after an earlier cleanup; code paths that must handle both topic kinds but only guard one.
Related errors
- This topic already exists
- Partitioned Topic Name should not contain '-partition-'
- Exceed maximum number of topics in namespace.
- ${loadManagerClass} does not support this operation
- domain() invoked from wrong resource
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/ba4deea4a2944644.
Report an issue: GitHub.