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

  1. curl the configured jwk-set-uri and confirm the body is valid JSON containing a 'keys' array.
  2. Fix the jwk-set-uri (e.g. use the provider's /.well-known/openid-configuration jwks_uri value) instead of guessing the URL.
  3. Check proxies/gateways are not interposing HTML content (auth redirects, error pages) on the JWKS request.
  4. 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

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.

Related errors


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)