jwtk/jjwt · error · io.jsonwebtoken.MalformedJwtException
Invalid compact JWT string: Compact JWSs must contain…
Error message
Invalid compact JWT string: Compact JWSs must contain exactly 2 period characters, and compact JWEs must contain exactly 4. Found: ${delimiterCount} What it means
A compact JWS must contain exactly 2 period delimiters and a compact JWE exactly 4. After tokenizing, JwtTokenizer counts the delimiters and throws MalformedJwtException if the count is neither 2 nor 4, meaning the string is not a structurally valid compact JWT at all.
Solutions
- Verify the token source: count '.' occurrences (2 for JWS, 4 for JWE) before parsing
- Log/inspect the raw incoming token to find where it gets truncated or mangled
- Distinguish token types in the client and route JWEs to parseEncryptedJws and JWSs to parseClaimsJws
- Catch MalformedJwtException and reject with 'malformed token' instead of surfacing a 500
Example fix
// before
String token = req.getHeader("Authorization").substring(7);
parser.parseClaimsJws(token); // token truncated
// after
String token = req.getHeader("Authorization").substring(7);
long dots = token.chars().filter(c -> c == '.').count();
if (dots != 2 && dots != 4) throw new MalformedJwtException("bad token shape");
parser.parseClaimsJws(token); Defensive patterns
Strategy: validation
Validate before calling
long dots = token == null ? -1 : token.chars().filter(c -> c == '.').count();
if (dots != 2 && dots != 4) throw new MalformedJwtException("Not a compact JWT: " + dots + " dots"); Try / catch
try {
Jws<Claims> jws = parser.parseClaimsJws(token);
} catch (MalformedJwtException e) {
respond(400, "Token is not a valid compact JWT");
} Prevention
- Validate token shape (2 or 4 periods) at the API boundary before parsing
- Avoid fixed-length storage columns that can truncate tokens
- Check for URL-encoding mangling ('%3D', '+'/space swaps) in transit
- Ensure clients send JWTs, not opaque session ids, to JWT parsers
When it happens
Trigger: Passing a truncated, concatenated, corrupted, or completely non-JWT string to parse/parseClaimsJws/parseEncryptedJws — e.g. an empty fragment, a token with a period added/removed, or a base64 blob that isn't a JWT.
Common situations: String truncation from fixed-size storage columns or log capture; sending the wrong token type (opaque session id) to a JWT parser; tokens mangled by URL encoding/decoding; clients sending 'null' or 'undefined' literal strings.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Compact JWE strings MUST always contain a payload…
- Compact JWT strings may not contain whitespace.
- Compact JWT strings MUST always have a Base64Url protected…
- Invalid Base64Url
- JWEs do not support key management alg header value 'none'…
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/5089d6962d150775.
Report an issue: GitHub.
Appendix: source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/JwtTokenizer.java:102
body = Strings.EMPTY; //clear out value set for JWS
iv = token;
break;
case 3:
body = token;
break;
}
delimiterCount++;
sb.setLength(0);
} else {
sb.append(c);
}
}
}
if (delimiterCount != 2 && delimiterCount != 4) {
String msg = DELIM_ERR_MSG_PREFIX + delimiterCount;
throw new MalformedJwtException(msg);
}
if (sb.length() > 0) {
digest = sb.toString();
}
if (delimiterCount == 2) {
return (T) new DefaultTokenizedJwt(protectedHeader, body, digest);
}
return (T) new DefaultTokenizedJwe(protectedHeader, body, digest, encryptedKey, iv);
}
}
View on GitHub (pinned to fb71496164)