apache/pulsar · error · IllegalArgumentException

Processing Guarantees cannot be altered

Error message

Processing Guarantees cannot be altered

What it means

Processing guarantees (ATLEAST_ONCE, ATMOST_ONCE, EFFECTIVELY_ONCE) are delivery-semantics set at creation and cannot be changed on update, since they affect the function's subscription and state management behavior. validateUpdate throws when the new config specifies a non-null processingGuarantees that differs from the existing one.

Source

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

                            "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())) {
            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());
        }

View on GitHub (pinned to 820761864e)

Solutions

  1. Leave processingGuarantees unset (null) in the update payload, or set it exactly to the existing value.
  2. If guarantees must change, delete and recreate the function with the new ProcessingGuarantees value.
  3. Check deploy tooling for hardcoded processingGuarantees defaults that differ from the original creation.

Example fix

// before
newConfig.setProcessingGuarantees(ProcessingGuarantees.EFFECTIVELY_ONCE); // existing: ATLEAST_ONCE
// after
newConfig.setProcessingGuarantees(null); // keep existing guarantee on update
// or recreate the function with EFFECTIVELY_ONCE
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

boolean guaranteesUnchanged(FunctionConfig existing, FunctionConfig updated) {
    return updated.getProcessingGuarantees() == null
        || updated.getProcessingGuarantees().equals(existing.getProcessingGuarantees());
}

Try / catch

try {
    merged = FunctionConfigUtils.validateUpdate(existing, updated);
} catch (IllegalArgumentException e) {
    if (e.getMessage().equals("Processing Guarantees cannot be altered")) {
        updated.setProcessingGuarantees(null);
        merged = FunctionConfigUtils.validateUpdate(existing, updated);
    } else { throw e; }
}

Prevention

When it happens

Trigger: Updating a function with newConfig.getProcessingGuarantees() != null and != existingConfig.getProcessingGuarantees(), e.g. switching ATLEAST_ONCE to EFFECTIVELY_ONCE.

Common situations: Trying to tighten/loosen delivery guarantees on a live function to fix duplicate or lost message concerns; config templates that always set processingGuarantees; migrations from at-most-once to at-least-once processing.

Related errors


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