{"record":{"id":"32bd19c571caf228","repo":"apache/pulsar","slug":"processing-guarantees-cannot-be-altered","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/FunctionConfigUtils.java","lineNumber":1067,"sourceCode":"                            \"isRegexPattern for input topic \" + topicName + \" cannot be altered\");\n                }\n                mergedConfig.getInputSpecs().put(topicName, consumerConfig);\n            });\n        }\n        if (!StringUtils.isEmpty(newConfig.getOutputSerdeClassName()) && !newConfig.getOutputSerdeClassName()\n                .equals(existingConfig.getOutputSerdeClassName())) {\n            throw new IllegalArgumentException(\"Output Serde mismatch\");\n        }\n        if (!StringUtils.isEmpty(newConfig.getOutputSchemaType()) && !newConfig.getOutputSchemaType()\n                .equals(existingConfig.getOutputSchemaType())) {\n            throw new IllegalArgumentException(\"Output Schema mismatch\");\n        }\n        if (!StringUtils.isEmpty(newConfig.getLogTopic())) {\n            mergedConfig.setLogTopic(newConfig.getLogTopic());\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.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        }\n        if (!StringUtils.isEmpty(newConfig.getOutput())) {\n            mergedConfig.setOutput(newConfig.getOutput());\n        }\n        if (newConfig.getUserConfig() != null) {\n            mergedConfig.setUserConfig(newConfig.getUserConfig());\n        }\n        if (newConfig.getSecrets() != null) {\n            mergedConfig.setSecrets(newConfig.getSecrets());\n        }","sourceCodeStart":1049,"sourceCodeEnd":1085,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L1049-L1085","documentation":"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.","triggerScenarios":"Updating a function with newConfig.getProcessingGuarantees() != null and != existingConfig.getProcessingGuarantees(), e.g. switching ATLEAST_ONCE to EFFECTIVELY_ONCE.","commonSituations":"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.","solutions":["Leave processingGuarantees unset (null) in the update payload, or set it exactly to the existing value.","If guarantees must change, delete and recreate the function with the new ProcessingGuarantees value.","Check deploy tooling for hardcoded processingGuarantees defaults that differ from the original creation."],"exampleFix":"// before\nnewConfig.setProcessingGuarantees(ProcessingGuarantees.EFFECTIVELY_ONCE); // existing: ATLEAST_ONCE\n// after\nnewConfig.setProcessingGuarantees(null); // keep existing guarantee on update\n// or recreate the function with EFFECTIVELY_ONCE","handlingStrategy":"validation","validationCode":"if (newConfig.getProcessingGuarantees() != null\n        && !newConfig.getProcessingGuarantees().equals(existingConfig.getProcessingGuarantees())) {\n    throw new IllegalArgumentException(\"processingGuarantees is immutable on update\");\n}","typeGuard":"boolean guaranteesUnchanged(FunctionConfig existing, FunctionConfig updated) {\n    return updated.getProcessingGuarantees() == null\n        || updated.getProcessingGuarantees().equals(existing.getProcessingGuarantees());\n}","tryCatchPattern":"try {\n    merged = FunctionConfigUtils.validateUpdate(existing, updated);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().equals(\"Processing Guarantees cannot be altered\")) {\n        updated.setProcessingGuarantees(null);\n        merged = FunctionConfigUtils.validateUpdate(existing, updated);\n    } else { throw e; }\n}","preventionTips":["Omit processingGuarantees from update payloads; set it only at creation.","Avoid hardcoded delivery-guarantee defaults in deploy tooling.","Recreate the function when delivery semantics must change."],"tags":["pulsar-functions","immutable-field","update-validation","processing-guarantees"],"backgroundTag":"immutable-field-update-rejected","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"}