{"record":{"id":"1f2b02fa35440600","repo":"apache/pulsar","slug":"discovertriggerer-class-cannot-be-updated-for-batc","errorCode":null,"errorMessage":"DiscoverTriggerer class cannot be updated for batchsources","messagePattern":"DiscoverTriggerer class cannot be updated for batchsources","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java","lineNumber":512,"sourceCode":"    }\n\n    public static String computeBatchSourceInstanceSubscriptionName(String tenant, String namespace,\n                                                                    String sourceName) {\n        return \"BatchSourceExecutor-\" + tenant + \"/\" + namespace + \"/\" + sourceName;\n    }\n\n    public static TopicName computeBatchSourceIntermediateTopicName(String tenant, String namespace,\n                                                                    String sourceName) {\n        return TopicName.get(TopicDomain.persistent.name(), tenant, namespace, sourceName + \"-intermediate\");\n    }\n\n    public static boolean isBatchSource(SourceConfig sourceConfig) {\n        return sourceConfig.getBatchSourceConfig() != null;\n    }\n\n    public static void validateBatchSourceConfigUpdate(BatchSourceConfig existingConfig, BatchSourceConfig newConfig) {\n        if (!existingConfig.getDiscoveryTriggererClassName().equals(newConfig.getDiscoveryTriggererClassName())) {\n            throw new IllegalArgumentException(\"DiscoverTriggerer class cannot be updated for batchsources\");\n        }\n    }\n\n    public static void validateSourceConfig(SourceConfig sourceConfig, ValidatableFunctionPackage sourceFunction) {\n        try {\n            ConnectorDefinition defn = sourceFunction.getFunctionMetaData(ConnectorDefinition.class);\n            if (defn != null && defn.getSourceConfigClass() != null) {\n                Class<?> configClass =\n                        Class.forName(defn.getSourceConfigClass(), true, sourceFunction.getClassLoader());\n                validateSourceConfig(sourceConfig, configClass);\n            }\n        } catch (ClassNotFoundException e) {\n            throw new IllegalArgumentException(\"Could not find source config class\");\n        }\n\n    }\n\n    public static void validateSourceConfig(SourceConfig sourceConfig, Class<?> configClass) {","sourceCodeStart":494,"sourceCodeEnd":530,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java#L494-L530","documentation":"During batch source updates, validateBatchSourceConfigUpdate enforces that the discovery triggerer class stays the same, because swapping it would change how the running source discovers batches mid-lifecycle. Any change is rejected with this message.","triggerScenarios":"validateUpdate on a batch source where existingConfig.getBatchSourceConfig().getDiscoveryTriggererClassName() differs from the new config's value.","commonSituations":"Operator switches to a different/improved triggerer implementation via update instead of recreation; template upgrade bumps the triggerer class; drift between environments leads to mismatched triggerer classes in update payloads.","solutions":["Resubmit the update keeping discoveryTriggererClassName identical to the existing value","If a different triggerer is required, delete and recreate the batch source with the new config","Adjust tooling so only discoveryTriggererConfig (not the class) is changed on updates"],"exampleFix":"// before\n{\"batchSourceConfig\":{\"discoveryTriggererClassName\":\"com.example.NewTriggerer\", ...}}\n// after\n{\"batchSourceConfig\":{\"discoveryTriggererClassName\":\"com.example.OldTriggerer\", ...}} // same class as existing","handlingStrategy":"validation","validationCode":"if (existing.getBatchSourceConfig() != null && update.getBatchSourceConfig() != null\n    && !existing.getBatchSourceConfig().getDiscoveryTriggererClassName()\n        .equals(update.getBatchSourceConfig().getDiscoveryTriggererClassName())) {\n    throw new IllegalArgumentException(\"discoveryTriggererClassName is immutable on update\");\n}","typeGuard":null,"tryCatchPattern":"try { SourceConfigUtils.validateUpdate(existing, update); } catch (IllegalArgumentException e) { if (e.getMessage().contains(\"DiscoverTriggerer class cannot be updated\")) { update.getBatchSourceConfig().setDiscoveryTriggererClassName(existing.getBatchSourceConfig().getDiscoveryTriggererClassName()); } else { throw e; } }","preventionTips":["Only change discoveryTriggererConfig, never discoveryTriggererClassName, on updates","To switch triggerer implementations, delete and recreate the batch source","Pin the triggerer class in shared templates to avoid drift"],"tags":["pulsar-functions","batch-source","immutable-field"],"backgroundTag":"immutable-field-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"}