apache/pulsar · error · AuthenticationException
Audiences in token is not in expected format: ${object}
Error message
Audiences in token is not in expected format: ${object} What it means
This branch is reached when the configured audience claim in the JWT is neither a Collection nor a String. The provider only knows how to validate audience values of those two shapes, so any other JSON type (number, boolean, nested object/array of non-strings) is rejected with this AuthenticationException. The comment 'should not reach here' marks it as a defensive case for malformed tokens.
Source
Thrown at pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProviderToken.java:260
if (object instanceof Collection) {
Collection<String> audiences = (Collection<String>) object;
// audience not contains this broker, throw exception.
if (audiences.stream().noneMatch(audienceInToken -> audienceInToken.equals(audience))) {
incrementFailureMetric(ErrorCode.INVALID_AUDIENCES);
throw new AuthenticationException("Audiences in token: ["
+ String.join(", ", audiences) + "] not contains this broker: " + audience);
}
} else if (object instanceof String) {
if (!object.equals(audience)) {
incrementFailureMetric(ErrorCode.INVALID_AUDIENCES);
throw new AuthenticationException(
"Audiences in token: [" + object + "] not contains this broker: " + audience);
}
} else {
// should not reach here.
incrementFailureMetric(ErrorCode.INVALID_AUDIENCES);
throw new AuthenticationException("Audiences in token is not in expected format: " + object);
}
}
var expiration = jwt.getBody().getExpiration();
var tokenRemainingDurationMs = expiration != null ? expiration.getTime() - new Date().getTime() : null;
authenticationMetricsToken.recordTokenDuration(tokenRemainingDurationMs);
return jwt;
} catch (JwtException e) {
if (e instanceof ExpiredJwtException) {
authenticationMetricsToken.recordTokenExpired();
}
incrementFailureMetric(ErrorCode.INVALID_TOKEN);
throw new AuthenticationException("Failed to authentication token: " + e.getMessage());
}
}
private String getPrincipal(Jws<Claims> jwt) {
try {View on GitHub (pinned to 820761864e)
Solutions
- Re-issue the token so the audience claim is either a string or an array of strings.
- Fix the token issuer's claim generation so it emits the standard aud format (string or list of strings).
- Verify the configured tokenAudienceClaim points at the intended claim; if the claim name collides with a non-audience field, correct it.
- If audience checking is unnecessary, disable it by removing the audience claim configuration.
Example fix
// before (issuer): payload.put("aud", 12345)
// after (issuer): payload.put("aud", "urn:my-broker") Defensive patterns
Strategy: type-guard
Validate before calling
// Before using a token, assert the audience claim type:
Object aud = claims.get("aud");
boolean formatOk = aud instanceof String || aud instanceof java.util.Collection<?>; Type guard
static boolean isSupportedAudienceFormat(Object claimValue) {
return claimValue instanceof String
|| (claimValue instanceof Collection<?> c && c.stream().allMatch(String.class::isInstance));
} Try / catch
try {
String role = authProvider.authenticate(authData);
} catch (AuthenticationException e) {
if (e.getMessage().startsWith("Audiences in token is not in expected format")) {
throw new IllegalStateException("Token issuer emits audience claim with unsupported JSON type; fix the issuer", e);
}
} Prevention
- Use a standard JWT library (jjwt, jose4j) on the issuer side so aud is emitted as string or array of strings.
- Never hand-roll the token payload with non-string audience values.
- Add an issuer-side unit test decoding the produced token and asserting the aud claim type.
- Ensure the configured audience claim name doesn't collide with another custom claim of a different type.
When it happens
Trigger: authenticateToken parses a token where jwt.getBody().get(audienceClaim) returns an object that is not a Collection<String> or String — e.g. the aud-style claim was issued as a number or a map — while audience validation is enabled.
Common situations: A custom token issuer serializes the audience claim in an unexpected type (e.g. numeric realm ID); a misconfigured claim name points at a claim that holds a non-string value; a JSON library upgrade changes deserialization of the claim.
Related errors
- Audiences in token: [${object}] not contains this broker: ${
- ERROR_VERIFYING_JWT
- ERROR_RETRIEVING_PUBLIC_KEY
- Invalid HTTP Authorization header
- No token credentials passed
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/545a85f40a9b255c.
Report an issue: GitHub.