{"record":{"id":"54c619086d61177e","repo":"apache/pulsar","slug":"topic-name-is-invalid","errorCode":null,"errorMessage":"Topic name is invalid","messagePattern":"Topic name is invalid","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java","lineNumber":275,"sourceCode":"        }\n\n        return sourceConfig;\n    }\n\n    public static ExtractedSourceDetails validateAndExtractDetails(SourceConfig sourceConfig,\n                                                                   ValidatableFunctionPackage sourceFunction,\n                                                                   boolean validateConnectorConfig) {\n        if (isEmpty(sourceConfig.getTenant())) {\n            throw new IllegalArgumentException(\"Source tenant cannot be null\");\n        }\n        if (isEmpty(sourceConfig.getNamespace())) {\n            throw new IllegalArgumentException(\"Source namespace cannot be null\");\n        }\n        if (isEmpty(sourceConfig.getName())) {\n            throw new IllegalArgumentException(\"Source name cannot be null\");\n        }\n        if (!isEmpty(sourceConfig.getTopicName()) && !TopicName.isValid(sourceConfig.getTopicName())) {\n            throw new IllegalArgumentException(\"Topic name is invalid\");\n        }\n        if (!isEmpty(sourceConfig.getLogTopic())) {\n            if (!TopicName.isValid(sourceConfig.getLogTopic())) {\n                throw new IllegalArgumentException(\n                        String.format(\"LogTopic topic %s is invalid\", sourceConfig.getLogTopic()));\n            }\n        }\n        if (sourceConfig.getParallelism() != null && sourceConfig.getParallelism() <= 0) {\n            throw new IllegalArgumentException(\"Source parallelism must be a positive number\");\n        }\n        if (sourceConfig.getResources() != null) {\n            ResourceConfigUtils.validate(sourceConfig.getResources());\n        }\n\n        String sourceClassName = sourceConfig.getClassName();\n        // if class name in source config is not set, this should be a built-in source\n        // thus we should try to find it class name in the NAR service definition\n        if (sourceClassName == null) {","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java#L257-L293","documentation":"When SourceConfig.topicName is set, validateAndExtractDetails checks it is a syntactically valid Pulsar topic via TopicName.isValid(). A non-null but malformed topicName (missing domain, bad characters, wrong structure) throws this IllegalArgumentException. The field is optional, but if present it must parse as a complete topic name.","triggerScenarios":"Setting sourceConfig.setTopicName(...) to a string that fails TopicName.isValid — e.g. 'my-topic' (no persistent:///, tenant/namespace), 'persistent://tenant/ns/' (trailing slash, no topic), or a topic with invalid characters; then calling validateAndExtractDetails or pulsar-admin sources create.","commonSituations":"Users entering a bare topic name instead of a fully-qualified one; copy-pasted Kafka topic names that lack Pulsar's topic domain prefix; typos like double slashes or URL-encoded characters; assuming Pulsar will resolve a short name against a default namespace.","solutions":["Use a fully-qualified topic name: persistent://<tenant>/<namespace>/<topic> (or non-persistent://...)","If you only know the short name, construct the full name with tenant/namespace, e.g. String.format(\"persistent://%s/%s/%s\", tenant, namespace, topic)","Validate with TopicName.isValid(topicName) yourself before submitting so you can surface which name was rejected"],"exampleFix":"// before\ncfg.setTopicName(\"kafka-mirror-topic\");\n// after\ncfg.setTopicName(\"persistent://public/default/kafka-mirror-topic\");","handlingStrategy":"validation","validationCode":"if (cfg.getTopicName() != null && !org.apache.pulsar.common.naming.TopicName.isValid(cfg.getTopicName())) {\n    throw new IllegalArgumentException(\"topicName must be fully qualified, e.g. persistent://tenant/ns/topic\");\n}","typeGuard":"static boolean isValidTopicName(SourceConfig cfg) {\n    return cfg == null || cfg.getTopicName() == null\n        || org.apache.pulsar.common.naming.TopicName.isValid(cfg.getTopicName());\n}","tryCatchPattern":"try {\n    SourceConfigUtils.validateAndExtractDetails(cfg, pkg, true);\n} catch (IllegalArgumentException e) {\n    if (\"Topic name is invalid\".equals(e.getMessage())) {\n        // show the configured topicName and the expected persistent://tenant/ns/topic format\n    }\n}","preventionTips":["Always store fully-qualified topic names (persistent://tenant/namespace/topic) in configs","Build topic names programmatically with tenant/namespace instead of hand-typing them","Pre-check with TopicName.isValid in CI when source configs are committed"],"tags":["pulsar-functions","config-validation","topic-name","illegal-argument"],"backgroundTag":"invalid-topic-name","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"}