spring-projects/spring-security · error · BadJwtException
An error occurred while attempting to decode the Jwt: Malfor
Error message
An error occurred while attempting to decode the Jwt: Malformed payload
What it means
NimbusJwtDecoder.createJwt's final catch-all converts a generic failure whose cause is a ParseException into a BadJwtException with the message 'An error occurred while attempting to decode the Jwt: Malformed payload'. This indicates the JWT string itself could not be parsed as a JOSE object — its compact form is not valid base64url-encoded JSON parts.
Source
Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java:189
.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);
if (result.hasErrors()) {
Collection<OAuth2Error> errors = result.getErrors();
String validationErrorString = getJwtValidationExceptionMessage(errors);
throw new JwtValidationException(validationErrorString, errors);
}
return jwt;
}
private String getJwtValidationExceptionMessage(Collection<OAuth2Error> errors) {
for (OAuth2Error oAuth2Error : errors) {
if (StringUtils.hasLength(oAuth2Error.getDescription())) {View on GitHub (pinned to 96852e8860)
Solutions
- Log the raw Authorization header and inspect the token: it must be three base64url segments separated by dots.
- Ensure the Bearer prefix is stripped and no whitespace/newlines are passed to decode().
- Verify the client is actually configured to obtain JWTs from the IdP (an opaque/reference token will not parse).
- Catch BadJwtException specifically to return 401 to the caller instead of 500.
Example fix
// before
String token = request.getHeader("Authorization"); // "Bearer eyJ..."
Jwt jwt = this.decoder.decode(token);
// after
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7).trim();
}
Jwt jwt = this.decoder.decode(token); Defensive patterns
Strategy: validation
Validate before calling
// Validate compact JWT form before calling decode
static boolean isPlausibleJwt(String token) {
if (token == null || token.isBlank()) return false;
String[] parts = token.split("\\.");
if (parts.length != 3) return false;
try {
Base64.getUrlDecoder().decode(parts[0]);
Base64.getUrlDecoder().decode(parts[1]);
return true;
} catch (IllegalArgumentException e) {
return false;
}
} Try / catch
try {
Jwt jwt = decoder.decode(token);
} catch (BadJwtException ex) {
// malformed token -> always 401, never 500; do not retry
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token format");
} Prevention
- Strip the 'Bearer ' prefix and trim whitespace before decode()
- Reject empty/null Authorization headers early in a filter
- Confirm the client uses the JWT flow (opaque tokens cannot be decoded by NimbusJwtDecoder)
- Never copy tokens across systems with added quoting or line breaks
When it happens
Trigger: decode(encodedJwt) is called with a string that is not a syntactically valid JWT: wrong number of dot-separated segments, segments not valid base64url, payload not valid JSON (causing Nimbus' ParseException as the cause), or whitespace/garbage passed in place of the token.
Common situations: Client sends 'Bearer null'/'undefined' or an empty/truncated token in the Authorization header; a Bearer token with extra characters (e.g. 'Bearer' prefix not stripped) or a token from a different format (opaque token, not JWT) is handed to the decoder; copying a token with line breaks.
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
- invalid_dpop_proof
- Invalid len
- Invalid maxolen
- invalid_key
- Unsupported alg parameter in JWS Header: ${algorithm.getName
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/e2337554d5cbe56f.
Report an issue: GitHub.