apache/pulsar · error · IllegalArgumentException
Output topic %s is also being used as an input topic (topics
Error message
Output topic %s is also being used as an input topic (topics must be one or the other)
What it means
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.
Source
Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java:782
private static void doGolangChecks(FunctionConfig functionConfig) {
if (functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {
throw new RuntimeException("Effectively-once processing guarantees not yet supported in Go function");
}
if (functionConfig.getWindowConfig() != null) {
throw new IllegalArgumentException("Windowing is not supported in Go function yet");
}
if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0) {
throw new IllegalArgumentException("Message retries not yet supported in Go function");
}
}
private static void verifyNoTopicClash(Collection<String> inputTopics, String outputTopic)
throws IllegalArgumentException {
if (inputTopics.contains(outputTopic)) {
throw new IllegalArgumentException(
String.format(
"Output topic %s is also being used as an input topic (topics must be one or the other)",
outputTopic));
}
}
public static void doCommonChecks(FunctionConfig functionConfig) {
if (isEmpty(functionConfig.getTenant())) {
throw new IllegalArgumentException("Function tenant cannot be null");
}
if (isEmpty(functionConfig.getNamespace())) {
throw new IllegalArgumentException("Function namespace cannot be null");
}
if (isEmpty(functionConfig.getName())) {
throw new IllegalArgumentException("Function name cannot be null");
}
// go doesn't need className. Java className is done in doJavaChecks.
if (functionConfig.getRuntime() == FunctionConfig.Runtime.PYTHON) {View on GitHub (pinned to 820761864e)
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
Example fix
// before
config.setInputSpecs(Map.of("persistent://public/default/events", ConsumerConfig.builder().build()));
config.setOutputTopic("persistent://public/default/events");
// after
config.setInputSpecs(Map.of("persistent://public/default/events", ConsumerConfig.builder().build()));
config.setOutputTopic("persistent://public/default/events-processed"); Defensive patterns
Strategy: validation
Validate before calling
Set<String> inputs = new HashSet<>(config.getInputTopics() != null ? config.getInputTopics() : List.of());
if (config.getInputSpecs() != null) inputs.addAll(config.getInputSpecs().keySet());
String output = config.getOutputTopic();
if (output != null && inputs.contains(output)) {
throw new IllegalArgumentException("Output topic " + output + " must not also be an input topic");
}
FunctionConfigUtils.validateFunctionConfig(config, null); Type guard
boolean topicsAreDisjoint(FunctionConfig c) {
Set<String> inputs = new HashSet<>();
if (c.getInputTopics() != null) inputs.addAll(c.getInputTopics());
if (c.getInputSpecs() != null) inputs.addAll(c.getInputSpecs().keySet());
return c.getOutputTopic() == null || !inputs.contains(c.getOutputTopic());
} Try / catch
try {
FunctionConfigUtils.validateFunctionConfig(config, null);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("is also being used as an input topic")) {
throw new ConfigException("Fix topic wiring: " + e.getMessage()); // surface actionable message to operator
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Function config is not provided
- Input topic %s is invalid
- Output topic %s is invalid
- Function parallelism must be a positive number
- Function timeout must be a positive number
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/09a41e2aa9e694ae.
Report an issue: GitHub.