{"record":{"id":"d0b5fe9ba4e40d67","repo":"apache/pulsar","slug":"transform-function-class-name-must-be-set","errorCode":null,"errorMessage":"Transform function class name must be set","messagePattern":"Transform function class name must be set","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java","lineNumber":497,"sourceCode":"                    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\n            if (functionClassName == null) {\n                FunctionDefinition functionDefinition =\n                        transformFunction.getFunctionMetaData(FunctionDefinition.class);\n                if (functionDefinition == null) {\n                    throw new IllegalArgumentException(\n                            \"Function package doesn't contain the META-INF/services/pulsar-io.yaml file.\");\n                }\n                functionClassName = functionDefinition.getFunctionClass();\n                if (functionClassName == null) {\n                    throw new IllegalArgumentException(\"Transform function class name must be set\");\n                }\n            }\n            TypeDefinition functionClass;\n            try {\n                functionClass = transformFunction.resolveType(functionClassName);\n            } catch (TypePool.Resolution.NoSuchTypeException e) {\n                throw new IllegalArgumentException(\n                        String.format(\"Function class %s not found\", functionClassName), e);\n            }\n            // extract type from transform function class\n            if (!getRawFunctionTypes(functionClass, false)[1].asErasure().isAssignableTo(Record.class)) {\n                throw new IllegalArgumentException(\"Sink transform function output must be of type Record\");\n            }\n            typeArg = getFunctionTypes(functionClass, false)[0];\n            inputFunction = transformFunction;\n        } else {\n            // extract type from sink class\n            typeArg = getSinkType(sinkClass);","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java#L479-L515","documentation":"After reading the FunctionDefinition from META-INF/services/pulsar-io.yaml, the library expects getFunctionClass() to name the transform function class. Thrown when the service descriptor exists but its functionClass field is null/empty.","triggerScenarios":"The pulsar-io.yaml in the transform function package exists but omits the functionClass property, and sinkConfig.transformFunctionClassName is null.","commonSituations":"Hand-written or templated pulsar-io.yaml missing the functionClass key; a build step that filtered the yaml resource out partially.","solutions":["Add/repair the functionClass entry in META-INF/services/pulsar-io.yaml of the transform function package","Or set sinkConfig.setTransformFunctionClassName(...) explicitly to bypass reading it from the descriptor"],"exampleFix":"# before\nname: my-transform\n# after\nname: my-transform\nfunctionClass: org.example.MyTransformFunction","handlingStrategy":"validation","validationCode":"// verify descriptor declares functionClass\njava.util.Properties p = new java.util.Properties();\n// read META-INF/services/pulsar-io.yaml as text and assert 'functionClass:' key present\nString yaml = new String(java.util.zip.GZIPInputStream.class.getResourceAsStream(\n    \"/META-INF/services/pulsar-io.yaml\").readAllBytes());\nif (!yaml.contains(\"functionClass:\")) throw new IllegalStateException(\"pulsar-io.yaml missing functionClass\");","typeGuard":null,"tryCatchPattern":"try {\n  admin.sinks().createSink(config, pkgPath);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Transform function class name must be set\")) {\n    config.setTransformFunctionClassName(\"org.example.MyTransform\");\n  } else { throw e; }\n}","preventionTips":["Keep the pulsar-io.yaml template with functionClass always filled","Validate descriptor contents in CI before publishing the package"],"tags":["pulsar-functions","sink","service-loader","configuration"],"backgroundTag":"missing-service-descriptor","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"}