spring-projects/spring-security · error · JwtEncodingException
Failed to encode the JWT due to signing error: Unable to…
Error message
Failed to encode the JWT due to signing error: Unable to convert 'jwk' JOSE header
What it means
NimbusJwtEncoder's header converter tries to parse the user-supplied 'jwk' JOSE header map into a nimbus-jose-jwt JWK via JWK.parse() and wraps any failure in a JwtEncodingException. It means the Map<String,Object> set as the jwk header (e.g. to embed a public key via 'jwk' or trigger key thumbprint embedding) is not a valid JWK JSON structure. The library rejects it early so no malformed JWS is produced.
Solutions
- Ensure the map contains at least the mandatory 'kty' member ('RSA', 'EC', 'oct') with string values, per RFC 7517.
- Instead of a hand-built map, serialize a real nimbus JWK: jwk.toJSONObject() and pass that map, guaranteeing structural validity.
- Inspect the cause via getCause() — JWK.parse reports the missing/invalid member.
- If you only need kid/thumbprint, use .keyId(...) or .jwk(URI/ thumbprint) options rather than embedding a raw jwk map.
Example fix
// before
Map<String, Object> jwk = Map.of("key_type", "RSA", "n", n, "e", e);
// after
Map<String, Object> jwk = rsaKey.toJSONObject(); // has "kty":"RSA", "n", "e" Defensive patterns
Strategy: validation
Validate before calling
// validate a jwk header map before passing to JwsHeader.with(...).jwk(map)
if (!(jwk.get("kty") instanceof String kty) || kty.isBlank()) {
throw new IllegalArgumentException("jwk header map missing required 'kty'");
}
JWK.parse(jwk); // dry-run parse; throws if structure invalid Try / catch
try {
token = jwtEncoder.encode(params);
} catch (JwtEncodingException ex) {
if (ex.getMessage().contains("'jwk' JOSE header")) {
throw new IllegalArgumentException("Embedded jwk header is not a valid JWK", ex);
}
throw ex;
} Prevention
- Build jwk header maps from a nimbus JWK instance's toJSONObject(), never by hand.
- Always include 'kty' and use RFC 7517 member names exactly.
- Add a startup test that round-trips your header map through JWK.parse().
- Prefer keyId/keyURL header options over embedding raw JWK JSON when possible.
When it happens
Trigger: Calling JwsHeader.with(...).jwk(Map<String,Object>) with a map that lacks required JWK fields ('kty'), contains values of wrong types (e.g. numbers where strings are required), or nested structures JWK.parse() cannot read; passing a full private-key map with unsupported extra members.
Common situations: Copying a PEM or X.509 structure into the jwk header instead of a proper JWK; building the map from a properties file where 'kty' was dropped; using snake_case or wrong parameter names (e.g. 'key_type' instead of 'kty'); serializing a JWK to JSON but double-decoding it so values arrive as nested JSON strings.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Failed to encode the JWT due to signing error: Failed to…
- Failed to encode the JWT due to signing error: Failed to…
- Failed to encode the JWT due to signing error: Failed to…
- Failed to encode the JWT due to signing error: Unable to…
- Failed to select a key since there are multiple for the…
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/2cd95c6b2a4f3441.
Report an issue: GitHub.
Appendix: source
Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java:303
}
}
private static JWSHeader convert(JwsHeader headers) {
JwsAlgorithm algorithm = headers.getAlgorithm();
Assert.notNull(algorithm, "JWS header algorithm must not be null");
JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.parse(algorithm.getName()));
if (headers.getJwkSetUrl() != null) {
builder.jwkURL(convertAsURI(JoseHeaderNames.JKU, headers.getJwkSetUrl()));
}
Map<String, Object> jwk = headers.getJwk();
if (!CollectionUtils.isEmpty(jwk)) {
try {
builder.jwk(JWK.parse(jwk));
}
catch (Exception ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Unable to convert '" + JoseHeaderNames.JWK + "' JOSE header"), ex);
}
}
String keyId = headers.getKeyId();
if (StringUtils.hasText(keyId)) {
builder.keyID(keyId);
}
if (headers.getX509Url() != null) {
builder.x509CertURL(convertAsURI(JoseHeaderNames.X5U, headers.getX509Url()));
}
List<String> x509CertificateChain = headers.getX509CertificateChain();
if (!CollectionUtils.isEmpty(x509CertificateChain)) {
List<Base64> x5cList = new ArrayList<>();
x509CertificateChain.forEach((x5c) -> x5cList.add(new Base64(x5c)));
if (!x5cList.isEmpty()) {View on GitHub (pinned to 96852e8860)