{"record":{"id":"09a41e2aa9e694ae","repo":"apache/pulsar","slug":"output-topic-s-is-also-being-used-as-an-input-top","errorCode":null,"errorMessage":"Output topic %s is also being used as an input topic (topics must be one or the other)","messagePattern":"Output topic (.+?) is also being used as an input topic \\(topics must be one or the other\\)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java","lineNumber":782,"sourceCode":"\n    private static void doGolangChecks(FunctionConfig functionConfig) {\n        if (functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {\n            throw new RuntimeException(\"Effectively-once processing guarantees not yet supported in Go function\");\n        }\n\n        if (functionConfig.getWindowConfig() != null) {\n            throw new IllegalArgumentException(\"Windowing is not supported in Go function yet\");\n        }\n\n        if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0) {\n            throw new IllegalArgumentException(\"Message retries not yet supported in Go function\");\n        }\n    }\n\n    private static void verifyNoTopicClash(Collection<String> inputTopics, String outputTopic)\n            throws IllegalArgumentException {\n        if (inputTopics.contains(outputTopic)) {\n            throw new IllegalArgumentException(\n                    String.format(\n                            \"Output topic %s is also being used as an input topic (topics must be one or the other)\",\n                            outputTopic));\n        }\n    }\n\n    public static void doCommonChecks(FunctionConfig functionConfig) {\n        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) {","sourceCodeStart":764,"sourceCodeEnd":800,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L764-L800","documentation":"A Pulsar function's output topic must be distinct from all of its input topics; a topic can only serve one role for a given function. verifyNoTopicClash (invoked from doCommonChecks during function config validation) throws this error when the configured output topic also appears in the input topic collection, because such a wiring would create a self-feeding loop and violate Pulsar's one-role-per-topic rule.","triggerScenarios":"Creating/updating any function where getOutputTopic() equals one of getInputTopics() (or inputTopicsSpec / custom serde input topic names), e.g. pulsar-admin functions create --inputs topic-a --output topic-a, or a config object with overlapping input/output topic sets.","commonSituations":"Copy-paste mistakes where output was left identical to input; auto-generated configs using the same topic name variable; pipelines wiring feedback loops incorrectly; renaming refactors that aliased both fields to one topic.","solutions":["Change the output topic to a different topic name not present in the input topic list","If round-tripping is intended, use two distinct topics (e.g. input-topic and input-topic-processed) and chain functions","Remove the overlapping topic from the input list if it was only meant to be the output"],"exampleFix":"// before\nconfig.setInputSpecs(Map.of(\"persistent://public/default/events\", ConsumerConfig.builder().build()));\nconfig.setOutputTopic(\"persistent://public/default/events\");\n\n// after\nconfig.setInputSpecs(Map.of(\"persistent://public/default/events\", ConsumerConfig.builder().build()));\nconfig.setOutputTopic(\"persistent://public/default/events-processed\");","handlingStrategy":"validation","validationCode":"Set<String> inputs = new HashSet<>(config.getInputTopics() != null ? config.getInputTopics() : List.of());\nif (config.getInputSpecs() != null) inputs.addAll(config.getInputSpecs().keySet());\nString output = config.getOutputTopic();\nif (output != null && inputs.contains(output)) {\n    throw new IllegalArgumentException(\"Output topic \" + output + \" must not also be an input topic\");\n}\nFunctionConfigUtils.validateFunctionConfig(config, null);","typeGuard":"boolean topicsAreDisjoint(FunctionConfig c) {\n    Set<String> inputs = new HashSet<>();\n    if (c.getInputTopics() != null) inputs.addAll(c.getInputTopics());\n    if (c.getInputSpecs() != null) inputs.addAll(c.getInputSpecs().keySet());\n    return c.getOutputTopic() == null || !inputs.contains(c.getOutputTopic());\n}","tryCatchPattern":"try {\n    FunctionConfigUtils.validateFunctionConfig(config, null);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"is also being used as an input topic\")) {\n        throw new ConfigException(\"Fix topic wiring: \" + e.getMessage()); // surface actionable message to operator\n    }\n    throw e;\n}","preventionTips":["Enforce a naming convention separating input and output topics (e.g. *-events vs *-events-out)","Validate topic disjointness in config generation code before calling the admin API","Never template the same topic variable into both --inputs and --output","Review function topology diagrams for accidental feedback loops before deployment"],"tags":["pulsar-functions","topic-configuration","validation","configuration"],"backgroundTag":"topic-role-clash","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"}