jwtk/jjwt · error · io.jsonwebtoken.io.DeserializationException

Deserialized data resulted in a null value; cannot create…

Error message

Deserialized data resulted in a null value; cannot create Map<String,?>

What it means

Thrown as a DeserializationException when the JSON deserializer produces null from the input, so a Map<String,?> cannot be constructed. JsonObjectDeserializer expects deserializable JSON object content; a null result means the input was empty, literal 'null', or otherwise deserialized to nothing.

Solutions

  1. Verify the input stream actually contains JSON object bytes before deserializing.
  2. Check the stream position — ensure it was not already read/consumed elsewhere.
  3. Ensure the producer serialized actual JSON object content, not null.
  4. Add a caller-side check: if the decoded byte array is empty, fail before calling the deserializer.

Example fix

// before
Map<String,?> claims = deserializer.deserialize(new ByteArrayInputStream(payload));
// after
if (payload == null || payload.length == 0) {
    throw new IllegalArgumentException("payload is empty");
}
Map<String,?> claims = deserializer.deserialize(new ByteArrayInputStream(payload));
Defensive patterns

Strategy: validation

Validate before calling

// Java
if (payload == null || payload.length == 0) {
    throw new IllegalArgumentException("payload must be non-empty JSON object bytes");
}
String s = new String(payload, StandardCharsets.UTF_8).trim();
if (s.isEmpty() || s.equals("null")) throw new IllegalArgumentException("payload deserializes to null");

Type guard

static boolean isNonEmptyJson(byte[] bytes) {
    if (bytes == null || bytes.length == 0) return false;
    String s = new String(bytes, StandardCharsets.UTF_8).trim();
    return !s.isEmpty() && !s.equalsIgnoreCase("null");
}

Try / catch

try {
    Map<String,?> claims = deserializer.apply(in);
} catch (DeserializationException e) {
    throw new IllegalArgumentException("Claims payload is empty or null: " + e.getMessage());
}

Prevention

When it happens

Trigger: Calling deserialize with an empty InputStream, an InputStream containing only whitespace or the JSON literal 'null', or a deserializer configured to return null for unrecognized input.

Common situations: Passing an empty JWT payload segment (decoded to zero bytes); reading from a stream that was already consumed; serializing a Java null claims map earlier and then trying to deserialize it back.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java:55

            "investigate the source further. Cause: %s";

    private final Deserializer<?> deserializer;
    private final String name;

    public JsonObjectDeserializer(Deserializer<?> deserializer, String name) {
        this.deserializer = Assert.notNull(deserializer, "JSON Deserializer cannot be null.");
        this.name = Assert.hasText(name, "name cannot be null or empty.");
    }

    @Override
    public Map<String, ?> apply(Reader in) {
        Assert.notNull(in, "InputStream argument cannot be null.");
        Object value;
        try {
            value = this.deserializer.deserialize(in);
            if (value == null) {
                String msg = "Deserialized data resulted in a null value; cannot create Map<String,?>";
                throw new DeserializationException(msg);
            }
            if (!(value instanceof Map)) {
                String msg = "Deserialized data is not a JSON Object; cannot create Map<String,?>";
                throw new DeserializationException(msg);
            }
            // JSON Specification requires all JSON Objects to have string-only keys.  So instead of
            // checking that the val.keySet() has all Strings, we blindly cast to a Map<String,?>
            // since input would rarely, if ever, have non-string keys.
            //noinspection unchecked
            return (Map<String, ?>) value;
        } catch (StackOverflowError e) {
            String msg = String.format(MALFORMED_COMPLEX_ERROR, this.name, this.name, e.getMessage());
            throw new DeserializationException(msg, e);
        } catch (Throwable t) {
            throw malformed(t);
        }
    }

View on GitHub (pinned to fb71496164)