Error message
java.lang.IllegalArgumentException: Illegal base64 character 2d
Also printed as
- java.lang.IllegalArgumentException: Illegal base64 character a
- java.lang.IllegalArgumentException: Illegal base64 character d
- java.lang.IllegalArgumentException: Illegal base64 character 5f
- java.lang.IllegalArgumentException: Illegal base64 character 20
The hex value names the offending character: `2d` '-', `5f` '_', `a` LF, `d` CR, `20` space.
Java (JDK) — java.util.Base64.Decoder.decode
What it means
`Base64.getDecoder()` is the strict RFC 4648 decoder: it accepts only `A–Z a–z 0–9 + /` and `=` padding, and stops at the first other character. The hex number is that character's code: `2d` is `-` (the dashes of `-----BEGIN …-----`, or the base64url alphabet), `a` is a line feed, `d` a carriage return, `20` a space, `5f` an underscore. The data is usually fine — it is in a different textual wrapping than the decoder expects.
Why it happens
The PEM header and footer were not removed (2d)
commonDecoding the whole PEM file hits the first `-` of `-----BEGIN`. Only the body between the armour lines is base64.
Line breaks inside the body (a or d)
commonPEM wraps base64 at 64 characters. The basic decoder rejects the newline; files edited on Windows add `\r` as well.
base64url data given to the basic decoder (2d or 5f)
commonJWT segments and JWK coordinates use `-` and `_` instead of `+` and `/`. They need `Base64.getUrlDecoder()`.
Spaces or quotes from configuration (20, 22)
occasionalKeys pasted into YAML, properties files or environment variables can pick up spaces, quotes or escaped `\n` sequences.
How to fix it
- 1.
Strip the armour and use the MIME decoder
`Base64.getMimeDecoder()` skips line separators, which is exactly what PEM bodies need.
java String body = pem.replaceAll("-----(BEGIN|END) [A-Z ]+-----", ""); byte[] der = Base64.getMimeDecoder().decode(body); // ignores line separators and other non-alphabet characters - 2.
Use the URL decoder for JOSE data
Everything in a JWT and in a JWK is base64url; the URL decoder also accepts the missing padding.
java // JWT segments and JWK fields ("x", "y", "n", "e") use base64url without padding. byte[] header = Base64.getUrlDecoder().decode(jwt.split("\\.")[0]); byte[] x = Base64.getUrlDecoder().decode(jwk.get("x"));