apache/pulsar · error · AuthenticationException

saslToken is null

Error message

saslToken is null

What it means

PulsarSaslClient.evaluateChallenge(saslToken) requires the server-provided SASL challenge bytes wrapped in an AuthData object. When the caller passes null, the method immediately throws this AuthenticationException. This is a defensive argument check — the SASL protocol expects each challenge from the broker to carry a token.

Source

Thrown at pulsar-client-auth-sasl/src/main/java/org/apache/pulsar/client/impl/auth/PulsarSaslClient.java:96

                    String[] mechs = {"GSSAPI"};
                    return Sasl.createSaslClient(mechs, clientPrincipalName, serviceName, serviceHostname, null,
                        new ClientCallbackHandler());
                }
            });
        } catch (PrivilegedActionException err) {
            log.error().exception(err.getCause()).log("GSSAPI client error");
            throw new SaslException("error while booting GSSAPI client", err.getCause());
        }

        if (saslClient == null) {
            throw new SaslException("Cannot create JVM SASL Client");
        }

    }

    public AuthData evaluateChallenge(final AuthData saslToken) throws AuthenticationException {
        if (saslToken == null) {
            throw new AuthenticationException("saslToken is null");
        }
        try {
            if (clientSubject != null) {
                final byte[] retval = Subject.doAs(clientSubject, new PrivilegedExceptionAction<byte[]>() {
                    @Override
                    public byte[] run() throws SaslException {
                        return saslClient.evaluateChallenge(saslToken.getBytes());
                    }
                });
                return AuthData.of(retval);

            } else {
                return AuthData.of(saslClient.evaluateChallenge(saslToken.getBytes()));
            }
        } catch (Exception e) {
            log.error().exception(e.getCause()).log("SASL error");
            throw new AuthenticationException("SASL/JAAS error" + e.getCause());
        }

View on GitHub (pinned to 820761864e)

Solutions

  1. Ensure every challenge passed to evaluateChallenge is a non-null AuthData (e.g. AuthData.INIT from the client for the first step)
  2. Inspect the Authentication/AuthenticationDataProvider implementation feeding the token; fix code paths that return null
  3. If the broker sent no token, verify broker-side SASL configuration and the client's authParams (JAAS subject) are consistent
  4. For custom plugins, default the first token to AuthData.of(new byte[0]) or AuthData.INIT rather than null

Example fix

// before
authData = saslClient.evaluateChallenge(challenge); // challenge may be null

// after
authData = saslClient.evaluateChallenge(challenge == null ? AuthData.INIT : challenge);
Defensive patterns

Strategy: validation

Validate before calling

// before calling evaluateChallenge
if (saslToken == null) {
    throw new IllegalArgumentException("SASL challenge token must not be null; use AuthData.INIT for the first step");
}
AuthData resp = saslClient.evaluateChallenge(saslToken);

Type guard

boolean isValidAuthData(AuthData d) {
    return d != null && d.getBytes() != null;
}

Prevention

When it happens

Trigger: Calling evaluateChallenge(null) directly; an Authentication/AuthenticationDataProvider path that produces a null AuthData for a challenge step; wiring a custom Authentication implementation into the SASL flow that returns null on some handshake stage.

Common situations: Custom auth plugin integration where authenticate()/getAuthData returns null before delegating to PulsarSaslClient; broker sends an empty/unexpected challenge and the client-side adapter maps it to null; unit tests invoking evaluateChallenge directly without constructing AuthData.

Related errors


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