{"record":{"id":"6d4d671d7db56365","repo":"apache/pulsar","slug":"failed-to-extract-sink-class-from-archive","errorCode":null,"errorMessage":"Failed to extract sink class from archive","messagePattern":"Failed to extract sink class from archive","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java","lineNumber":469,"sourceCode":"            ResourceConfigUtils.validate(sinkConfig.getResources());\n        }\n\n        if (sinkConfig.getTimeoutMs() != null && sinkConfig.getTimeoutMs() < 0) {\n            throw new IllegalArgumentException(\"Sink timeout must be a positive number\");\n        }\n\n        String sinkClassName = sinkConfig.getClassName();\n        // if class name in sink config is not set, this should be a built-in sink\n        // thus we should try to find it class name in the NAR service definition\n        if (sinkClassName == null) {\n            ConnectorDefinition connectorDefinition = sinkFunction.getFunctionMetaData(ConnectorDefinition.class);\n            if (connectorDefinition == null) {\n                throw new IllegalArgumentException(\n                        \"Sink package doesn't contain the META-INF/services/pulsar-io.yaml file.\");\n            }\n            sinkClassName = connectorDefinition.getSinkClass();\n            if (sinkClassName == null) {\n                throw new IllegalArgumentException(\"Failed to extract sink class from archive\");\n            }\n        }\n\n        // check if sink implements the correct interfaces\n        TypeDefinition sinkClass;\n        try {\n            sinkClass = sinkFunction.resolveType(sinkClassName);\n        } catch (TypePool.Resolution.NoSuchTypeException e) {\n            throw new IllegalArgumentException(\n                    String.format(\"Sink class %s not found\", sinkClassName), e);\n        }\n\n        String functionClassName = sinkConfig.getTransformFunctionClassName();\n        TypeDefinition typeArg;\n        ValidatableFunctionPackage inputFunction;\n        if (transformFunction != null) {\n            // if function class name in sink config is not set, this should be a built-in function\n            // thus we should try to find it class name in the NAR service definition","sourceCodeStart":451,"sourceCodeEnd":487,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java#L451-L487","documentation":"When className is unset, validateAndExtractDetails reads the connector definition from META-INF/services/pulsar-io.yaml; if the YAML parses but declares no sinkClass (e.g. it only defines a source, or the sinkClass field is empty), this IllegalArgumentException is thrown because a sink deployment cannot proceed without an implementing class.","triggerScenarios":"Uploading a connector NAR whose pulsar-io.yaml defines sourceClass but not sinkClass (a source-only connector used with 'sink create'); pulsar-io.yaml with an empty sinkClass key.","commonSituations":"Reusing a source connector archive as a sink; hand-edited pulsar-io.yaml missing the sinkClass line; generic connector archives where only one direction is implemented.","solutions":["Add/fix the sinkClass entry in META-INF/services/pulsar-io.yaml inside the NAR.","Use the correct archive — one that implements a sink (or set className explicitly to your sink class).","If the connector is source-only, deploy it via 'pulsar-admin source create' instead.","Inspect the YAML: 'unzip -p my-sink.nar META-INF/services/pulsar-io.yaml' to confirm sinkClass is present."],"exampleFix":"// before — META-INF/services/pulsar-io.yaml\nname: my-connector\nsourceClass: org.example.MySource\n// after\nname: my-connector\nsourceClass: org.example.MySource\nsinkClass: org.example.MySink","handlingStrategy":"validation","validationCode":"ConnectorDefinition def = sinkPkg.getFunctionMetaData(ConnectorDefinition.class);\nif (sinkConfig.getClassName() == null && (def == null || def.getSinkClass() == null)) {\n    throw new IllegalArgumentException(\"connector archive does not declare a sinkClass\");\n}","typeGuard":"static boolean declaresSinkClass(ValidatableFunctionPackage pkg, String className) {\n    if (className != null) return true;\n    ConnectorDefinition d = pkg.getFunctionMetaData(ConnectorDefinition.class);\n    return d != null && d.getSinkClass() != null && !d.getSinkClass().isEmpty();\n}","tryCatchPattern":"try {\n    SinkConfigUtils.validateAndExtractDetails(cfg, sinkPkg, transformPkg, true);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Failed to extract sink class\")) {\n        log.error(\"pulsar-io.yaml has no sinkClass: is this a source-only connector?\", e);\n    }\n    throw e;\n}","preventionTips":["Verify the connector type (source vs sink) matches the deployment command.","Keep sinkClass populated in every pulsar-io.yaml you ship.","Inspect archive YAML with 'unzip -p nar META-INF/services/pulsar-io.yaml' before deploying.","Set className explicitly when using a custom archive to bypass descriptor resolution."],"tags":["pulsar","pulsar-io","nar-packaging","connector"],"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"}