{"record":{"id":"8a986ed073dd6449","repo":"apache/pulsar","slug":"logtopic-topic-s-is-invalid-8a986e","errorCode":null,"errorMessage":"LogTopic topic %s is invalid","messagePattern":"LogTopic topic (.+?) 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":279,"sourceCode":"\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) {\n            ConnectorDefinition connectorDefinition = sourceFunction.getFunctionMetaData(ConnectorDefinition.class);\n            if (connectorDefinition == null) {\n                throw new IllegalArgumentException(\n                        \"Source package doesn't contain the META-INF/services/pulsar-io.yaml file.\");","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java#L261-L297","documentation":"If SourceConfig.logTopic is set, validateAndExtractDetails requires it to be a valid Pulsar topic (TopicName.isValid). The message embeds the offending value via String.format so the developer can see exactly which log topic string was rejected. The log topic is where the function framework writes user log statements.","triggerScenarios":"Setting sourceConfig.setLogTopic(...) to a malformed topic name — e.g. 'logs' without a domain, 'persistent://tenant/ns' missing the topic part, or names with illegal characters — then validating/registering the source.","commonSituations":"Copying the logTopic config between functions and truncating the qualified name; writing a bare topic name assuming defaults apply; typos introduced when templating configs across environments.","solutions":["Set logTopic to a fully-qualified name, e.g. persistent://public/default/source-log-topic (use the exact value from the error message to see what was rejected)","Run TopicName.isValid(logTopic) in your tooling before submission and show a precise error","If you don't need function logging routed to a topic, remove the logTopic field entirely (it is optional)"],"exampleFix":"// before\ncfg.setLogTopic(\"source-logs\");\n// after\ncfg.setLogTopic(\"persistent://public/default/source-logs\");","handlingStrategy":"validation","validationCode":"if (cfg.getLogTopic() != null && !org.apache.pulsar.common.naming.TopicName.isValid(cfg.getLogTopic())) {\n    throw new IllegalArgumentException(\"logTopic must be fully qualified: \" + cfg.getLogTopic());\n}","typeGuard":"static boolean isValidLogTopic(SourceConfig cfg) {\n    return cfg == null || cfg.getLogTopic() == null\n        || org.apache.pulsar.common.naming.TopicName.isValid(cfg.getLogTopic());\n}","tryCatchPattern":"try {\n    SourceConfigUtils.validateAndExtractDetails(cfg, pkg, true);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"LogTopic topic\")) {\n        // the message names the bad value; correct it to a fully-qualified topic\n    }\n}","preventionTips":["Use one convention for log topics across all functions, fully-qualified","Generate logTopic from tenant/namespace/source name instead of hand-editing","Omit logTopic entirely when function log routing is not needed"],"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-14T05:17:10.506Z"}