jwtk/jjwt · error · io.jsonwebtoken.io.DecodingException
Unable to Base64Url-decode InputStream: ${t.getMessage()}
Error message
Unable to Base64Url-decode InputStream: ${t.getMessage()} What it means
Thrown as a DecodingException when the bytes of an InputStream cannot be Base64Url-decoded. The method reads the stream, converts to a UTF-8 string, and delegates to a base64url codec; any failure (invalid characters, IO error reading the stream) is wrapped with this message.
Source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/io/DelegateStringDecoder.java:43
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated //TODO: delete when deleting JwtParserBuilder#base64UrlDecodeWith
public class DelegateStringDecoder implements Decoder<InputStream, InputStream> {
private final Decoder<CharSequence, byte[]> delegate;
public DelegateStringDecoder(Decoder<CharSequence, byte[]> delegate) {
this.delegate = Assert.notNull(delegate, "delegate cannot be null.");
}
@Override
public InputStream decode(InputStream in) throws DecodingException {
try {
byte[] data = Streams.bytes(in, "Unable to Base64URL-decode input.");
data = delegate.decode(Strings.utf8(data));
return Streams.of(data);
} catch (Throwable t) {
String msg = "Unable to Base64Url-decode InputStream: " + t.getMessage();
throw new DecodingException(msg, t);
}
}
}
View on GitHub (pinned to fb71496164)
Solutions
- Check the cause of the exception; invalid base64url characters are the most common root cause.
- Normalize the input to base64url (replace '+' with '-', '/' with '_', strip '=' padding) before decoding.
- Confirm you are passing the encoded JWT segment, not the decoded JSON payload.
- Verify the InputStream is open and fully readable.
Example fix
// before
byte[] out = decoder.decode(new ByteArrayInputStream(standardBase64Payload));
// after
String b64url = standardBase64Payload.replace('+', '-').replace('/', '_').replaceAll("=+$", "");
byte[] out = decoder.decode(new ByteArrayInputStream(b64url.getBytes(StandardCharsets.UTF_8))); Defensive patterns
Strategy: validation
Validate before calling
// Java
if (input == null || !input.available() > 0) throw new IllegalArgumentException("empty input");
String s = new String(bytes, StandardCharsets.UTF_8);
if (!s.matches("[A-Za-z0-9_-]*")) throw new IllegalArgumentException("not base64url"); Type guard
static boolean isDecodableBase64Url(byte[] bytes) {
if (bytes == null || bytes.length == 0) return false;
return new String(bytes, StandardCharsets.UTF_8).matches("[A-Za-z0-9_-]*");
} Try / catch
try {
InputStream decoded = delegateDecoder.decode(in);
} catch (DecodingException e) {
throw new IllegalArgumentException("Input is not valid base64url: " + e.getMessage(), e);
} Prevention
- Validate the character set of base64url inputs before streaming them to the decoder.
- Ensure standard-Base64 inputs from external systems are converted to base64url first.
- Confirm streams are freshly created and not already consumed.
- Preserve the original cause when logging decode failures.
When it happens
Trigger: Calling decode(InputStream) on DelegateStringDecoder (used when serializing/parsing JWT components from streams) with content that is not valid base64url, or when Streams.bytes fails reading the input.
Common situations: Passing a JWT payload that includes standard Base64 characters ('+', '/', '='); passing plain JSON instead of the encoded segment; a closed or broken InputStream underneath.
Related errors
- Unable to ${codecName}-decode ${name}: ${t.getMessage()}
- Unable to obtain JSON String from Reader:
- Unable to ${codecName}-encode ${name}: ${t.getMessage()}
- IO Exception: ${t.getMessage()}
- IO Exception ${t.getMessage()}
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/3256c6fde719da6b.
Report an issue: GitHub.