apache/pulsar · error · IllegalArgumentException
Function package doesn't contain the META-INF/services/pulsa
Error message
Function package doesn't contain the META-INF/services/pulsar-io.yaml file.
What it means
Thrown when a sink config sets no transformFunctionClassName, so the library treats the uploaded package as a transform-function package and looks up its service definition via META-INF/services/pulsar-io.yaml. If FunctionMetaData is null the package is not a valid transform function package.
Source
Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java:492
TypeDefinition sinkClass;
try {
sinkClass = sinkFunction.resolveType(sinkClassName);
} catch (TypePool.Resolution.NoSuchTypeException e) {
throw new IllegalArgumentException(
String.format("Sink class %s not found", sinkClassName), e);
}
String functionClassName = sinkConfig.getTransformFunctionClassName();
TypeDefinition typeArg;
ValidatableFunctionPackage inputFunction;
if (transformFunction != null) {
// if function class name in sink config is not set, this should be a built-in function
// thus we should try to find it class name in the NAR service definition
if (functionClassName == null) {
FunctionDefinition functionDefinition =
transformFunction.getFunctionMetaData(FunctionDefinition.class);
if (functionDefinition == null) {
throw new IllegalArgumentException(
"Function package doesn't contain the META-INF/services/pulsar-io.yaml file.");
}
functionClassName = functionDefinition.getFunctionClass();
if (functionClassName == null) {
throw new IllegalArgumentException("Transform function class name must be set");
}
}
TypeDefinition functionClass;
try {
functionClass = transformFunction.resolveType(functionClassName);
} catch (TypePool.Resolution.NoSuchTypeException e) {
throw new IllegalArgumentException(
String.format("Function class %s not found", functionClassName), e);
}
// extract type from transform function class
if (!getRawFunctionTypes(functionClass, false)[1].asErasure().isAssignableTo(Record.class)) {
throw new IllegalArgumentException("Sink transform function output must be of type Record");
}View on GitHub (pinned to 820761864e)
Solutions
- Add META-INF/services/pulsar-io.yaml declaring the FunctionDefinition (name, functionClass, type args) inside the package
- Or set sinkConfig.setTransformFunctionClassName(...) explicitly so the service lookup path is skipped
- Verify you uploaded the intended transform-function archive, not a plain sink jar
Example fix
// before
// jar has no META-INF/services/pulsar-io.yaml, transformFunctionClassName unset
sinkConfig.setTransformFunctionClassName(null);
// after
sinkConfig.setTransformFunctionClassName("org.example.MyTransformFunction"); Defensive patterns
Strategy: validation
Validate before calling
try (java.util.jar.JarFile jar = new java.util.jar.JarFile(pkgPath)) {
if (jar.getJarEntry("META-INF/services/pulsar-io.yaml") == null
&& sinkConfig.getTransformFunctionClassName() == null) {
throw new IllegalStateException("Package is not a transform function package and no transformFunctionClassName set");
}
} Try / catch
try {
admin.sinks().createSink(config, pkgPath);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("pulsar-io.yaml")) {
log.error("Set transformFunctionClassName or fix the package's service descriptor", e);
} else { throw e; }
} Prevention
- Ensure build includes resources: check pulsar-io.yaml lands in META-INF/services in the artifact
- Only pass transform-function archives where the pipeline expects them
- Set transformFunctionClassName explicitly when in doubt
When it happens
Trigger: Passing a function/transform package (or arbitrary jar) as sinkFunction while sinkConfig.getTransformFunctionClassName() is null and the archive lacks META-INF/services/pulsar-io.yaml.
Common situations: Using a plain sink jar where a transform-function package is expected; building a NAR without the service descriptor file; wrong file placed in the service directory.
Related errors
- Transform function class name must be set
- SourceRecord class type must be PulsarRecord
- Sink does not implement correct interface
- PartitionId needs to be specified for every record while in
- RecordSequence needs to be specified for every record while
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/793758a24e2d4eea.
Report an issue: GitHub.