apache/pulsar · error · PulsarClientException.UnsupportedAuthenticationException

v4 plugin ${v4.getClass().getName} provided no HTTP auth hea

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.

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)

Solutions

  1. Use a v4 plugin that implements hasDataForHttp()/getHttpHeaders(), or switch to a v5-native HTTP-capable authentication plugin.
  2. If you own the plugin, implement hasDataForHttp() returning true and populate getHttpHeaders() with the required auth headers.
  3. Configure separate authentication for the HTTP path if the binary-protocol plugin cannot produce HTTP headers.
  4. 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

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


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/3834d42e79ef0266. Report an issue: GitHub.