{"record":{"id":"9675b6f12c32de80","repo":"apache/pulsar","slug":"source-class-s-not-found-in-class-loader","errorCode":null,"errorMessage":"Source class %s not found in class loader","messagePattern":"Source class (.+?) not found in class loader","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java","lineNumber":310,"sourceCode":"        // 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        }\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            }","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java#L292-L328","documentation":"After resolving the source class name, validateAndExtractDetails loads the type via sourceFunction.resolveType (ByteBuddy TypePool over the configured class loader). If the class does not exist in that loader, the resolution fails with NoSuchTypeException, rethrown as this IllegalArgumentException (with the class name interpolated) — the archive/classpath does not actually contain the declared class.","triggerScenarios":"className (explicit or extracted from pulsar-io.yaml) references a class absent from the uploaded archive or its dependencies — wrong package spelling, class renamed in a newer connector version, or missing dependency jar in the NAR.","commonSituations":"Refactoring a connector and forgetting to update sourceClass/className; uploading an old archive after upgrading the config; typos in fully-qualified class names; shading that strips the class from the final jar.","solutions":["Verify the exact class name inside the archive (unzip and javap / jar tf) and fix sourceConfig.setClassName(...) or the pulsar-io.yaml sourceClass to match","Rebuild/re-upload the archive so it actually contains the declared class and its dependencies","If the connector was upgraded, use the new fully-qualified class name from its release notes","Check the NAR includes dependency jars (extraDeps) needed by the class"],"exampleFix":"// before\ncfg.setClassName(\"com.example.MySource\"); // class moved packages in v2\n// after\ncfg.setClassName(\"com.example.sources.MySource\");","handlingStrategy":"validation","validationCode":"// Before submit, confirm the class exists in the archive:\ntry (java.util.zip.ZipFile zar = new java.util.zip.ZipFile(cfg.getArchive())) {\n    String path = cfg.getClassName().replace('.', '/') + \".class\";\n    boolean found = zar.stream().anyMatch(e -> e.getName().endsWith(path));\n    if (!found) throw new IllegalStateException(\"Class \" + cfg.getClassName() + \" not present in \" + cfg.getArchive());\n}","typeGuard":null,"tryCatchPattern":"try {\n    SourceConfigUtils.validateAndExtractDetails(cfg, pkg, true);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"not found in class loader\")) {\n        // fix the class name or re-upload an archive that contains the class\n    }\n}","preventionTips":["Copy fully-qualified class names from the connector build output, never by hand","After connector upgrades, regenerate configs with the new class names","Run 'jar tf archive | grep <ClassName>' as a CI step before deployment","Ensure shading configs don't strip connector classes from the final jar"],"tags":["pulsar-functions","classnotfound","connector-nar","packaging"],"backgroundTag":"class-not-found","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"}