spring-projects/spring-security · error · BadJwtException

An error occurred while attempting to decode the Jwt

Error message

An error occurred while attempting to decode the Jwt: %s

What it means

parse() also handles non-ParseException failures from JWTParser.parse (e.g. IllegalStateException, IllegalCallerException, IO errors on nested objects); it wraps the underlying exception message into BadJwtException via the DECODING_ERROR_MESSAGE_TEMPLATE. The %s is filled with ex.getMessage().

Solutions

  1. Read the wrapped message (and cause) to identify the specific parse failure; log the exception chain server-side.
  2. Regenerate the token from the authorization server and compare headers/claims with the failing token.
  3. Ensure token claims use standard JSON types; avoid exotic claim serialization on the issuer side.
  4. Catch BadJwtException and reject the request with 401 invalid_token.

Example fix

// before
catch (BadJwtException e) { /* message ignored */ }
// after
catch (BadJwtException e) {
    logger.warn("JWT rejected: {} cause={}", e.getMessage(), e.getCause(), e);
    // return 401 with WWW-Authenticate: Bearer error="invalid_token"
}
Defensive patterns

Strategy: try-catch

Try / catch

try { return jwtDecoder.decode(token); }
catch (BadJwtException e) {
    log.warn("JWT parse failure: {}", e.getMessage(), e.getCause());
    // respond 401 invalid_token
}

Prevention

When it happens

Trigger: decode() → parse(token) where parsing throws a RuntimeException other than ParseException — e.g. nested/serialized JSON claims that fail to deserialize, illegal header parameter combinations detected by Nimbus.

Common situations: Tokens with non-standard nested claim encodings; corrupted JWTs whose structure parses superficially but fails deeper validation; custom Nimbus configuration or version differences changing parse behavior.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/51c084b61f424653. Report an issue: GitHub.

Appendix: source

Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java:158

		JWT jwt = parse(token);
		if (jwt instanceof PlainJWT) {
			this.logger.trace("Failed to decode unsigned token");
			throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
		}
		Jwt createdJwt = createJwt(token, jwt);
		return validateJwt(createdJwt);
	}

	private JWT parse(String token) {
		try {
			return JWTParser.parse(token);
		}
		catch (Exception ex) {
			this.logger.trace("Failed to parse token", ex);
			if (ex instanceof ParseException) {
				throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed token"), ex);
			}
			throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
		}
	}

	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);

View on GitHub (pinned to 96852e8860)