{"record":{"id":"6f4a0fec167a0178","repo":"apache/pulsar","slug":"deadletter-topic-s-is-invalid","errorCode":null,"errorMessage":"DeadLetter topic %s is invalid","messagePattern":"DeadLetter topic (.+?) is invalid","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java","lineNumber":832,"sourceCode":"        }\n\n        if (!isEmpty(functionConfig.getOutput())) {\n            if (!TopicName.isValid(functionConfig.getOutput())) {\n                throw new IllegalArgumentException(\n                        String.format(\"Output topic %s is invalid\", functionConfig.getOutput()));\n            }\n        }\n\n        if (!isEmpty(functionConfig.getLogTopic())) {\n            if (!TopicName.isValid(functionConfig.getLogTopic())) {\n                throw new IllegalArgumentException(\n                        String.format(\"LogTopic topic %s is invalid\", functionConfig.getLogTopic()));\n            }\n        }\n\n        if (!isEmpty(functionConfig.getDeadLetterTopic())) {\n            if (!TopicName.isValid(functionConfig.getDeadLetterTopic())) {\n                throw new IllegalArgumentException(\n                        String.format(\"DeadLetter topic %s is invalid\", functionConfig.getDeadLetterTopic()));\n            }\n        }\n\n        if (functionConfig.getParallelism() != null && functionConfig.getParallelism() <= 0) {\n            throw new IllegalArgumentException(\"Function parallelism must be a positive number\");\n        }\n        // Ensure that topics aren't being used as both input and output\n        verifyNoTopicClash(allInputTopics, functionConfig.getOutput());\n\n        WindowConfig windowConfig = functionConfig.getWindowConfig();\n        if (windowConfig != null) {\n            // set auto ack to false since windowing framework is responsible\n            // for acking and not the function framework\n            @SuppressWarnings(\"deprecation\")\n            Boolean windowAutoAck = functionConfig.getAutoAck();\n            if (windowAutoAck != null && windowAutoAck) {\n                throw new IllegalArgumentException(\"Cannot enable auto ack when using windowing functionality\");","sourceCodeStart":814,"sourceCodeEnd":850,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L814-L850","documentation":"The dead-letter topic (used to store messages that exhaust max retries) is validated with TopicName.isValid in doCommonChecks; an invalid name causes function creation to fail with this IllegalArgumentException. A DLQ must be a real, addressable Pulsar topic for the retry policy to publish to it.","triggerScenarios":"createFunction/updateFunction where functionConfig.getDeadLetterTopic() is non-empty and fails TopicName.isValid: bare topic name, malformed persistent:// URI, trailing whitespace, or empty segments.","commonSituations":"Setting --dead-letter-topic on the CLI with a short name, YAML placeholders not rendered, renaming tenant/namespace elsewhere and forgetting the DLQ field.","solutions":["Set a fully qualified DLQ name: persistent://public/default/my-dlq","Also set maxMessageRetries >= 0 (a DLQ alone with infinite retries is rejected separately — see error 1419)","Remove the deadLetterTopic if retries handling is not desired","Validate with TopicName.isValid before submission"],"exampleFix":"// before\nconfig.setDeadLetterTopic(\"dlq\");\n// after\nconfig.setDeadLetterTopic(\"persistent://public/default/dlq\");\nconfig.setMaxMessageRetries(3);","handlingStrategy":"validation","validationCode":"String dlq = config.getDeadLetterTopic();\nif (dlq != null && !dlq.isEmpty() && !TopicName.isValid(dlq.trim())) {\n    throw new IllegalArgumentException(\"Invalid DLQ topic: \" + dlq);\n}\nif (dlq != null && !dlq.isEmpty()\n        && (config.getMaxMessageRetries() == null || config.getMaxMessageRetries() < 0)) {\n    throw new IllegalArgumentException(\"DLQ requires maxMessageRetries >= 0\");\n}","typeGuard":"boolean hasValidDlq(FunctionConfig c) {\n    String t = c.getDeadLetterTopic();\n    return t == null || t.isEmpty()\n        || (TopicName.isValid(t.trim()) && c.getMaxMessageRetries() != null && c.getMaxMessageRetries() >= 0);\n}","tryCatchPattern":"try {\n    admin.functions().createFunction(functionConfig, sourceConfigLocation);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"DeadLetter\")) {\n        // fix deadLetterTopic format and ensure maxMessageRetries is set\n    }\n    throw e;\n}","preventionTips":["Always pair deadLetterTopic with a finite maxMessageRetries","Create the DLQ topic ahead of deployment","Use fully qualified DLQ names","Keep DLQ naming conventions centralized in config templates"],"tags":["pulsar-functions","validation","topic-name","dead-letter"],"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"}