{"record":{"id":"86d07ce6d05b05b2","repo":"spring-projects/spring-security","slug":"failed-to-select-a-key-since-there-are-multiple-fo","errorCode":null,"errorMessage":"Failed to select a key since there are multiple for the signing algorithm [%s]; please specify a selector in NimbusJwsEncoder#setJwkSelector","messagePattern":"Failed to select a key since there are multiple for the signing algorithm \\[(.+?)\\]; please specify a selector in NimbusJwsEncoder#setJwkSelector","errorType":"exception","errorClass":"JwtEncodingException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java","lineNumber":115,"sourceCode":" * @see <a target=\"_blank\" href=\"https://connect2id.com/products/nimbus-jose-jwt\">Nimbus\n * JOSE + JWT SDK</a>\n */\npublic final class NimbusJwtEncoder implements JwtEncoder {\n\n\tprivate static final String ENCODING_ERROR_MESSAGE_TEMPLATE = \"An error occurred while attempting to encode the Jwt: %s\";\n\n\tprivate static final JwsHeader DEFAULT_JWS_HEADER = JwsHeader.with(SignatureAlgorithm.RS256).build();\n\n\tprivate static final JWSSignerFactory JWS_SIGNER_FACTORY = new DefaultJWSSignerFactory();\n\n\tprivate final JwsHeader defaultJwsHeader;\n\n\tprivate final Map<JWK, JWSSigner> jwsSigners = new ConcurrentHashMap<>();\n\n\tprivate final JWKSource<SecurityContext> jwkSource;\n\n\tprivate Converter<List<JWK>, JWK> jwkSelector = (jwks) -> {\n\t\tthrow new JwtEncodingException(\n\t\t\t\tString.format(\n\t\t\t\t\t\t\"Failed to select a key since there are multiple for the signing algorithm [%s]; \"\n\t\t\t\t\t\t\t\t+ \"please specify a selector in NimbusJwsEncoder#setJwkSelector\",\n\t\t\t\t\t\tjwks.get(0).getAlgorithm()));\n\t};\n\n\t/**\n\t * Constructs a {@code NimbusJwtEncoder} using the provided parameters.\n\t * @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}\n\t */\n\tpublic NimbusJwtEncoder(JWKSource<SecurityContext> jwkSource) {\n\t\tthis.defaultJwsHeader = DEFAULT_JWS_HEADER;\n\t\tAssert.notNull(jwkSource, \"jwkSource cannot be null\");\n\t\tthis.jwkSource = jwkSource;\n\t}\n\n\tprivate NimbusJwtEncoder(JWK jwk) {\n\t\tAssert.notNull(jwk, \"jwk cannot be null\");","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java#L97-L133","documentation":"NimbusJwtEncoder's default jwkSelector cannot decide which key to use when the JWKSource returns more than one JWK matching the signing algorithm/headers. Instead of guessing, it throws JwtEncodingException telling the developer to supply an explicit selector via setJwkSelector. This is a deliberate guard against silently signing with the wrong key.","triggerScenarios":"Calling NimbusJwtEncoder.encode(JwtEncoderParameters) (or via JwtGenerator) when the configured jwkSource (e.g. an ImmutableJWKSet holding a JWKSet with several keys of the same algorithm/type, or a rotating key set) matches multiple keys for the request's JWS headers, and setJwkSelector was never called with a custom Converter<List<JWK>, JWK>.","commonSituations":"Key rotation deployments where the JWKS contains old + new keys for the same algorithm; a JWKSet containing both RSA and EC (or multiple RSA) keys that all match the matcher; copying multi-key examples without narrowing by kid; upgrading Spring Security to a version that defaults to throwing instead of picking the first key.","solutions":["Call encoder.setJwkSelector(jwks -> jwks.get(0)) if any matching key is acceptable, or better, a selector that filters by kid/key use.","Prefer narrowing the JWKSource itself (e.g. JWKSet with a single active signing key) so only one key matches.","Add a distinct \"kid\" per key and implement a selector that picks the JWK whose.getKeyID() equals the intended kid.","If keys differ by use, set \"use\":\"sig\" on signing keys so non-signing keys no longer match the JWKSelector matcher.","For rotation, keep exactly one active key in the source used for signing while exposing old keys only for verification."],"exampleFix":"// before\nNimbusJwtEncoder encoder = new NimbusJwtEncoder(jwkSource); // multiple matching keys -> throws\n// after\nencoder.setJwkSelector((jwks) -> jwks.stream()\n\t.filter(jwk -> \"my-key-id\".equals(jwk.getKeyID()))\n\t.findFirst()\n\t.orElseThrow(() -> new JwtEncodingException(\"no matching kid\")));","handlingStrategy":"validation","validationCode":"// Ensure exactly one key will match before encoding, or install a deterministic selector\nList<JWK> candidates = jwkSource.get(new JWKSelector(\n\tnew JWKMatcher.Builder().algorithm(JWSAlgorithm.RS256.getName()).build()), null);\nif (candidates == null || candidates.size() > 1) {\n\tencoder.setJwkSelector(jwks -> jwks.stream()\n\t\t.filter(j -> \"my-kid\".equals(j.getKeyID()))\n\t\t.findFirst()\n\t\t.orElseThrow());\n}","typeGuard":null,"tryCatchPattern":"try {\n\tJwt jwt = encoder.encode(params);\n} catch (org.springframework.security.oauth2.jwt.JwtEncodingException ex) {\n\tif (ex.getMessage().contains(\"multiple\")) {\n\t\t// fall back to a kid-based selector configured at startup\n\t}\n\tthrow ex;\n}","preventionTips":["Keep only one active signing key per algorithm in the encoder's JWKSource","Set keyID on every JWK and select by kid in a custom setJwkSelector","Mark keys with KeyUse.SIGNATURE so non-signing keys don't match","During key rotation, add new keys for verification but encode with a pinned kid"],"tags":["jwt","jwk","signing","key-rotation","spring-security"],"backgroundTag":"jwt-signing-key-selection-failed","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}