jwtk/jjwt · error · DecodingException

Unable to decode input: ${e.getMessage()}

Error message

Unable to decode input: ${e.getMessage()}

What it means

ExceptionPropagatingDecoder is JJWT's wrapper that adapts any Decoder implementation into JJWT's Decoder contract. Its decode method rethrows DecodingException as-is, but if the delegate decoder throws any other checked/unchecked Exception (e.g. IOException from a custom decoder), it is wrapped in a DecodingException with 'Unable to decode input: ...'. This normalizes decoder failures for callers while keeping the original cause.

Source

Thrown at api/src/main/java/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java:57

    /**
     * Decode the specified encoded data, delegating to the wrapped Decoder, wrapping any
     * non-{@link DecodingException} as a {@code DecodingException}.
     *
     * @param t the encoded data
     * @return the decoded data
     * @throws DecodingException if there is an unexpected problem during decoding.
     */
    @Override
    public R decode(T t) throws DecodingException {
        Assert.notNull(t, "Decode argument cannot be null.");
        try {
            return decoder.decode(t);
        } catch (DecodingException e) {
            throw e; //propagate
        } catch (Exception e) {
            String msg = "Unable to decode input: " + e.getMessage();
            throw new DecodingException(msg, e);
        }
    }
}

View on GitHub (pinned to fb71496164)

Solutions

  1. Check getCause() to find the real failure from the delegate decoder and fix that root cause
  2. Verify the input type matches what the decoder expects (String vs byte[]) and that the data is valid for the decoder's format
  3. If you wrote a custom Decoder, catch your internal failures and throw DecodingException from decode() so they propagate unchanged
  4. Ensure consistent JJWT api/impl/runtime versions on the classpath to avoid mismatched decoder behavior

Example fix

// before
public byte[] doDecode(String s) throws IOException {
    return Files.readAllBytes(Paths.get(s)); // raw IOException -> Unable to decode input
}

// after
public byte[] doDecode(String s) {
    try {
        return Files.readAllBytes(Paths.get(s));
    } catch (IOException e) {
        throw new DecodingException("Unable to read input", e);
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Java: check input type and nullness before handing to a decoder
boolean isDecodableInput(Object in) {
    if (in instanceof String s) return !s.isEmpty();
    if (in instanceof byte[] b) return b.length > 0;
    return false;
}

Type guard

boolean isStringOrBytes(Object in) {
    return in instanceof String || in instanceof byte[];
}

Try / catch

try {
    byte[] out = decoder.decode(input);
} catch (DecodingException e) {
    Throwable root = e.getCause();
    // null cause = delegate threw DecodingException; non-null = wrapped unexpected failure
}

Prevention

When it happens

Trigger: Using a custom Decoder (e.g. via io.jsonwebtoken.io.Decoders or supplied to an Encoder/Decoder-based component) whose decode() throws a non-DecodingException — a raw IOException, RuntimeException, or third-party library exception — during any JJWT operation that decodes input (token parsing, Base64 decoding of keys, etc.).

Common situations: A custom Decoder reading from a stream/file that fails with IOException; a delegate decoder throwing NullPointerException on malformed input instead of DecodingException; mixing JJWT versions where a decoder returns unexpected types; byte[] vs String misuse with a decoder expecting a different input type.

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/25db8859e896d63a. Report an issue: GitHub.