apache/pulsar · error · IllegalArgumentException

Retain Ordering cannot be altered

Error message

Retain Ordering cannot be altered

What it means

FunctionConfigUtils.validateUpdate enforces that certain function settings are immutable after creation. retainOrdering changes would require a new subscription/consumption mode on the existing topic, so Pulsar rejects the update with IllegalArgumentException instead of applying it.

Solutions

  1. Keep retainOrdering unchanged in the update request (pass null or the existing value fetched via getFunction).
  2. If a different retainOrdering is truly needed, delete and re-create (or re-register) the function with the new value.
  3. Diff the new config against the current config before submitting to catch the conflicting field.
  4. Check client SDK/tooling defaults so retainOrdering is not implicitly set on updates.

Example fix

// before
FunctionConfig update = existing;
update.setRetainOrdering(!existing.getRetainOrdering()); // rejected
admin.functions().updateFunction(update, configFile);
// after
FunctionConfig update = existing;
update.setRetainOrdering(existing.getRetainOrdering()); // unchanged, or leave null
admin.functions().updateFunction(update, configFile);
Defensive patterns

Strategy: validation

Validate before calling

FunctionConfig existing = admin.functions().getFunction(tenant, namespace, fnName);
if (update.getRetainOrdering() != null && !update.getRetainOrdering().equals(existing.getRetainOrdering())) {
    update.setRetainOrdering(null); // drop immutable change
}

Try / catch

try {
    admin.functions().updateFunction(update, pkgFile);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("cannot be altered")) { /* reset immutable fields and retry once */ }
    else throw e;
}

Prevention

When it happens

Trigger: Calling updateFunction (or the PUT admin API) with a FunctionConfig whose getRetainOrdering() is non-null and differs from the existing function's retainOrdering.

Common situations: Submitting an updated function config where the caller populated retainOrdering=true by default or changed it to fix ordering issues; client tooling that always serializes all fields, turning an unset field into an explicit conflicting value.

Related errors


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

Appendix: source

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

        }
        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())) {
            throw new IllegalArgumentException("Retain Key Ordering cannot be altered");
        }
        if (!StringUtils.isEmpty(newConfig.getOutput())) {
            mergedConfig.setOutput(newConfig.getOutput());
        }
        if (newConfig.getUserConfig() != null) {
            mergedConfig.setUserConfig(newConfig.getUserConfig());
        }
        if (newConfig.getSecrets() != null) {
            mergedConfig.setSecrets(newConfig.getSecrets());
        }
        if (newConfig.getRuntime() != null && !newConfig.getRuntime().equals(existingConfig.getRuntime())) {
            throw new IllegalArgumentException("Runtime cannot be altered");
        }
        @SuppressWarnings("deprecation")

View on GitHub (pinned to 820761864e)