apache/incubator-seata · error · IllegalArgumentException

Failed to decompress zstd data

Error message

Failed to decompress zstd data

What it means

Wrapped IOException from ZstdUtil.decompress thrown as IllegalArgumentException('Failed to decompress zstd data', e). ZstdInputStream fails when the payload lacks the zstd frame magic (0x28 0xB5 0x2F 0xFD), is truncated, or fails checksum validation. Note this class throws IllegalArgumentException, not RuntimeException, unlike the gzip/zip utils.

Source

Thrown at compressor/seata-compressor-zstd/src/main/java/org/apache/seata/compressor/zstd/ZstdUtil.java:54

        return Zstd.compress(bytes);
    }

    public static byte[] decompress(byte[] bytes) {
        if (bytes == null) {
            throw new NullPointerException("bytes is null");
        }
        try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
                ZstdInputStream zis = new ZstdInputStream(bais);
                ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[8192];
            int len;
            while ((len = zis.read(buffer)) > 0) {
                baos.write(buffer, 0, len);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to decompress zstd data", e);
        }
    }
}

View on GitHub (pinned to e01f97c6db)

Solutions

  1. Inspect the cause for zstd frame errors ('Unknown frame descriptor' = wrong format, short reads = truncation).
  2. Align compressor type on all seata participants.
  3. Regenerate corrupted persisted data; do not retry the same bytes.
  4. Preflight check for the zstd magic bytes before decompressing untrusted payloads.

Example fix

// before
byte[] out = ZstdUtil.decompress(bytes);

// after
if (bytes.length < 4 || (bytes[0] & 0xff) != 0x28 || (bytes[1] & 0xff) != 0xB5) {
    throw new IllegalArgumentException("payload is not zstd framed");
}
byte[] out = ZstdUtil.decompress(bytes);
Defensive patterns

Strategy: try-catch

Validate before calling

boolean isZstd(byte[] b) {
    return b != null && b.length >= 4
        && (b[0] & 0xff) == 0x28 && (b[1] & 0xff) == 0xB5
        && (b[2] & 0xff) == 0x2F && (b[3] & 0xff) == 0xFD;
}

Try / catch

try {
    byte[] out = ZstdUtil.decompress(bytes);
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("Failed to decompress zstd data")) {
        // wrong codec or truncated zstd frame; inspect e.getCause(); reject, don't retry
    } else { throw e; }
}

Prevention

When it happens

Trigger: Feeding non-zstd bytes (gzip/zip/lz4 output) to ZstdUtil.decompress; truncated arrays; corrupted stored payloads; passing an empty non-null array which makes ZstdInputStream throw on the first read.

Common situations: Compressor type mismatch between seata client and server; damaged binary columns in undo/branch tables; proxies altering bodies; upgrading seata where the default compressor changed to zstd while one side still sends uncompressed or differently-compressed data.

Related errors


AI-assisted analysis of apache/incubator-seata@e01f97c6db (2026-08-14). Data as JSON: /api/errors/5ca8bad90b1c6ebb. Report an issue: GitHub.