jwtk/jjwt · error · DecodingException
Illegal ${name} character: '${c}'
Error message
Illegal ${name} character: '${c}' What it means
Base64.ctoi maps each character to its 6-bit value using the Base64 alphabet; if a character is outside the alphabet (index lookup returns -1) JJWT throws DecodingException with 'Illegal <Base64/Base64Url> character'. This guards the decode path in Base64.decode/Base64Url against malformed input. getName() reports whether the Base64 or Base64Url variant rejected the character.
Source
Thrown at api/src/main/java/io/jsonwebtoken/io/Base64.java:221
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++] = (byte) (i >> 8);
if (d < len) {
dArr[d++] = (byte) i;
}
}
}
return dArr;
}
*/
private int ctoi(char c) {
int i = c > IALPHABET_MAX_INDEX ? -1 : IALPHABET[c];
if (i < 0) {
String msg = "Illegal " + getName() + " character: '" + c + "'";
throw new DecodingException(msg);
}
return i;
}
/**
* Decodes a BASE64-encoded {@code CharSequence} that is known to be reasonably well formatted. The preconditions
* are:<br>
* + The sequence must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The sequence must not contain illegal characters within the encoded string<br>
* + The sequence CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
*
* @param seq The source sequence. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0.
* @throws DecodingException on illegal input
*/
byte[] decodeFast(CharSequence seq) throws DecodingException {
View on GitHub (pinned to fb71496164)
Solutions
- Inspect the offending character shown in the message and remove/fix it in the input (whitespace, quotes, wrapping are the usual culprits)
- Use the correct decoder for your data: Jwts.parser() for full JWTs, Base64Url for URL-safe segments, Base64 only for standard-base64 data
- Normalize input before decoding: strip whitespace/newlines and re-encode if the source used the wrong alphabet
- Parse the JWT with its dedicated API instead of manually base64-decoding segments so segment boundaries are handled for you
Example fix
// before
byte[] sig = Base64Url.decode(token.replace("\n", "").getBytes()); // still may contain '+'
// DecodingException: Illegal Base64Url character: '+'
// after
Jws<Claims> jws = Jwts.parserBuilder().build().parseClaimsJws(token.trim()); Defensive patterns
Strategy: validation
Validate before calling
// Java: strip whitespace and validate base64url alphabet before decoding
boolean isBase64Url(String s) {
return s != null && s.matches("[A-Za-z0-9_-]*");
}
String sanitize(String s) {
return s == null ? null : s.replaceAll("\\s+", "").replace("\"", "");
} Type guard
boolean isDecodableSegment(String segment) {
return segment != null && !segment.isEmpty()
&& segment.matches("[A-Za-z0-9_-]+="); // base64url chars only
} Try / catch
try {
byte[] decoded = Base64Url.decode(segment);
} catch (DecodingException e) {
// segment contained an illegal character; reject or re-encode input
} Prevention
- Strip whitespace, newlines, and quotes from any base64/base64url data before decoding
- Use Base64Url for JWT and URL contexts, Base64 only for standard-base64 data
- Parse full JWTs with Jwts.parser() rather than manually decoding segments
- Never wrap JWTs at storage/display time (disable line wrapping in emails, logs, config files)
- Check the character named in the message to diagnose alphabet mismatches quickly
When it happens
Trigger: Calling Jwts.parser() on a token whose signature or payload segment contains characters not valid for the variant being decoded — e.g. '+' or '/' in a Base64Url segment, whitespace/newlines inside a token pasted from an email or PDF, quotes or trailing period, or decoding arbitrary text with JJWT's Base64/Base64Url utility directly.
Common situations: JWT copied with line breaks or smart quotes; token signed/encoded with standard Base64 but decoded as Base64Url (or vice versa); storing tokens in systems that wrap lines; hand-rolling JWT parsing and passing the whole token (with dots) where only a segment should be decoded.
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
- Unable to decode input: ${e.getMessage()}
- Compact JWE string represents an encrypted key, but the key
- Unable to ${codecName}-decode ${name}: ${t.getMessage()}
- Unable to Base64Url-decode InputStream: ${t.getMessage()}
- Unable to convert Base64 String '${s}' to X509Certificate in
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/ce235bc9cfe3f8f8.
Report an issue: GitHub.