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: Malformed token
What it means
NimbusJwtDecoder.parse wraps JWTParser.parse failures: when Nimbus throws ParseException the token is structurally invalid, and the decoder throws BadJwtException 'An error occurred while attempting to decode the Jwt: Malformed token'.
Solutions
- Confirm the token is actually a JWT (three dot-separated Base64URL segments) — log/inspect the raw Authorization header server-side.
- Check for transport corruption: strip whitespace, avoid re-encoding, ensure the full token is forwarded (long tokens can be cut by proxies/gateways).
- If the token is opaque, use an introspection decoder (NimbusOpaqueTokenIntrospector / OpaqueTokenIntrospector) instead of NimbusJwtDecoder.
- Catch BadJwtException and return 401 so the client re-authenticates with a fresh token.
Example fix
// before
String token = request.getHeader("Authorization").replace("Bearer ", "").trim();
// after
String auth = request.getHeader("Authorization");
if (auth != null && auth.startsWith("Bearer ")) {
String token = auth.substring(7).trim(); // validate non-empty, 3 segments
} Defensive patterns
Strategy: try-catch
Validate before calling
String token = authorizationHeader != null && authorizationHeader.startsWith("Bearer ")
? authorizationHeader.substring(7).trim() : null;
boolean plausible = token != null && token.chars().filter(c -> c == '.').count() == 2
&& !token.contains(" ") && !token.contains("\n"); Type guard
boolean looksLikeJwt(String token) {
return token != null && token.split("\\.", -1).length == 3
&& token.matches("[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]*");
} Try / catch
try { return jwtDecoder.decode(token); }
catch (BadJwtException e) {
// malformed token: respond 401 WWW-Authenticate: Bearer error="invalid_token"
} Prevention
- Distinguish JWTs from opaque tokens before choosing decoder vs introspector
- Strip 'Bearer ' exactly once and trim; never re-encode the token in transit
- Check proxies/gateways for header size limits that truncate long tokens
When it happens
Trigger: decode() → parse(token) with a string that is not parseable as a JWT: missing segments, invalid Base64URL, oversized/malformed JSON header or payload, wrong compact serialization.
Common situations: Sending an opaque token or OAuth2 access token that is not a JWT to a JWT decoder; truncating the Authorization header value; whitespace/newlines inside the token; double-URL-encoding the bearer token in transit; proxy mangling the header.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- An error occurred while attempting to decode the Jwt
- An error occurred while attempting to decode the Jwt: +…
- An error occurred while attempting to decode the Jwt…
- An error occurred reading the OAuth 2.0 Access Token…
- An error occurred while attempting to decode the Jwt…
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/78c891692d945403.
Report an issue: GitHub.
Appendix: source
Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java:156
@Override
public Jwt decode(String token) throws JwtException {
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
}View on GitHub (pinned to 96852e8860)