{"record":{"id":"3834d42e79ef0266","repo":"apache/pulsar","slug":"v4-plugin-v4-getclass-getname-provided-no-htt","errorCode":null,"errorMessage":"v4 plugin ${v4.getClass().getName} provided no HTTP auth headers (hasDataForHttp()/getHttpHeaders() returned nothing)","messagePattern":"v4 plugin (.+?) provided no HTTP auth headers \\(hasDataForHttp\\(\\)/getHttpHeaders\\(\\) returned nothing\\)","errorType":"exception","errorClass":"PulsarClientException.UnsupportedAuthenticationException","httpStatus":null,"severity":"error","filePath":"pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/v5/LegacyV4AuthenticationAdapter.java","lineNumber":407,"sourceCode":"                AuthChallenge challenge) {\n            return supplyOffloaded(() -> {\n                AuthenticationDataProvider d = callContext.getStateObject(AuthenticationDataProvider.class)\n                        .orElse(null);\n                if (d == null) {\n                    d = v4.getAuthData(callContext.brokerHost());\n                    callContext.setStateObject(AuthenticationDataProvider.class, d);\n                }\n                AuthData response = d.authenticate(AuthData.of(challenge.bytes()));\n                return new ChallengeResponse(response == null ? null : response.getBytes());\n            });\n        }\n\n        @Override\n        public CompletableFuture<HttpAuthHeaders> getHttpHeadersAsync(HttpAuthCallContext callContext) {\n            return supplyOffloaded(() -> {\n                AuthenticationDataProvider d = v4.getAuthData();\n                if (d == null || !d.hasDataForHttp() || d.getHttpHeaders() == null) {\n                    throw new PulsarClientException.UnsupportedAuthenticationException(\n                            \"v4 plugin \" + v4.getClass().getName() + \" provided no HTTP auth headers \"\n                                    + \"(hasDataForHttp()/getHttpHeaders() returned nothing)\");\n                }\n                Map<String, String> headers = new HashMap<>();\n                for (Map.Entry<String, String> e : d.getHttpHeaders()) {\n                    headers.put(e.getKey(), e.getValue());\n                }\n                return HttpAuthHeaders.of(headers);\n            });\n        }\n    }\n\n    /**\n     * Adapter for plugins that drive a multi-stage challenge/response flow such as SASL, where the v4\n     * {@link AuthenticationDataProvider#authenticate(AuthData)} method is used to exchange tokens.\n     *\n     * <p>The per-exchange v4 {@link AuthenticationDataProvider} is stored in the call-context state slot\n     * so the same provider instance handles the whole challenge/response exchange.","sourceCodeStart":389,"sourceCodeEnd":425,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/v5/LegacyV4AuthenticationAdapter.java#L389-L425","documentation":"For HTTP-based v5 authentication, the v4 adapter asks the legacy plugin for HTTP headers via getAuthData() -> hasDataForHttp()/getHttpHeaders(). If the provider is null, reports no HTTP data, or returns null headers, getHttpHeadersAsync throws UnsupportedAuthenticationException naming the plugin, since the v5 HTTP flow cannot proceed without auth headers.","triggerScenarios":"A wrapped v4 plugin whose AuthenticationDataProvider returns hasDataForHttp()==false or getHttpHeaders()==null during getHttpHeadersAsync — typically a plugin designed only for the binary command/TLS flow, not HTTP.","commonSituations":"Using a v4 token/cert plugin meant for the Pulsar binary protocol against an HTTP endpoint (e.g. websocket or admin REST auth path); plugin implementations that only populate HTTP headers when a specific context is present.","solutions":["Use a v4 plugin that implements hasDataForHttp()/getHttpHeaders(), or switch to a v5-native HTTP-capable authentication plugin.","If you own the plugin, implement hasDataForHttp() returning true and populate getHttpHeaders() with the required auth headers.","Configure separate authentication for the HTTP path if the binary-protocol plugin cannot produce HTTP headers.","Check the plugin's configuration so it recognizes the request and produces header data (some plugins gate on endpoint)."],"exampleFix":"// before (in v4 plugin)\npublic boolean hasDataForHttp() { return false; }\n// after\n@Override\npublic boolean hasDataForHttp() { return true; }\n@Override\npublic List<KeyValuePair> getHttpHeaders() {\n    return Collections.singletonList(new KeyValuePair(\"Authorization\", \"Bearer \" + token));\n}","handlingStrategy":"validation","validationCode":"AuthenticationDataProvider d = v4.getAuthData();\nboolean httpCapable = d != null && d.hasDataForHttp() && d.getHttpHeaders() != null;\nif (!httpCapable) {\n    throw new IllegalStateException(\"plugin cannot produce HTTP headers; use an HTTP-capable plugin\");\n}","typeGuard":"static boolean supportsHttpAuth(org.apache.pulsar.client.api.Authentication a) throws Exception {\n    AuthenticationDataProvider d = a.getAuthData();\n    return d != null && d.hasDataForHttp() && d.getHttpHeaders() != null;\n}","tryCatchPattern":"try {\n    HttpAuthHeaders h = adapter.getHttpHeadersAsync(ctx).join();\n} catch (java.util.concurrent.CompletionException e) {\n    if (e.getCause() instanceof PulsarClientException.UnsupportedAuthenticationException\n            && e.getCause().getMessage().contains(\"provided no HTTP auth headers\")) {\n        // switch to an HTTP-capable auth plugin\n    } else throw e.getCause();\n}","preventionTips":["Verify hasDataForHttp()/getHttpHeaders() on the plugin before routing HTTP traffic through it.","Use plugins explicitly documented as supporting HTTP (websocket/REST) auth.","Keep binary-protocol and HTTP auth configuration separate."],"tags":["authentication","http-headers","unsupported-authentication","v4-plugin"],"backgroundTag":"missing-http-auth-headers","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"}