apache/pulsar · error · IllegalArgumentException
${topic} is invalid. Expected format: '<domain>://tenant/nam
Error message
${topic} is invalid. Expected format: '<domain>://tenant/namespace/topic' What it means
toFullTopicName expects a full topic URL of the form '<domain>://<tenant>/<namespace>/<local-topic>' — exactly 3 segments after '://'. If the string contains '://' but splits into any count other than 3 or 4 (4 being the rejected V1 form), the method throws this IllegalArgumentException stating the expected format.
Source
Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java:560
* For convenience, clients can pass a short topic name:
* - "<local-topic>", which represents "persistent://public/default/<local-topic>"
* - "<tenant>/<namespace>/<local-topic>", which represents "persistent://<tenant>/<namespace>/<local-topic>"
*
* @param topic the topic name from client
* @return the full topic name.
*/
public static String toFullTopicName(String topic) {
final int index = topic.indexOf("://");
if (index >= 0) {
TopicDomain.getEnum(topic.substring(0, index));
final List<String> parts = splitBySlash(topic.substring(index + "://".length()), 4);
if (parts.size() == 4) {
throw new IllegalArgumentException(
"V1 topic names (with cluster component) are no longer supported. "
+ "Please use the V2 format: '<domain>://tenant/namespace/topic'. Got: " + topic);
}
if (parts.size() != 3) {
throw new IllegalArgumentException(topic + " is invalid. "
+ "Expected format: '<domain>://tenant/namespace/topic'");
}
NamespaceName.validateNamespaceName(parts.get(0), parts.get(1));
if (StringUtils.isBlank(parts.get(2))) {
throw new IllegalArgumentException(topic + " has blank local topic");
}
return topic; // it's a valid full topic name
} else {
List<String> parts = splitBySlash(topic, 0);
if (parts.size() != 1 && parts.size() != 3) {
throw new IllegalArgumentException(topic + " is invalid");
}
if (parts.size() == 1) {
if (StringUtils.isBlank(parts.get(0))) {
throw new IllegalArgumentException(topic + " has blank local topic");
}
return "persistent://public/default/" + parts.get(0);
} else {View on GitHub (pinned to 820761864e)
Solutions
- Use exactly '<domain>://<tenant>/<namespace>/<local-topic>' with 3 slash-separated parts after '://'
- Count the segments in your input; fix missing or extra segments
- For short names, omit '://' entirely and let Pulsar apply the default (see short-name handling)
Example fix
// before
TopicName.toFullTopicName("persistent://my-tenant/my-ns");
// after
TopicName.toFullTopicName("persistent://my-tenant/my-ns/my-topic"); Defensive patterns
Strategy: validation
Validate before calling
public static boolean isWellFormedTopicUrl(String s) {
if (s == null) return false;
int i = s.indexOf("://");
if (i < 0) return false;
return s.substring(i + 3).split("/").length == 3;
} Try / catch
try {
String full = TopicName.toFullTopicName(raw);
} catch (IllegalArgumentException e) {
log.error("Topic '{}' does not match '<domain>://tenant/namespace/topic': {}", raw, e.getMessage());
} Prevention
- Count segments after '://' — it must be exactly 3 for a valid V2 URL
- Don't confuse REST admin paths (/admin/v2/persistent/tenant/ns/topic) with topic URLs
- Build names with TopicName objects or fromPersistenceNamingEncoding rather than string surgery
When it happens
Trigger: Calling toFullTopicName() with a name like 'persistent://tenant' or 'persistent://tenant/ns' (too few segments) or one with extra segments (5+), e.g. 'persistent://tenant/ns/topic/extra'.
Common situations: Typos in config (missing namespace or topic segment); string concatenation bugs producing extra slashes/segments; confusing a REST path with a topic URL; partitioned-topic suffix handling gone wrong.
Related errors
- ${topic} is invalid
- Invalid topic name: %s. Topic local name must not be blank.
- Invalid topic name: ${completeTopicName}
- V1 topic names (with cluster component) are no longer suppor
- ${topic} has blank local topic
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/05c478b8d7e77e44.
Report an issue: GitHub.