apache/pulsar · warning · UnsupportedCallbackException

Unrecognized SASL GSSAPI Client Callback.

Error message

Unrecognized SASL GSSAPI Client Callback.

What it means

PulsarSaslClient's ClientCallbackHandler only understands javax.security.sasl.AuthorizeCallback. When the JVM's SASL/GSSAPI implementation hands it any other Callback type during client creation or evaluation, it throws UnsupportedCallbackException with this message. It indicates a callback type the client handler does not support was requested.

Source

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

            }
        } catch (Exception e) {
            log.error().exception(e.getCause()).log("SASL error");
            throw new AuthenticationException("SASL/JAAS error" + e.getCause());
        }
    }

    public boolean hasInitialResponse() {
        return saslClient.hasInitialResponse();
    }

    static class ClientCallbackHandler implements CallbackHandler {
        @Override
        public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
            for (Callback callback : callbacks) {
                if (callback instanceof AuthorizeCallback) {
                    handleAuthorizeCallback((AuthorizeCallback) callback);
                } else {
                    throw new UnsupportedCallbackException(callback, "Unrecognized SASL GSSAPI Client Callback.");
                }
            }
        }

        private void handleAuthorizeCallback(AuthorizeCallback ac) {
            String authid = ac.getAuthenticationID();
            String authzid = ac.getAuthorizationID();
            if (authid.equals(authzid)) {
                ac.setAuthorized(true);
            } else {
                ac.setAuthorized(false);
            }
            if (ac.isAuthorized()) {
                ac.setAuthorizedID(authzid);
            }
            log.info().attr("authenticationID", authid).attr("authorizationID", authzid)
                    .log("Successfully authenticated");
        }

View on GitHub (pinned to 820761864e)

Solutions

  1. Only use this ClientCallbackHandler with the GSSAPI mechanism — GSSAPI obtains credentials from the JAAS Subject and should only ask AuthorizeCallback
  2. If a different mechanism is required, extend the handler with instanceof branches for the needed callbacks (NameCallback, PasswordCallback, etc.)
  3. Verify the configured SASL mechanism string is "GSSAPI" (as in PulsarSaslClient's mechs array) and no other provider is picking up the creation
  4. Check the JVM's SASL provider order so the standard GSSAPI provider handles the mechanism rather than a custom one

Example fix

// before
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
    for (Callback callback : callbacks) {
        if (callback instanceof AuthorizeCallback) {
            handleAuthorizeCallback((AuthorizeCallback) callback);
        } else {
            throw new UnsupportedCallbackException(callback, "Unrecognized SASL GSSAPI Client Callback.");
        }
    }
}

// after
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
    for (Callback callback : callbacks) {
        if (callback instanceof AuthorizeCallback) {
            handleAuthorizeCallback((AuthorizeCallback) callback);
        } else if (callback instanceof NameCallback) {
            ((NameCallback) callback).setName(clientPrincipalName);
        } else {
            throw new UnsupportedCallbackException(callback, "Unrecognized SASL GSSAPI Client Callback.");
        }
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure GSSAPI is the negotiated mechanism so only AuthorizeCallback is delivered
String[] mechs = {"GSSAPI"};
if (!java.util.Arrays.asList(javax.security.sasl.Sasl.getSaslClientFactories().stream()
        .flatMap(f -> java.util.Arrays.stream(f.getMechanismNames(new java.util.HashMap<>())))
        .toArray(String[]::new)).contains("GSSAPI")) {
    throw new IllegalStateException("GSSAPI mechanism not available; handler only supports AuthorizeCallback");
}

Type guard

boolean isSupportedCallback(javax.security.auth.callback.Callback c) {
    return c instanceof javax.security.sasl.AuthorizeCallback;
}

Try / catch

try {
    saslClient.evaluateChallenge(token);
} catch (javax.security.sasl.SaslException e) {
    if (e.getCause() instanceof javax.security.auth.callback.UnsupportedCallbackException) {
        log.error("ClientCallbackHandler received an unsupported callback: {}",
            ((javax.security.auth.callback.UnsupportedCallbackException) e.getCause()).getCallback().getClass());
        // switch mechanism to GSSAPI or extend the handler
    }
}

Prevention

When it happens

Trigger: A SASL mechanism or provider (e.g. a non-GSSAPI mechanism, or a provider variant) invoking the handler with callbacks like NameCallback, PasswordCallback, or RealmCallback instead of only AuthorizeCallback; plugging this handler into a different SASL mechanism than GSSAPI.

Common situations: Using the Pulsar SASL client code with a mechanism other than GSSAPI (e.g. SCRAM/DIGEST-MD5 via shared code); a JVM SASL provider that requests extra callbacks; custom provider implementations with non-standard callback requirements.

Related errors


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