jwtk/jjwt · error · io.jsonwebtoken.MalformedJwtException

Unable to read compact JWT: ${e.getMessage()}

Error message

Unable to read compact JWT: ${e.getMessage()}

What it means

JwtTokenizer.read wraps any IOException from reading the compact JWT's character stream in a MalformedJwtException with this message. The library treats an I/O failure while reading the token as a malformed-token condition, chaining the original IOException as the cause.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/JwtTokenizer.java:38

import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;

import java.io.IOException;
import java.io.Reader;

public class JwtTokenizer {

    static final char DELIMITER = '.';

    private static final String DELIM_ERR_MSG_PREFIX = "Invalid compact JWT string: Compact JWSs must contain " +
            "exactly 2 period characters, and compact JWEs must contain exactly 4.  Found: ";

    private static int read(Reader r, char[] buf) {
        try {
            return r.read(buf);
        } catch (IOException e) {
            String msg = "Unable to read compact JWT: " + e.getMessage();
            throw new MalformedJwtException(msg, e);
        }
    }

    @SuppressWarnings("unchecked")
    public <T extends TokenizedJwt> T tokenize(Reader reader) {

        Assert.notNull(reader, "Reader argument cannot be null.");

        CharSequence protectedHeader = Strings.EMPTY; //Both JWS and JWE
        CharSequence body = Strings.EMPTY; //JWS payload or JWE Ciphertext
        CharSequence encryptedKey = Strings.EMPTY; //JWE only
        CharSequence iv = Strings.EMPTY; //JWE only
        CharSequence digest = Strings.EMPTY; //JWS Signature or JWE AAD Tag

        int delimiterCount = 0;
        char[] buf = new char[4096];
        int len = 0;
        StringBuilder sb = new StringBuilder(4096);

View on GitHub (pinned to fb71496164)

Solutions

  1. Pass the compact JWT as a String instead of a Reader to avoid streaming I/O
  2. Check the cause (IOException) to fix the underlying source: reopen the stream, check connectivity, or verify file access
  3. Catch MalformedJwtException (or its IOException cause) and retry once if the source is transient

Example fix

// before
parser.parseClaimsJws(new InputStreamReader(socket.getInputStream()));
// after
String token = readFully(socket.getInputStream()); // no IOException mid-parse
parser.parseClaimsJws(token);
Defensive patterns

Strategy: try-catch

Try / catch

try {
    Jws<Claims> jws = parser.parseClaimsJws(token);
} catch (MalformedJwtException e) {
    Throwable cause = e.getCause();
    if (cause instanceof IOException) retryOrReopenStream();
}

Prevention

When it happens

Trigger: Calling Jwts.parser().parse(...) or parseClaimsJws(...) with a Reader whose underlying source (socket, file, stream) throws IOException mid-read.

Common situations: Reading tokens from a network stream that was closed or reset; parsing from a file/stream already consumed or with encoding issues; passing an Reader backed by a failing input source instead of the token String.

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/744d3ef70844116a. Report an issue: GitHub.