apache/pulsar · error · IllegalArgumentException
Failed to extract source class from archive
Error message
Failed to extract source class from archive
What it means
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.
Source
Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java:301
if (sourceConfig.getParallelism() != null && sourceConfig.getParallelism() <= 0) {
throw new IllegalArgumentException("Source parallelism must be a positive number");
}
if (sourceConfig.getResources() != null) {
ResourceConfigUtils.validate(sourceConfig.getResources());
}
String sourceClassName = sourceConfig.getClassName();
// if class name in source config is not set, this should be a built-in source
// thus we should try to find it class name in the NAR service definition
if (sourceClassName == null) {
ConnectorDefinition connectorDefinition = sourceFunction.getFunctionMetaData(ConnectorDefinition.class);
if (connectorDefinition == null) {
throw new IllegalArgumentException(
"Source package doesn't contain the META-INF/services/pulsar-io.yaml file.");
}
sourceClassName = connectorDefinition.getSourceClass();
if (sourceClassName == null) {
throw new IllegalArgumentException("Failed to extract source class from archive");
}
}
// check if source implements the correct interfaces
TypeDescription sourceClass;
try {
sourceClass = sourceFunction.resolveType(sourceClassName);
} catch (TypePool.Resolution.NoSuchTypeException e) {
throw new IllegalArgumentException(
String.format("Source class %s not found in class loader", sourceClassName), e);
}
if (!(sourceClass.asErasure().isAssignableTo(Source.class) || sourceClass.asErasure()
.isAssignableTo(BatchSource.class))) {
throw new IllegalArgumentException(
String.format("Source class %s does not implement the correct interface",
sourceClass.getName()));
}View on GitHub (pinned to 820761864e)
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
Example fix
// pulsar-io.yaml inside the NAR // before sinkClass: com.example.MySink // after sourceClass: com.example.MySource
Defensive patterns
Strategy: validation
Validate before calling
// For NAR packages, parse the descriptor before submitting:
ConnectorDefinition def = null;
try (java.util.zip.ZipFile zar = new java.util.zip.ZipFile(cfg.getArchive())) {
java.util.zip.ZipEntry e = zar.getEntry("META-INF/services/pulsar-io.yaml");
if (e != null) {
def = new com.fasterxml.jackson.dataformat.yaml.YAMLMapper()
.readValue(zar.getInputStream(e), ConnectorDefinition.class);
}
}
if (cfg.getClassName() == null && (def == null || def.getSourceClass() == null)) {
throw new IllegalStateException("NAR descriptor has no sourceClass; not deployable as a source");
} Try / catch
try {
SourceConfigUtils.validateAndExtractDetails(cfg, pkg, true);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("Failed to extract source class")) {
// inspect pulsar-io.yaml: add sourceClass or register via the sinks API if it's a sink
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Source package doesn't contain the META-INF/services/pulsar-
- Source class %s not found in class loader
- Function package doesn't contain the META-INF/services/pulsa
- You must specify either a Fully Qualified Function Name (FQF
- You must specify a name for the function or a Fully Qualifie
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/381d02b6e99b9aae.
Report an issue: GitHub.