{"record":{"id":"5fadac43e0e26c30","repo":"apache/pulsar","slug":"source-class-s-implements-s-but-batch-source-sou","errorCode":null,"errorMessage":"Source class %s implements %s but batch source source config is not specified","messagePattern":"Source class (.+?) implements (.+?) but batch source source config is not specified","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java","lineNumber":325,"sourceCode":"        try {\n            sourceClass = sourceFunction.resolveType(sourceClassName);\n        } catch (TypePool.Resolution.NoSuchTypeException e) {\n            throw new IllegalArgumentException(\n              String.format(\"Source class %s not found in class loader\", sourceClassName), e);\n        }\n\n        if (!(sourceClass.asErasure().isAssignableTo(Source.class) || sourceClass.asErasure()\n                .isAssignableTo(BatchSource.class))) {\n            throw new IllegalArgumentException(\n                    String.format(\"Source class %s does not implement the correct interface\",\n                            sourceClass.getName()));\n        }\n\n        if (sourceClass.asErasure().isAssignableTo(BatchSource.class)) {\n            if (sourceConfig.getBatchSourceConfig() != null) {\n                validateBatchSourceConfig(sourceConfig.getBatchSourceConfig());\n            } else {\n                throw new IllegalArgumentException(\n                  String.format(\"Source class %s implements %s but batch source source config is not specified\",\n                    sourceClass.getName(), BatchSource.class.getName()));\n            }\n        }\n\n        // extract type from source class\n        TypeDefinition typeArg;\n\n        try {\n            typeArg = getSourceType(sourceClass);\n        } catch (Exception e) {\n            throw new IllegalArgumentException(\n                    String.format(\"Failed to resolve type for Source class %s\", sourceClassName), e);\n        }\n\n        // Only one of serdeClassName or schemaType should be set\n        if (!StringUtils.isEmpty(sourceConfig.getSerdeClassName()) && !StringUtils\n                .isEmpty(sourceConfig.getSchemaType())) {","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java#L307-L343","documentation":"During source validation, Pulsar detected that the source class implements BatchSource but the SourceConfig has no batchSourceConfig set. A BatchSource requires its discovery triggerer/record generator settings to operate, so the framework refuses to deploy a batch source without that configuration block.","triggerScenarios":"Calling SourceConfigUtils.validateAndExtractDetails with a SourceConfig whose className points to a class assignable to org.apache.pulsar.functions.source.BatchSource while sourceConfig.getBatchSourceConfig() returns null.","commonSituations":"Deploying a batch source connector via CLI/REST and forgetting the --batch-source-config JSON; copying a regular source config template for a batch source; a config file where the batchSourceConfig key is misspelled or nested incorrectly so it deserializes to null.","solutions":["Add a batchSourceConfig object (with discoveryTriggererClassName, discoveryTriggererConfig, batchSize) to the SourceConfig before submitting","Verify the className really needs to be a BatchSource; if a regular source suffices, use a class implementing Source instead of BatchSource","Check config file/JSON key spelling and nesting so batchSourceConfig actually deserializes into the SourceConfig","Re-submit the source with the corrected config"],"exampleFix":"// before\n{\"className\":\"com.example.MyBatchSource\",\"topicName\":\"out\"}\n// after\n{\"className\":\"com.example.MyBatchSource\",\"topicName\":\"out\",\"batchSourceConfig\":{\"discoveryTriggererClassName\":\"com.example.MyTriggerer\",\"discoveryTriggererConfig\":{},\"batchSize\":10}}","handlingStrategy":"validation","validationCode":"if (config.getClassName() != null && isBatchSourceClass(config.getClassName(), loader)\n    && config.getBatchSourceConfig() == null) {\n    throw new IllegalArgumentException(\"batchSourceConfig required for BatchSource class \" + config.getClassName());\n}","typeGuard":"boolean isBatchSourceConfigured(SourceConfig c) { return c.getBatchSourceConfig() != null; }","tryCatchPattern":"try { SourceConfigUtils.validateAndExtractDetails(...); } catch (IllegalArgumentException e) { if (e.getMessage().contains(\"batch source source config\")) { /* add batchSourceConfig and retry */ } else { throw e; } }","preventionTips":["Always populate batchSourceConfig when the source class implements BatchSource","Validate config JSON schema before submitting (required keys: discoveryTriggererClassName, batchSize)","Keep separate config templates for batch vs regular sources","Use the pulsar-admin source create validation early in CI"],"tags":["pulsar-functions","config-validation","batch-source"],"backgroundTag":"missing-required-config","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"}