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

  1. Check the cause of the exception; invalid base64url characters are the most common root cause.
  2. Normalize the input to base64url (replace '+' with '-', '/' with '_', strip '=' padding) before decoding.
  3. Confirm you are passing the encoded JWT segment, not the decoded JSON payload.
  4. 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

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


AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09). Data as JSON: /api/errors/3256c6fde719da6b. Report an issue: GitHub.