apache/pulsar · error · IllegalArgumentException

Output Serde mismatch

Error message

Output Serde mismatch

What it means

The output serialization class of a function is immutable on update. If the new config supplies a non-empty outputSerdeClassName that differs from the existing one, validateUpdate rejects the update, since changing the output serializer would break the declared output schema of the existing subscription.

Source

Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java:1056

                                .isRegexPattern(false)
                                .build());
            });
        }
        if (!newConfig.getInputSpecs().isEmpty()) {
            newConfig.getInputSpecs().forEach((topicName, consumerConfig) -> {
                if (!existingConfig.getInputSpecs().containsKey(topicName)) {
                    throw new IllegalArgumentException("Input Topics cannot be altered");
                }
                if (consumerConfig.isRegexPattern() != existingConfig.getInputSpecs().get(topicName).isRegexPattern()) {
                    throw new IllegalArgumentException(
                            "isRegexPattern for input topic " + topicName + " cannot be altered");
                }
                mergedConfig.getInputSpecs().put(topicName, consumerConfig);
            });
        }
        if (!StringUtils.isEmpty(newConfig.getOutputSerdeClassName()) && !newConfig.getOutputSerdeClassName()
                .equals(existingConfig.getOutputSerdeClassName())) {
            throw new IllegalArgumentException("Output Serde mismatch");
        }
        if (!StringUtils.isEmpty(newConfig.getOutputSchemaType()) && !newConfig.getOutputSchemaType()
                .equals(existingConfig.getOutputSchemaType())) {
            throw new IllegalArgumentException("Output Schema mismatch");
        }
        if (!StringUtils.isEmpty(newConfig.getLogTopic())) {
            mergedConfig.setLogTopic(newConfig.getLogTopic());
        }
        if (newConfig.getProcessingGuarantees() != null && !newConfig.getProcessingGuarantees()
                .equals(existingConfig.getProcessingGuarantees())) {
            throw new IllegalArgumentException("Processing Guarantees cannot be altered");
        }
        if (newConfig.getRetainOrdering() != null && !newConfig.getRetainOrdering()
                .equals(existingConfig.getRetainOrdering())) {
            throw new IllegalArgumentException("Retain Ordering cannot be altered");
        }
        if (newConfig.getRetainKeyOrdering() != null && !newConfig.getRetainKeyOrdering()
                .equals(existingConfig.getRetainKeyOrdering())) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Omit outputSerdeClassName in the update (leave empty) to keep the existing value, or pass the identical existing value.
  2. To change the output serde, delete and recreate the function with the new serde.
  3. Check that both configs use the same fully qualified serde class name (package differences also count as mismatch).

Example fix

// before
newConfig.setOutputSerdeClassName("org.apache.pulsar.functions.api.examples.JsonSerde"); // existing: ...StringSerde
// after
newConfig.setOutputSerdeClassName(""); // keep existing serde on update
// or recreate the function with the new serde
Defensive patterns

Strategy: validation

Validate before calling

if (newConfig.getOutputSerdeClassName() != null && !newConfig.getOutputSerdeClassName().isEmpty()
        && !newConfig.getOutputSerdeClassName().equals(existingConfig.getOutputSerdeClassName())) {
    throw new IllegalArgumentException("outputSerdeClassName is immutable on update");
}

Type guard

boolean outputSerdeUnchanged(FunctionConfig existing, FunctionConfig updated) {
    String n = updated.getOutputSerdeClassName();
    return n == null || n.isEmpty() || n.equals(existing.getOutputSerdeClassName());
}

Try / catch

try {
    merged = FunctionConfigUtils.validateUpdate(existing, updated);
} catch (IllegalArgumentException e) {
    if (e.getMessage().equals("Output Serde mismatch")) {
        updated.setOutputSerdeClassName("");
        merged = FunctionConfigUtils.validateUpdate(existing, updated);
    } else { throw e; }
}

Prevention

When it happens

Trigger: Updating a function with newConfig.getOutputSerdeClassName() non-empty and != existingConfig.getOutputSerdeClassName().

Common situations: Switching SerDe classes (e.g. from StringSerde to JSONSchema-based serde) on a live function; config template changes; fixing a wrong serde name via update instead of recreate.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/f70a9ca30102f4e8. Report an issue: GitHub.