apache/pulsar · error · PulsarClientException.UnsupportedAuthenticationException
v4 plugin provided no HTTP auth headers…
Error message
v4 plugin ${v4.getClass().getName} provided no HTTP auth headers (hasDataForHttp()/getHttpHeaders() returned nothing) What it means
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.
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).
Example fix
// before (in v4 plugin)
public boolean hasDataForHttp() { return false; }
// after
@Override
public boolean hasDataForHttp() { return true; }
@Override
public List<KeyValuePair> getHttpHeaders() {
return Collections.singletonList(new KeyValuePair("Authorization", "Bearer " + token));
} Defensive patterns
Strategy: validation
Validate before calling
AuthenticationDataProvider d = v4.getAuthData();
boolean httpCapable = d != null && d.hasDataForHttp() && d.getHttpHeaders() != null;
if (!httpCapable) {
throw new IllegalStateException("plugin cannot produce HTTP headers; use an HTTP-capable plugin");
} Type guard
static boolean supportsHttpAuth(org.apache.pulsar.client.api.Authentication a) throws Exception {
AuthenticationDataProvider d = a.getAuthData();
return d != null && d.hasDataForHttp() && d.getHttpHeaders() != null;
} Try / catch
try {
HttpAuthHeaders h = adapter.getHttpHeadersAsync(ctx).join();
} catch (java.util.concurrent.CompletionException e) {
if (e.getCause() instanceof PulsarClientException.UnsupportedAuthenticationException
&& e.getCause().getMessage().contains("provided no HTTP auth headers")) {
// switch to an HTTP-capable auth plugin
} else throw e.getCause();
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- v4 plugin returned no AuthenticationDataProvider
- authentication plugin does not expose…
- A v5 authentication plugin
- Allowed offset for athenz role token verification must not…
- Audiences in token is not in expected format
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/3834d42e79ef0266.
Report an issue: GitHub.
Appendix: source
Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/v5/LegacyV4AuthenticationAdapter.java:407
AuthChallenge challenge) {
return supplyOffloaded(() -> {
AuthenticationDataProvider d = callContext.getStateObject(AuthenticationDataProvider.class)
.orElse(null);
if (d == null) {
d = v4.getAuthData(callContext.brokerHost());
callContext.setStateObject(AuthenticationDataProvider.class, d);
}
AuthData response = d.authenticate(AuthData.of(challenge.bytes()));
return new ChallengeResponse(response == null ? null : response.getBytes());
});
}
@Override
public CompletableFuture<HttpAuthHeaders> getHttpHeadersAsync(HttpAuthCallContext callContext) {
return supplyOffloaded(() -> {
AuthenticationDataProvider d = v4.getAuthData();
if (d == null || !d.hasDataForHttp() || d.getHttpHeaders() == null) {
throw new PulsarClientException.UnsupportedAuthenticationException(
"v4 plugin " + v4.getClass().getName() + " provided no HTTP auth headers "
+ "(hasDataForHttp()/getHttpHeaders() returned nothing)");
}
Map<String, String> headers = new HashMap<>();
for (Map.Entry<String, String> e : d.getHttpHeaders()) {
headers.put(e.getKey(), e.getValue());
}
return HttpAuthHeaders.of(headers);
});
}
}
/**
* Adapter for plugins that drive a multi-stage challenge/response flow such as SASL, where the v4
* {@link AuthenticationDataProvider#authenticate(AuthData)} method is used to exchange tokens.
*
* <p>The per-exchange v4 {@link AuthenticationDataProvider} is stored in the call-context state slot
* so the same provider instance handles the whole challenge/response exchange.View on GitHub (pinned to 820761864e)