{"record":{"id":"ce930d18cb5b2c2a","repo":"apache/pulsar","slug":"processing-guarantees-cannot-be-altered-ce930d","errorCode":null,"errorMessage":"Processing Guarantees cannot be altered","messagePattern":"Processing Guarantees cannot be altered","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java","lineNumber":685,"sourceCode":"                                .build());\n            });\n        }\n        if (!newConfig.getInputSpecs().isEmpty()) {\n            SinkConfig finalMergedConfig = mergedConfig;\n            newConfig.getInputSpecs().forEach((topicName, consumerConfig) -> {\n                if (!existingConfig.getInputSpecs().containsKey(topicName)) {\n                    throw new IllegalArgumentException(\"Input Topics cannot be altered\");\n                }\n                if (consumerConfig.isRegexPattern() != existingConfig.getInputSpecs().get(topicName).isRegexPattern()) {\n                    throw new IllegalArgumentException(\n                            \"isRegexPattern for input topic \" + topicName + \" cannot be altered\");\n                }\n                finalMergedConfig.getInputSpecs().put(topicName, consumerConfig);\n            });\n        }\n        if (newConfig.getProcessingGuarantees() != null && !newConfig.getProcessingGuarantees()\n                .equals(existingConfig.getProcessingGuarantees())) {\n            throw new IllegalArgumentException(\"Processing Guarantees cannot be altered\");\n        }\n        if (newConfig.getConfigs() != null) {\n            mergedConfig.setConfigs(newConfig.getConfigs());\n        }\n        if (newConfig.getSecrets() != null) {\n            mergedConfig.setSecrets(newConfig.getSecrets());\n        }\n        if (newConfig.getParallelism() != null) {\n            mergedConfig.setParallelism(newConfig.getParallelism());\n        }\n        if (newConfig.getRetainOrdering() != null && !newConfig.getRetainOrdering()\n                .equals(existingConfig.getRetainOrdering())) {\n            throw new IllegalArgumentException(\"Retain Ordering cannot be altered\");\n        }\n        if (newConfig.getRetainKeyOrdering() != null && !newConfig.getRetainKeyOrdering()\n                .equals(existingConfig.getRetainKeyOrdering())) {\n            throw new IllegalArgumentException(\"Retain Key Ordering cannot be altered\");\n        }","sourceCodeStart":667,"sourceCodeEnd":703,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java#L667-L703","documentation":"Thrown by SinkConfigUtils.validateUpdate when processingGuarantees is set in the new config and differs from the existing sink's value. The delivery semantics (ATLEAST_ONCE, ATMOST_ONCE, EFFECTIVELY_ONCE) are locked after creation because changing them affects message acknowledgment behavior.","triggerScenarios":"Updating a sink with getProcessingGuarantees() non-null and not equal to the deployed value, e.g. switching ATLEAST_ONCE to EFFECTIVELY_ONCE via update.","commonSituations":"Team hardens delivery guarantees on an existing sink via update; template config carries a different default guarantee than the deployed sink; copy-paste between function and sink configs with different guarantees.","solutions":["Keep processingGuarantees equal to the existing sink's value (or null to leave it unchanged)","To change guarantees, delete the sink and create it anew with the desired setting","Check the deployed value with 'pulsar-admin sinks get' before setting the field"],"exampleFix":"// before\nsinkConfig.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE);\nadmin.sinks().updateSink(tenant, namespace, sinkConfig, null);\n// after\nsinkConfig.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE); // matches existing\nadmin.sinks().updateSink(tenant, namespace, sinkConfig, null);","handlingStrategy":"validation","validationCode":"if (newCfg.getProcessingGuarantees() != null\n    && !newCfg.getProcessingGuarantees().equals(existing.getProcessingGuarantees())) {\n    throw new IllegalArgumentException(\"processingGuarantees is immutable\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    admin.sinks().updateSink(tenant, namespace, cfg, null);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Processing Guarantees cannot be altered\")) { /* keep existing guarantee */ }\n    else throw e;\n}","preventionTips":["Do not set processingGuarantees in update payloads","Recreate the sink to change delivery semantics","Standardize guarantees across environments to avoid config drift"],"tags":["pulsar","functions","config-validation","immutable-field"],"backgroundTag":"immutable-config-field-update","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}