{"record":{"id":"381d02b6e99b9aae","repo":"apache/pulsar","slug":"failed-to-extract-source-class-from-archive","errorCode":null,"errorMessage":"Failed to extract source class from archive","messagePattern":"Failed to extract source class from archive","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java","lineNumber":301,"sourceCode":"        if (sourceConfig.getParallelism() != null && sourceConfig.getParallelism() <= 0) {\n            throw new IllegalArgumentException(\"Source parallelism must be a positive number\");\n        }\n        if (sourceConfig.getResources() != null) {\n            ResourceConfigUtils.validate(sourceConfig.getResources());\n        }\n\n        String sourceClassName = sourceConfig.getClassName();\n        // if class name in source config is not set, this should be a built-in source\n        // thus we should try to find it class name in the NAR service definition\n        if (sourceClassName == null) {\n            ConnectorDefinition connectorDefinition = sourceFunction.getFunctionMetaData(ConnectorDefinition.class);\n            if (connectorDefinition == null) {\n                throw new IllegalArgumentException(\n                        \"Source package doesn't contain the META-INF/services/pulsar-io.yaml file.\");\n            }\n            sourceClassName = connectorDefinition.getSourceClass();\n            if (sourceClassName == null) {\n                throw new IllegalArgumentException(\"Failed to extract source class from archive\");\n            }\n        }\n\n        // check if source implements the correct interfaces\n        TypeDescription sourceClass;\n        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        }","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java#L283-L319","documentation":"For built-in (NAR) sources — className not set — the library reads the connector definition from META-INF/services/pulsar-io.yaml and takes connectorDefinition.getSourceClass() as the implementation class. If the descriptor exists but has no sourceClass (or it is blank), this IllegalArgumentException is thrown: the archive declares itself a connector but doesn't name a Source implementation.","triggerScenarios":"Uploading a NAR whose pulsar-io.yaml exists but omits the sourceClass field (e.g. it's a sink-only connector descriptor, or the YAML key is misspelled), while sourceConfig.className is null.","commonSituations":"Trying to deploy a sink connector NAR as a source (descriptor has sinkClass but no sourceClass); hand-edited pulsar-io.yaml with wrong key casing; connector releases where the class was renamed and the descriptor wasn't updated.","solutions":["Ensure the archive is a SOURCE connector and its pulsar-io.yaml contains a valid sourceClass: entry","If the package is actually a sink or generic function, register it through the sinks/functions API instead of sources","Set sourceConfig.setClassName(...) to the Source implementation explicitly to bypass descriptor extraction","Open the NAR and inspect META-INF/services/pulsar-io.yaml to confirm the key and value"],"exampleFix":"// pulsar-io.yaml inside the NAR\n// before\nsinkClass: com.example.MySink\n// after\nsourceClass: com.example.MySource","handlingStrategy":"validation","validationCode":"// For NAR packages, parse the descriptor before submitting:\nConnectorDefinition def = null;\ntry (java.util.zip.ZipFile zar = new java.util.zip.ZipFile(cfg.getArchive())) {\n    java.util.zip.ZipEntry e = zar.getEntry(\"META-INF/services/pulsar-io.yaml\");\n    if (e != null) {\n        def = new com.fasterxml.jackson.dataformat.yaml.YAMLMapper()\n                .readValue(zar.getInputStream(e), ConnectorDefinition.class);\n    }\n}\nif (cfg.getClassName() == null && (def == null || def.getSourceClass() == null)) {\n    throw new IllegalStateException(\"NAR descriptor has no sourceClass; not deployable as a source\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    SourceConfigUtils.validateAndExtractDetails(cfg, pkg, true);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Failed to extract source class\")) {\n        // inspect pulsar-io.yaml: add sourceClass or register via the sinks API if it's a sink\n    }\n}","preventionTips":["Match the API to the connector type: sources API for sourceClass descriptors, sinks API for sinkClass","Keep pulsar-io.yaml keys exact (sourceClass / sinkClass) and validated in your connector build","Set className explicitly to sidestep descriptor extraction when you know the class"],"tags":["pulsar-functions","connector-nar","missing-resource","packaging"],"backgroundTag":"missing-descriptor-file","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}