jwtk/jjwt · error · IllegalArgumentException

Unable to convert Base64 String '${s}' to X509Certificate in

Error message

Unable to convert Base64 String '${s}' to X509Certificate instance. Cause: ${e.getMessage()}

What it means

JwtX509StringConverter.applyFrom decodes a Base64 string (the x5c header value) back into an X509Certificate. If Base64 decoding or certificate parsing fails for any reason, it throws IllegalArgumentException with this message wrapping the original cause. Note the input must be standard Base64, not Base64URL, per RFC 7515.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/security/JwtX509StringConverter.java:71

        } finally {
            Bytes.clear(der);
        }
    }

    // visible for testing
    protected X509Certificate toCert(final byte[] der) throws SecurityException {
        return new JcaTemplate("X.509").generateX509Certificate(der);
    }

    @Override
    public X509Certificate applyFrom(CharSequence s) {
        Assert.hasText(s, "X.509 Certificate encoded string cannot be null or empty.");
        try {
            byte[] der = Decoders.BASE64.decode(s); //RFC requires Base64, not Base64Url
            return toCert(der);
        } catch (Exception e) {
            String msg = "Unable to convert Base64 String '" + s + "' to X509Certificate instance. Cause: " + e.getMessage();
            throw new IllegalArgumentException(msg, e);
        }
    }
}

View on GitHub (pinned to fb71496164)

Solutions

  1. Verify the string is standard Base64 (alphabet A-Za-z0-9+/ with = padding), not Base64URL.
  2. Strip PEM headers/footers and whitespace before passing the value.
  3. Decode and validate the string manually with java.util.Base64.getDecoder() and CertificateFactory to see the underlying cause.
  4. Use JwtX509StringConverter (or jjwt) to produce the string in the first place instead of hand-rolling encoding.

Example fix

// before
String x5c = Base64.getUrlEncoder().encodeToString(cert.getEncoded()); // wrong alphabet

// after
String x5c = Base64.getEncoder().encodeToString(cert.getEncoded()); // standard Base64 per RFC
Defensive patterns

Strategy: validation

Validate before calling

boolean isStandardBase64(String s) {
    return s != null && s.matches("[A-Za-z0-9+/]+={0,2}");
}

Try / catch

try {
    X509Certificate cert = converter.applyFrom(x5cValue);
} catch (IllegalArgumentException e) {
    logger.error("Bad x5c value '{}': {}", x5cValue, e.getCause());
    // fix encoding (URL-safe -> standard) or re-encode from the cert
}

Prevention

When it happens

Trigger: Reading an x5c header value that is invalid Base64, Base64URL-encoded instead of Base64 (contains '-'/'_'), has whitespace/newlines, does not include the leading certificate bytes, or is otherwise not parseable as X.509 DER.

Common situations: Manually building an x5c header from a PEM file while forgetting to strip the BEGIN/END lines; using a Base64URL encoder by mistake; header values altered by URL-encoding or line-wrapping; certificates generated by tooling with wrong formats.

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


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