spring-projects/spring-security · error · JwtException
An error occurred while attempting to decode the Jwt…
Error message
An error occurred while attempting to decode the Jwt: Malformed Jwk set
What it means
createJwt wraps RemoteKeySourceException from the JWK source: when the failure cause is a ParseException, the JWK Set document fetched from the jwk-set-uri was not valid JSON, so it throws JwtException 'An error occurred while attempting to decode the Jwt: Malformed Jwk set'.
Solutions
- curl the configured jwk-set-uri and confirm the body is valid JSON containing a 'keys' array.
- Fix the jwk-set-uri (e.g. use the provider's /.well-known/openid-configuration jwks_uri value) instead of guessing the URL.
- Check proxies/gateways are not interposing HTML content (auth redirects, error pages) on the JWKS request.
- Clear any cached JWKS (restart or adjust cache settings) if a previously bad response was cached.
Example fix
// before
NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/certs") // wrong path returning HTML
// after
NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/oauth2/default/jwks") // real JWKS endpoint Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: JWKS endpoint must return JSON
// curl -fsS "$JWK_SET_URI" | python3 -c 'import json,sys; json.load(sys.stdin); print("valid json")' Try / catch
try { return jwtDecoder.decode(token); }
catch (JwtException e) {
if (e.getMessage().contains("Malformed Jwk set")) {
// JWKS endpoint returned non-JSON: fix jwk-set-uri or proxy, then retry after cache expiry
}
} Prevention
- Take jwks_uri from the provider's discovery document rather than guessing the path
- Verify the JWKS URL from the app host (auth redirects and HTML error pages are common culprits)
- Monitor the JWKS endpoint in health checks; keep JWK cache TTL sane so bad responses expire
When it happens
Trigger: decode() → createJwt → JWKSet retrieval (RemoteJWKSet) fetches the JWKS endpoint and parsing the response body as JSON throws ParseException, wrapped in RemoteKeySourceException with ParseException as cause.
Common situations: jwk-set-uri pointing at an HTML error page, login page, or wrong endpoint; a proxy/gateway returning an HTML 200 error page; misconfigured mock server returning non-JSON; cached/stale JWK responses corrupted by intermediary.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- An error occurred reading the OAuth 2.0 Authorization…
- An error occurred reading the OpenID Client Registration
- An error occurred reading the OpenID Provider Configuration
- An error occurred reading the UserInfo response
- An error occurred writing the OpenID Client Registration
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/b99e1fd0f0b53b18.
Report an issue: GitHub.
Appendix: source
Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java:178
}
private Jwt createJwt(String token, JWT parsedJwt) {
try {
// Verify the signature
JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
// @formatter:off
return Jwt.withTokenValue(token)
.headers((h) -> h.putAll(headers))
.claims((c) -> c.putAll(claims))
.build();
// @formatter:on
}
catch (RemoteKeySourceException ex) {
this.logger.trace("Failed to retrieve JWK set", ex);
if (ex.getCause() instanceof ParseException) {
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed Jwk set"), ex);
}
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
}
catch (JOSEException ex) {
this.logger.trace("Failed to process JWT", ex);
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
}
catch (Exception ex) {
this.logger.trace("Failed to process JWT", ex);
if (ex.getCause() instanceof ParseException) {
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed payload"), ex);
}
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
}
}
private Jwt validateJwt(Jwt jwt) {
OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);View on GitHub (pinned to 96852e8860)