quarkusio/quarkus · error · org.jose4j.jwt.MalformedClaimException

expected claim %s must be a list of strings or a string

Error message

expected claim %s must be a list of strings or a string

What it means

Thrown by OidcProvider's CustomClaimsValidator when a required claim configured in quarkus.oidc.token.required-claims is present in the token but is neither a string nor a list of strings (e.g. a number, boolean, or object). Only string and string-list claims can be validated against the configured expected values, so MalformedClaimException is thrown.

Source

Thrown at extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java:757

                if (requiredClaimValues.size() == 1) {
                    String actualClaimValue = claims.getStringClaimValue(requiredClaimName);
                    String requiredClaimValue = requiredClaimValues.iterator().next();
                    if (!requiredClaimValue.equals(actualClaimValue)) {
                        return "claim " + requiredClaimName + " does not match expected value of " + requiredClaimValues;
                    }
                } else {
                    throw new MalformedClaimException("expected claim " + requiredClaimName + " must be a list of strings");
                }
            } else {
                if (claims.isClaimValueStringList(requiredClaimName)) {
                    List<String> actualClaimValues = claims.getStringListClaimValue(requiredClaimName);
                    for (String requiredClaimValue : requiredClaimValues) {
                        if (!actualClaimValues.contains(requiredClaimValue)) {
                            return "claim " + requiredClaimName + " does not match expected value of " + requiredClaimValues;
                        }
                    }
                } else {
                    throw new MalformedClaimException(
                            "expected claim " + requiredClaimName + " must be a list of strings or a string");
                }
            }
            return null;
        }
    }

    private static Map<String, Object> tokenMap(String token, TokenType tokenType) {
        return Map.of(tokenType == TokenType.ID_TOKEN ? OidcConstants.ID_TOKEN_VALUE : OidcConstants.ACCESS_TOKEN_VALUE,
                token);
    }

    private static final class CatchingErrorCodeValidator extends ErrorCodeValidatorAdapter {

        private AuthenticationFailedException authenticationFailure;

        private CatchingErrorCodeValidator(Validator validator) {
            super(validator);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Reference only string-valued claims in required-claims (decode the token to check the JSON type)
  2. If the claim is a number/boolean, implement a custom Validator bean instead of using required-claims config
  3. Ask the issuer to emit the claim as a string (e.g. "loa": "2" instead of 2)
  4. Verify the claim name isn't accidentally colliding with a different claim of non-string type

Example fix

// token: "loa": 2 (number)
// before: quarkus.oidc.token.required-claims.loa=2 -> throws
// after: custom Validator bean:
public class LoaValidator implements Validator {
    public Error validate(JwtContext ctx) {
        return ctx.getJwtClaims().getClaimValue("loa", Integer.class) >= 2 ? null : Error.authFailure("loa too low");
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// ensure the claim value type matches what required-claims supports
Object v = decodedClaims.get(claimName);
if (v != null && !(v instanceof String) && !(v instanceof List)) {
    // non-string claim (number/boolean/object): use a custom Validator instead
}

Type guard

boolean isValidatableClaim(Object v) {
    return v instanceof String
        || (v instanceof List<?> l && l.stream().allMatch(String.class::isInstance));
}

Prevention

When it happens

Trigger: quarkus.oidc.token.required-claims.<name> configured for a claim that the token carries as a non-string JSON type — for example "loa": 2 (number) or "roles": {"admin": true} (object).

Common situations: Identity providers emitting numeric authentication levels or structured claims that developers reference in required-claims; copy-pasting claim names from decoded JSON without noticing the value type differs from what's configured.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/b504e4765aede88e. Report an issue: GitHub.