{"record":{"id":"2395ef5fb5461d98","repo":"quarkusio/quarkus","slug":"expected-claim-s-must-be-a-list-of-strings","errorCode":null,"errorMessage":"expected claim %s must be a list of strings","messagePattern":"expected claim (.+?) must be a list of strings","errorType":"validation","errorClass":"org.jose4j.jwt.MalformedClaimException","httpStatus":null,"severity":"error","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java","lineNumber":746,"sourceCode":"                }\n            }\n            return null;\n        }\n\n        private static String validate(String requiredClaimName, Set<String> requiredClaimValues, JwtClaims claims)\n                throws MalformedClaimException {\n            if (!claims.hasClaim(requiredClaimName)) {\n                return \"claim \" + requiredClaimName + \" is missing\";\n            }\n            if (claims.isClaimValueString(requiredClaimName)) {\n                if (requiredClaimValues.size() == 1) {\n                    String actualClaimValue = claims.getStringClaimValue(requiredClaimName);\n                    String requiredClaimValue = requiredClaimValues.iterator().next();\n                    if (!requiredClaimValue.equals(actualClaimValue)) {\n                        return \"claim \" + requiredClaimName + \" does not match expected value of \" + requiredClaimValues;\n                    }\n                } else {\n                    throw new MalformedClaimException(\"expected claim \" + requiredClaimName + \" must be a list of strings\");\n                }\n            } else {\n                if (claims.isClaimValueStringList(requiredClaimName)) {\n                    List<String> actualClaimValues = claims.getStringListClaimValue(requiredClaimName);\n                    for (String requiredClaimValue : requiredClaimValues) {\n                        if (!actualClaimValues.contains(requiredClaimValue)) {\n                            return \"claim \" + requiredClaimName + \" does not match expected value of \" + requiredClaimValues;\n                        }\n                    }\n                } else {\n                    throw new MalformedClaimException(\n                            \"expected claim \" + requiredClaimName + \" must be a list of strings or a string\");\n                }\n            }\n            return null;\n        }\n    }\n","sourceCodeStart":728,"sourceCodeEnd":764,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java#L728-L764","documentation":"Thrown by OidcProvider's CustomClaimsValidator when a required claim configured in quarkus.oidc.token.required-claims is expected to be a single string value in the token, but the app configured MULTIPLE required values for that claim (a set), and the token claim is a plain string. A string claim can only be compared against exactly one expected value, so the configuration is contradictory and MalformedClaimException is thrown.","triggerScenarios":"quarkus.oidc.token.required-claims.<name> maps to a set with 2+ values (e.g. comma-separated list) while the verified token's claim with that name is a single string, not a JSON array of strings.","commonSituations":"Developers write required-claims.acr=loa2,loa3 expecting OR-matching, but the provider emits acr as the string 'loa2' rather than an array; misunderstanding that multiple values only work when the token claim is a string list.","solutions":["Configure only ONE required value per claim if the token emits it as a plain string","If multiple alternatives are needed, the token claim must be a JSON array of strings — ask the issuer to emit e.g. \"acr\": [\"loa2\",\"loa3\"]","Match exactly the value the token actually carries (decode the token to inspect the claim type)","Use a custom Validator bean instead of static config if flexible matching is required"],"exampleFix":"# before (token acr is a plain string \"loa2\")\nquarkus.oidc.token.required-claims.acr=loa2,loa3\n# after\nquarkus.oidc.token.required-claims.acr=loa2","handlingStrategy":"validation","validationCode":"// decode the token and check the claim type before configuring multiple expected values\nvar claims = decodeJwtClaims(token);\nObject acr = claims.get(\"acr\");\nif (!(acr instanceof List)) {\n    // claim is a plain string: configure exactly ONE required value\n}","typeGuard":"boolean isStringOrStringList(Object v) {\n    return v instanceof String || (v instanceof List<?> l && l.stream().allMatch(String.class::isInstance));\n}","tryCatchPattern":null,"preventionTips":["Decode a real token to see the claim's JSON type before writing required-claims config","Use a single required value for string claims; arrays only for list claims","Prefer a custom Validator bean for OR-style matching"],"tags":["oidc","jwt-claims","configuration","validation"],"backgroundTag":"claim-type-mismatch","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}