elastic/elasticsearch · error · LZ4Exception

Malformed input at {}

Error message

Malformed input at {}

What it means

ESLZ4Decompressor's fast decompress path handles destLen==0 specially: it expects at least one source byte and that byte to be 0x00 (the LZ4 EOF marker for empty output). If srcLen is less than 1 or the first source byte is non-zero, the stream is considered malformed. This guards the zero-length output edge case before the main decode loop.

Source

Thrown at libs/lz4/src/main/java/org/elasticsearch/lz4/ESLZ4Decompressor.java:63

    private ESLZ4Decompressor() {}

    @Override
    public int decompress(byte[] src, final int srcOff, byte[] dest, final int destOff, int destLen) {

        final int srcEnd = src.length;

        return decompress(src, srcOff, srcEnd - srcOff, dest, destOff, destLen);
    }

    private int decompress(byte[] src, final int srcOff, final int srcLen, byte[] dest, final int destOff, int destLen) {
        SafeUtils.checkRange(src, srcOff, srcLen);
        SafeUtils.checkRange(dest, destOff, destLen);

        if (destLen == 0) {
            // Allow `srcLen > 1` despite just one byte being consumed since this 'fast' decompressor does not have to fully consume the src
            if (srcLen < 1 || SafeUtils.readByte(src, srcOff) != 0) {
                throw new LZ4Exception("Malformed input at " + srcOff);
            }
            return 1;
        }

        final int srcEnd = srcOff + srcLen;
        final int destEnd = destOff + destLen;

        int sOff = srcOff;
        int dOff = destOff;

        while (true) {
            if (sOff >= srcEnd) {
                throw new LZ4Exception("Malformed input at " + sOff);
            }
            final int token = SafeUtils.readByte(src, sOff) & 0xFF;
            ++sOff;

            // literals

View on GitHub (pinned to db6a809a66)

Solutions

  1. Verify the source was produced by the matching LZ4 fast compressor (LZ4FastDecompressor's counterpart).
  2. Ensure the compressed payload is not truncated; re-read or re-fetch the original bytes.
  3. Check that destLen matches the originally compressed length; a 0 destLen with non-empty source indicates a length bookkeeping bug upstream.
Defensive patterns

Strategy: try-catch

Validate before calling

// Before decompressing, sanity-check the destLen==0 case
if (destLen == 0 && (srcLen < 1 || src[srcOff] != 0)) {
    throw new IllegalArgumentException("Invalid LZ4 empty-output block");
}

Try / catch

try {
    int read = ESLZ4Decompressor.INSTANCE.decompress(src, srcOff, dest, destOff, destLen);
} catch (LZ4Exception e) {
    // input is corrupt/truncated; recover from replica or snapshot
    throw new IllegalStateException("Corrupt LZ4 block at offset " + srcOff, e);
}

Prevention

When it happens

Trigger: Calling ESLZ4Decompressor.decompress (or INSTANCE.decompress) with destLen=0 but the source buffer is empty or its first byte is not the 0x00 end-of-block sentinel. Typically the result of truncating or corrupting an LZ4 frame.

Common situations: Truncated LZ4-compressed stored fields or translog entries in Elasticsearch. Passing a buffer produced by a different LZ4 variant/format. Off-by-one destLen miscalculation that yields 0 when the source is actually a real block.

Understand the failure class

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/7acc1c4ba073131e. Report an issue: GitHub.