apache/pulsar · error · PulsarClientException.UnsupportedAuthenticationException

v4 plugin ${v4.getClass().getName} returned no Authenticatio

Error message

v4 plugin ${v4.getClass().getName} returned no AuthenticationDataProvider

What it means

During the v5 authentication handshake, the v4 adapter calls the legacy plugin's getAuthData(brokerHost) off the event loop. If the plugin returns null — meaning it produced no AuthenticationDataProvider for this exchange — the adapter raises PulsarClientException.UnsupportedAuthenticationException naming the plugin class, because the protocol requires a provider to continue the conversation.

Source

Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/v5/LegacyV4AuthenticationAdapter.java:373

                return Optional.of(kind.cast(this));
            }
            if (kind == HttpAuthHeadersProvider.class) {
                return supportsHttp ? Optional.of(kind.cast(this)) : Optional.empty();
            }
            return Optional.empty();
        }

        @Override
        public String authMethodName() {
            return v4.getAuthMethodName();
        }

        @Override
        public CompletableFuture<BinaryAuthData> getAuthDataAsync(AuthenticationCallContext callContext) {
            return supplyOffloaded(() -> {
                AuthenticationDataProvider d = v4.getAuthData(callContext.brokerHost());
                if (d == null) {
                    throw new PulsarClientException.UnsupportedAuthenticationException(
                            "v4 plugin " + v4.getClass().getName() + " returned no AuthenticationDataProvider");
                }
                // The provider is retained for this exchange so a following challenge round continues the
                // same conversation, and the initial credential comes from authenticate(INIT_AUTH_DATA) —
                // verbatim what the v4 client did. Its default implementation returns getCommandData(), so a
                // single-pass plugin is unaffected, while a challenge/response plugin (which serves its
                // first frame only through authenticate) keeps working.
                callContext.setStateObject(AuthenticationDataProvider.class, d);
                AuthData initial = d.authenticate(AuthData.INIT_AUTH_DATA);
                return new BinaryAuthData(initial == null ? new byte[0] : initial.getBytes());
            });
        }

        @Override
        public CompletableFuture<ChallengeResponse> respondToChallengeAsync(AuthenticationCallContext callContext,
                AuthChallenge challenge) {
            return supplyOffloaded(() -> {
                AuthenticationDataProvider d = callContext.getStateObject(AuthenticationDataProvider.class)

View on GitHub (pinned to 820761864e)

Solutions

  1. Fix or reconfigure the v4 plugin (authParams/authParamsString) so getAuthData returns a valid AuthenticationDataProvider.
  2. Check the broker hostname passed in the call context matches what the plugin expects (host-based plugins return null for unknown hosts).
  3. If you own the plugin, return a provider or throw a descriptive AuthenticationException instead of returning null.
  4. Verify the plugin is fully initialized (initialize(authParams) called) before the handshake.

Example fix

// before (in v4 plugin)
public AuthenticationDataProvider getAuthData(String host) { return configured ? data : null; }
// after
public AuthenticationDataProvider getAuthData(String host) {
    if (!configured) throw new AuthenticationException("plugin not configured");
    return data;
}
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: exercise the plugin before the handshake
AuthenticationDataProvider d = v4.getAuthData(expectedBrokerHost);
if (d == null) {
    throw new IllegalStateException("v4 plugin " + v4.getClass().getName() + " returned null for host " + expectedBrokerHost);
}

Try / catch

try {
    BinaryAuthData data = adapter.getAuthDataAsync(ctx).join();
} catch (java.util.concurrent.CompletionException e) {
    if (e.getCause() instanceof PulsarClientException.UnsupportedAuthenticationException
            && e.getCause().getMessage().contains("returned no AuthenticationDataProvider")) {
        // reconfigure or replace the plugin
    } else throw e.getCause();
}

Prevention

When it happens

Trigger: A wrapped v4 plugin whose getAuthData(String) returns null during getAuthDataAsync — e.g. a plugin that only supports specific host names or returns null when its state was not initialized.

Common situations: Misconfigured v4 plugins (missing/malformed authParams so initialization silently failed); plugins written for a different broker hostname pattern; plugin implementations that return null instead of throwing when credentials are absent.

Understand the failure class

Related errors


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