{"record":{"id":"eff8de90f70ed338","repo":"apache/pulsar","slug":"cannot-derive-a-tenant-namespace-from-regex-patt","errorCode":null,"errorMessage":"Cannot derive a tenant/namespace from --regex pattern '${pattern}'. Use a fully-qualified pattern, e.g. persistent://tenant/namespace/.*","messagePattern":"Cannot derive a tenant/namespace from --regex pattern '(.+?)'\\. Use a fully-qualified pattern, e\\.g\\. persistent://tenant/namespace/\\.\\*","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-client-tools/src/main/java/org/apache/pulsar/client/cli/CmdConsume.java","lineNumber":284,"sourceCode":"                                     java.util.function.Consumer<String> namespaceFn) {\n        if (isRegex) {\n            namespaceFn.accept(namespaceFromPattern(topic));\n        } else {\n            topicFn.accept(topic);\n        }\n    }\n\n    static String namespaceFromPattern(String pattern) {\n        // Strip an optional persistent:// / non-persistent:// domain prefix, then take the first\n        // two path segments as tenant/namespace.\n        String rest = pattern;\n        int scheme = rest.indexOf(\"://\");\n        if (scheme >= 0) {\n            rest = rest.substring(scheme + 3);\n        }\n        String[] parts = rest.split(\"/\");\n        if (parts.length < 2) {\n            throw new IllegalArgumentException(\"Cannot derive a tenant/namespace from --regex pattern '\"\n                    + pattern + \"'. Use a fully-qualified pattern, e.g. persistent://tenant/namespace/.*\");\n        }\n        return parts[0] + \"/\" + parts[1];\n    }\n\n    private ConsumerEncryptionPolicy buildConsumerEncryptionPolicy() {\n        return buildFileDecryptionPolicy(this.encKeyValue, cryptoFailureAction);\n    }\n\n    @VisibleForTesting\n    public String getWebSocketConsumeUri(String topic) {\n        String serviceURLWithoutTrailingSlash = serviceURL.substring(0,\n                serviceURL.endsWith(\"/\") ? serviceURL.length() - 1 : serviceURL.length());\n\n        TopicName topicName = TopicName.get(topic);\n        String wsTopic = String.format(\"%s/%s/%s/%s\", topicName.getDomain(), topicName.getTenant(),\n                topicName.getNamespacePortion(), topicName.getLocalName());\n","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-tools/src/main/java/org/apache/pulsar/client/cli/CmdConsume.java#L266-L302","documentation":"namespaceFromPattern derives the tenant/namespace to look up topics from a --regex subscription pattern by stripping the scheme and taking the first two path segments. If the pattern does not contain at least tenant/namespace after the scheme, an IllegalArgumentException is thrown because the CLI cannot determine which namespace to list topics in.","triggerScenarios":"Passing a --regex like '.*', 'mytopic-.*', or 'topic://x' — anything that after removing 'scheme://' yields fewer than 2 slash-separated segments when applyTopicSelection calls namespaceFromPattern.","commonSituations":"Writing a regex meant to match topics across all namespaces; shortening persistent://tenant/ns/topic.* to just topic.*; confusing --regex (needs fully-qualified pattern) with plain topic-name matching.","solutions":["Use a fully-qualified pattern: --regex 'persistent://my-tenant/my-ns/.*'","Keep at least tenant/namespace in the pattern before the wildcard portion, e.g. persistent://public/default/my-topic-.*","If you truly need cross-namespace discovery, run the CLI once per namespace pattern"],"exampleFix":"// before\n--regex 'orders-.*'\n// after\n--regex 'persistent://public/default/orders-.*'","handlingStrategy":"validation","validationCode":"String rest = pattern.replaceAll(\"^[a-z]+://\", \"\");\nString[] parts = rest.split(\"/\");\nif (parts.length < 2 || parts[0].isEmpty() || parts[1].isEmpty())\n    throw new IllegalArgumentException(\"--regex must be fully qualified: persistent://tenant/namespace/...\");","typeGuard":"static boolean isFullyQualifiedTopicPattern(String p) {\n    String rest = p.contains(\"://\") ? p.substring(p.indexOf(\"://\") + 3) : p;\n    String[] parts = rest.split(\"/\");\n    return parts.length >= 2 && !parts[0].isEmpty() && !parts[1].isEmpty();\n}","tryCatchPattern":"try {\n    String ns = namespaceFromPattern(pattern);\n} catch (IllegalArgumentException e) {\n    LOG.error(\"{} — defaulting pattern prefix to persistent://public/default/\", e.getMessage());\n    pattern = \"persistent://public/default/\" + pattern.replaceFirst(\"^[a-z]+://\", \"\");\n}","preventionTips":["Always build --regex as persistent://tenant/namespace/<body> even when wildcarding the topic part","Never pass bare topic wildcards like 'orders-.*' to --regex","Add a preflight check in scripts that splits the pattern and asserts tenant/ns segments exist","For cross-namespace discovery, loop over namespaces with one fully-qualified pattern each"],"tags":["cli","regex","validation","topic-pattern"],"backgroundTag":"malformed-topic-pattern","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}