{"record":{"id":"22d600e56ef9761b","repo":"apache/pulsar","slug":"sink-config-is-not-provided","errorCode":null,"errorMessage":"Sink config is not provided","messagePattern":"Sink config is not provided","errorType":"http","errorClass":"RestException","httpStatus":400,"severity":"error","filePath":"pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/rest/api/SinksImpl.java","lineNumber":100,"sourceCode":"                             final String sinkPkgUrl,\n                             final SinkConfig sinkConfig,\n                             final AuthenticationParameters authParams) {\n\n        if (!isWorkerServiceAvailable()) {\n            throwUnavailableException();\n        }\n\n        if (tenant == null) {\n            throw new RestException(Response.Status.BAD_REQUEST, \"Tenant is not provided\");\n        }\n        if (namespace == null) {\n            throw new RestException(Response.Status.BAD_REQUEST, \"Namespace is not provided\");\n        }\n        if (sinkName == null) {\n            throw new RestException(Response.Status.BAD_REQUEST, \"Sink name is not provided\");\n        }\n        if (sinkConfig == null) {\n            throw new RestException(Response.Status.BAD_REQUEST, \"Sink config is not provided\");\n        }\n\n        throwRestExceptionIfUnauthorizedForNamespace(tenant, namespace, sinkName, \"register\", authParams);\n\n        try {\n            // Check tenant exists\n            worker().getBrokerAdmin().tenants().getTenantInfo(tenant);\n\n            String qualifiedNamespace = tenant + \"/\" + namespace;\n            List<String> namespaces = worker().getBrokerAdmin().namespaces().getNamespaces(tenant);\n            if (namespaces != null && !namespaces.contains(qualifiedNamespace)) {\n                String qualifiedNamespaceWithCluster = String.format(\"%s/%s/%s\", tenant,\n                        worker().getWorkerConfig().getPulsarFunctionsCluster(), namespace);\n                if (namespaces != null && !namespaces.contains(qualifiedNamespaceWithCluster)) {\n                    log.error().attr(\"tenant\", tenant).attr(\"namespace\", namespace).attr(\"componentName\", sinkName)\n\n                            .attr(\"namespace3\", namespace).log(\"/ / Namespace does not exist\");\n                    throw new RestException(Response.Status.BAD_REQUEST, \"Namespace does not exist\");","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/rest/api/SinksImpl.java#L82-L118","documentation":"registerSink in SinksImpl throws a 400 Bad Request RestException when the submitted SinkConfig is null. The config carries the sink's runtime settings (className, inputs, topics pattern, etc.) needed to derive FunctionDetails. This is the last of the four mandatory-parameter checks, after tenant, namespace and sinkName.","triggerScenarios":"Calling the sink registration endpoint without a request body, with an empty/unparsable body that deserializes to null SinkConfig, or passing null sinkConfig to SinksImpl.registerSink directly.","commonSituations":"curl requests missing -d/--data or the Content-Type: application/json header so the body is dropped; client SDKs failing silent JSON deserialization into null; scripts that build the config conditionally and skip it when inputs are empty.","solutions":["Send a complete SinkConfig JSON body with the request and set Content-Type: application/json.","Verify the client-side deserialization: log the parsed SinkConfig before calling; if it is null, the body was empty or malformed.","Construct the SinkConfig explicitly (className, topicToSerdeClassName/inputs) before registration."],"exampleFix":"// before\ncurl -X PUT .../sinks/my-tenant/my-ns/my-sink   # no body -> sinkConfig == null\n// after\ncurl -X PUT .../sinks/my-tenant/my-ns/my-sink \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"className\":\"org.example.MySink\",\"inputs\":\"my-topic\"}'","handlingStrategy":"validation","validationCode":"if (sinkConfig == null) {\n    throw new IllegalArgumentException(\"sinkConfig must be provided\");\n}\nif (sinkConfig.getClassName() == null || sinkConfig.getClassName().isBlank()) {\n    throw new IllegalArgumentException(\"sinkConfig.className must be set\");\n}","typeGuard":"boolean hasValidSinkConfig(SinkConfig cfg) {\n    return cfg != null && cfg.getClassName() != null && !cfg.getClassName().isBlank();\n}","tryCatchPattern":"try {\n    sinks.registerSink(tenant, ns, name, cfg, null, null, null, authParams);\n} catch (RestException e) {\n    if (e.getResponse().getStatus() == 400 && \"Sink config is not provided\".equals(e.getMessage())) {\n        // the request body was missing or not deserialized; fix headers/body\n    } else { throw e; }\n}","preventionTips":["Always send Content-Type: application/json with a non-empty body","Log the deserialized SinkConfig client-side to catch silent null deserialization","Build the config via a typed builder rather than ad-hoc JSON strings"],"tags":["rest-api","bad-request","validation","pulsar-sinks"],"backgroundTag":"missing-required-parameter","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"}