{"record":{"id":"f70a9ca30102f4e8","repo":"apache/pulsar","slug":"output-serde-mismatch","errorCode":null,"errorMessage":"Output Serde mismatch","messagePattern":"Output Serde mismatch","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java","lineNumber":1056,"sourceCode":"                                .isRegexPattern(false)\n                                .build());\n            });\n        }\n        if (!newConfig.getInputSpecs().isEmpty()) {\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                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())) {","sourceCodeStart":1038,"sourceCodeEnd":1074,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L1038-L1074","documentation":"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.","triggerScenarios":"Updating a function with newConfig.getOutputSerdeClassName() non-empty and != existingConfig.getOutputSerdeClassName().","commonSituations":"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.","solutions":["Omit outputSerdeClassName in the update (leave empty) to keep the existing value, or pass the identical existing value.","To change the output serde, delete and recreate the function with the new serde.","Check that both configs use the same fully qualified serde class name (package differences also count as mismatch)."],"exampleFix":"// before\nnewConfig.setOutputSerdeClassName(\"org.apache.pulsar.functions.api.examples.JsonSerde\"); // existing: ...StringSerde\n// after\nnewConfig.setOutputSerdeClassName(\"\"); // keep existing serde on update\n// or recreate the function with the new serde","handlingStrategy":"validation","validationCode":"if (newConfig.getOutputSerdeClassName() != null && !newConfig.getOutputSerdeClassName().isEmpty()\n        && !newConfig.getOutputSerdeClassName().equals(existingConfig.getOutputSerdeClassName())) {\n    throw new IllegalArgumentException(\"outputSerdeClassName is immutable on update\");\n}","typeGuard":"boolean outputSerdeUnchanged(FunctionConfig existing, FunctionConfig updated) {\n    String n = updated.getOutputSerdeClassName();\n    return n == null || n.isEmpty() || n.equals(existing.getOutputSerdeClassName());\n}","tryCatchPattern":"try {\n    merged = FunctionConfigUtils.validateUpdate(existing, updated);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().equals(\"Output Serde mismatch\")) {\n        updated.setOutputSerdeClassName(\"\");\n        merged = FunctionConfigUtils.validateUpdate(existing, updated);\n    } else { throw e; }\n}","preventionTips":["Leave outputSerdeClassName empty in update payloads to keep the existing value.","Use schemaType-based config consistently instead of mixing serdeClassName.","Recreate the function to change output serialization."],"tags":["pulsar-functions","immutable-field","update-validation","serde"],"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"}