{"record":{"id":"1132b59182e1cb2d","repo":"apache/pulsar","slug":"sources-cannot-be-update-between-regular-sources-a","errorCode":null,"errorMessage":"Sources cannot be update between regular sources and batchsource","messagePattern":"Sources cannot be update between regular sources and batchsource","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java","lineNumber":433,"sourceCode":"        }\n        if (newConfig.getParallelism() != null) {\n            mergedConfig.setParallelism(newConfig.getParallelism());\n        }\n        if (newConfig.getResources() != null) {\n            mergedConfig\n                    .setResources(ResourceConfigUtils.merge(existingConfig.getResources(), newConfig.getResources()));\n        }\n        if (!StringUtils.isEmpty(newConfig.getArchive())) {\n            mergedConfig.setArchive(newConfig.getArchive());\n        }\n        if (!StringUtils.isEmpty(newConfig.getRuntimeFlags())) {\n            mergedConfig.setRuntimeFlags(newConfig.getRuntimeFlags());\n        }\n        if (!StringUtils.isEmpty(newConfig.getCustomRuntimeOptions())) {\n            mergedConfig.setCustomRuntimeOptions(newConfig.getCustomRuntimeOptions());\n        }\n        if (isBatchSource(existingConfig) != isBatchSource(newConfig)) {\n            throw new IllegalArgumentException(\"Sources cannot be update between regular sources and batchsource\");\n        }\n        if (newConfig.getBatchSourceConfig() != null) {\n            validateBatchSourceConfigUpdate(existingConfig.getBatchSourceConfig(), newConfig.getBatchSourceConfig());\n            mergedConfig.setBatchSourceConfig(newConfig.getBatchSourceConfig());\n        }\n        if (newConfig.getProducerConfig() != null) {\n            mergedConfig.setProducerConfig(newConfig.getProducerConfig());\n        }\n        return mergedConfig;\n    }\n\n    public static void validateBatchSourceConfig(BatchSourceConfig batchSourceConfig) throws IllegalArgumentException {\n        if (isEmpty(batchSourceConfig.getDiscoveryTriggererClassName())) {\n            log.error(\"BatchSourceConfig does not specify Discovery Trigger ClassName\");\n            throw new IllegalArgumentException(\"BatchSourceConfig does not specify Discovery Trigger ClassName\");\n        }\n    }\n","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java#L415-L451","documentation":"A source cannot be converted between a regular Source and a BatchSource via update; the two runtimes differ fundamentally. validateUpdate compares isBatchSource(existingConfig) with isBatchSource(newConfig) and rejects any mismatch.","triggerScenarios":"validateUpdate where exactly one of the existing/new configs has batchSourceConfig set (presence of batchSourceConfig defines batch source), e.g. adding batchSourceConfig to an existing regular source, or removing/nulling it on a batch source update.","commonSituations":"Upgrading a regular source to batch mode by patching the config; a deserializer drops batchSourceConfig from the update payload, making a batch source look regular; template reuse across batch and non-batch sources.","solutions":["Submit the update keeping the same source kind: include batchSourceConfig for batch sources, omit it for regular sources","To switch kinds, delete the source and create a new one of the desired type","Check serialization/deserialization of the update payload so batchSourceConfig is not silently dropped"],"exampleFix":"// before\n{\"className\":\"com.example.MyBatchSource\", ...} // batchSourceConfig omitted in update\n// after\n{\"className\":\"com.example.MyBatchSource\",\"batchSourceConfig\":{\"discoveryTriggererClassName\":\"com.example.MyTriggerer\", ...}, ...}","handlingStrategy":"validation","validationCode":"if ((existing.getBatchSourceConfig() == null) != (update.getBatchSourceConfig() == null)) {\n    throw new IllegalArgumentException(\"cannot switch between regular and batch source on update\");\n}","typeGuard":"boolean isBatchSource(SourceConfig c) { return c.getBatchSourceConfig() != null; }","tryCatchPattern":"try { SourceConfigUtils.validateUpdate(existing, update); } catch (IllegalArgumentException e) { if (e.getMessage().contains(\"regular sources and batchsource\")) { if (SourceConfigUtils.isBatchSource(existing)) update.setBatchSourceConfig(existing.getBatchSourceConfig()); else update.setBatchSourceConfig(null); } else { throw e; } }","preventionTips":["Always carry batchSourceConfig forward from the existing config on batch-source updates","Use distinct create/update templates for batch vs regular sources","Check payload serialization doesn't drop batchSourceConfig"],"tags":["pulsar-functions","batch-source","update-validation"],"backgroundTag":"incompatible-config-change","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"}