{"record":{"id":"255e6ee8a5e8c174","repo":"apache/pulsar","slug":"no-input-topic-s-specified-for-the-function","errorCode":null,"errorMessage":"No input topic(s) specified for the function","messagePattern":"No input topic\\(s\\) specified for the function","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java","lineNumber":808,"sourceCode":"        if (isEmpty(functionConfig.getTenant())) {\n            throw new IllegalArgumentException(\"Function tenant cannot be null\");\n        }\n        if (isEmpty(functionConfig.getNamespace())) {\n            throw new IllegalArgumentException(\"Function namespace cannot be null\");\n        }\n        if (isEmpty(functionConfig.getName())) {\n            throw new IllegalArgumentException(\"Function name cannot be null\");\n        }\n        // go doesn't need className. Java className is done in doJavaChecks.\n        if (functionConfig.getRuntime() == FunctionConfig.Runtime.PYTHON) {\n            if (isEmpty(functionConfig.getClassName())) {\n                throw new IllegalArgumentException(\"Function classname cannot be null\");\n            }\n        }\n\n        Collection<String> allInputTopics = collectAllInputTopics(functionConfig);\n        if (allInputTopics.isEmpty()) {\n            throw new IllegalArgumentException(\"No input topic(s) specified for the function\");\n        }\n        for (String topic : allInputTopics) {\n            if (!TopicName.isValid(topic)) {\n                throw new IllegalArgumentException(String.format(\"Input topic %s is invalid\", topic));\n            }\n        }\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()));","sourceCodeStart":790,"sourceCodeEnd":826,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L790-L826","documentation":"A Pulsar function must consume from at least one input topic; doCommonChecks aggregates all configured inputs (the inputTopics map plus any topicsPattern) via collectAllInputTopics and throws this IllegalArgumentException when the resulting collection is empty.","triggerScenarios":"Submitting a function whose FunctionConfig has neither setInputTopics(...) (or setInputSpecs) nor a topicsPattern, via validateJavaFunction/validateNonJavaFunction, the REST API, or pulsar-admin functions create; a config file missing all 'input' entries.","commonSituations":"New function configs created from scratch where inputs were deferred; YAML templates with the 'inputs:' section removed or indented wrong so it fails to parse into the map; code refactors renaming input config keys; tests constructing minimal configs without inputs.","solutions":["Add input topics: functionConfig.setInputTopics(Collections.singletonList(\"persistent://public/default/input-topic\")).","Or use a pattern subscription: functionConfig.setTopicsPattern(\"persistent://public/default/.*\").","In YAML/JSON configs, add the 'inputs:' (or 'inputSpecs:') entries with correct indentation.","Verify with pulsar-admin that the topics exist; after this check, each topic must also be a valid TopicName."],"exampleFix":"// before\nFunctionConfig config = new FunctionConfig();\nconfig.setTenant(\"public\");\nconfig.setNamespace(\"default\");\n// after\nFunctionConfig config = new FunctionConfig();\nconfig.setTenant(\"public\");\nconfig.setNamespace(\"default\");\nconfig.setName(\"my-fn\");\nconfig.setInputTopics(Collections.singletonList(\"persistent://public/default/in-topic\"));","handlingStrategy":"validation","validationCode":"java.util.Collection<String> inputs = FunctionConfigUtils.collectAllInputTopics(config);\nif (inputs == null || inputs.isEmpty()) {\n    throw new IllegalArgumentException(\"At least one input topic (or topicsPattern) is required\");\n}","typeGuard":"static boolean hasInputTopics(FunctionConfig c) {\n    return c != null\n        && ((c.getInputSpecs() != null && !c.getInputSpecs().isEmpty())\n            || (c.getInputTopics() != null && !c.getInputTopics().isEmpty())\n            || (c.getTopicsPattern() != null && !c.getTopicsPattern().trim().isEmpty()));\n}","tryCatchPattern":"try {\n    FunctionConfigUtils.validateNonJavaFunction(functionConfig, null);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"input topic\")) {\n        throw new IllegalStateException(\"Configure at least one input topic via 'inputs:' or --inputs\", e);\n    }\n    throw e;\n}","preventionTips":["Always configure inputs (inputTopics/inputSpecs or topicsPattern) alongside tenant/namespace/name.","Verify YAML 'inputs:' entries parse into the map — indentation errors silently drop topics.","Confirm the input topics exist (pulsar-admin topics list) and use fully-qualified persistent:// URLs.","Run local validation of the assembled FunctionConfig before submitting to the functions worker."],"tags":["pulsar","functions","configuration","topics"],"backgroundTag":"required-field-missing","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"}