elastic/elasticsearch · error · IllegalArgumentException

{zstdLib.getErrorName(ret)}

Error message

{zstdLib.getErrorName(ret)}

What it means

Thrown as IllegalArgumentException when libzstd's ZSTD_decompress returns an error code (detected via ZSTD_isError). The message is the human-readable error name from ZSTD_getErrorName(ret). Common libzstd errors include corrupted frame magic, truncated input, destination buffer too small, or checksum mismatch. This indicates the compressed data is invalid or the output buffer is undersized.

Source

Thrown at libs/native/src/main/java/org/elasticsearch/nativeaccess/Zstd.java:77

            throw new IllegalArgumentException(zstdLib.getErrorName(ret));
        } else if (ret < 0 || ret > Integer.MAX_VALUE) {
            throw new IllegalStateException("Integer overflow? ret=" + ret);
        }
        return (int) ret;
    }

    /**
     * Decompress the content of {@code src} into {@code dst}, and return the number of decompressed bytes. {@link ByteBuffer#position()}
     * and {@link ByteBuffer#limit()} of both {@link ByteBuffer}s are left unmodified.
     */
    public int decompress(CloseableByteBuffer dst, CloseableByteBuffer src) {
        Objects.requireNonNull(dst, "Null destination buffer");
        Objects.requireNonNull(src, "Null source buffer");
        long dstSize = dst.buffer().remaining();
        long srcSize = src.buffer().remaining();
        long ret = zstdLib.decompress(MemorySegment.ofBuffer(dst.buffer()), dstSize, MemorySegment.ofBuffer(src.buffer()), srcSize);
        if (zstdLib.isError(ret)) {
            throw new IllegalArgumentException(zstdLib.getErrorName(ret));
        } else if (ret < 0 || ret > Integer.MAX_VALUE) {
            throw new IllegalStateException("Integer overflow? ret=" + ret);
        }
        return (int) ret;
    }

    /**
     * Decompress the content of {@code src} into {@code dst}, and return the number of decompressed bytes.
     * Both segments may be native or heap-backed. On JDK 22+ heap segments are passed through the
     * critical downcall without copying; on JDK 21 the fallback adapter stages them via a confined arena.
     */
    public int decompress(MemorySegment dst, MemorySegment src) {
        Objects.requireNonNull(dst, "Null dst segment");
        Objects.requireNonNull(src, "Null src segment");
        long ret = zstdLib.decompressHeap(dst, dst.byteSize(), src, src.byteSize());
        if (zstdLib.isError(ret)) {
            throw new IllegalArgumentException(zstdLib.getErrorName(ret));
        } else if (ret < 0 || ret > Integer.MAX_VALUE) {

View on GitHub (pinned to db6a809a66)

Solutions

  1. Verify the source buffer starts with the zstd magic number (0x28B52FFD) and spans a complete frame.
  2. Ensure dst.remaining() >= the original uncompressed size (use the frame's content size header or compressBound).
  3. If reading from a stream/channel, verify all compressed bytes were read before decompressing.
  4. Check for disk corruption: re-read or restore the compressed data from a healthy replica.
  5. Log the exact ZSTD_getErrorName string to distinguish corruption from buffer-size issues.

Example fix

// before: dst buffer too small
ByteBuffer dst = ByteBuffer.allocate(src.remaining()); // sized for compressed, not decompressed
zstd.decompress(wrap(dst), wrap(src));

// after: dst sized to decompressed content size
long decompressedSize = Zstd.decompressedSize(src); // read frame header
ByteBuffer dst = ByteBuffer.allocate((int) decompressedSize);
zstd.decompress(wrap(dst), wrap(src));
Defensive patterns

Strategy: validation

Validate before calling

// Verify zstd magic bytes and minimum frame size before decompressing
int magic = src.getInt(src.position()) & 0xFFFFFFFF;
if (magic != 0xFD2FB528) {
    throw new IllegalArgumentException("Source buffer does not start with zstd magic number");
}
if (dst.remaining() < minRequiredDecompressedSize) {
    throw new IllegalArgumentException("Destination buffer too small for decompressed data");
}

Try / catch

try {
    int decompressed = zstd.decompress(dst, src);
} catch (IllegalArgumentException e) {
    logger.error("Zstd decompression failed: {}", e.getMessage());
    // trigger segment recovery or fallback read
    throw new IOException("Corrupted zstd-compressed data", e);
}

Prevention

When it happens

Trigger: Calling Zstd.decompress(dst, src) where src does not contain a valid complete zstd frame, or dst.remaining() is smaller than the decompressed content size. Also triggered by partial reads where only a prefix of the compressed frame was loaded into src.

Common situations: Reading a corrupted or truncated Lucene segment compressed with zstd. Passing a ByteBuffer whose position/limit slice does not cover the full compressed frame. Mixing zstd frame versions. Disk I/O errors causing partial reads. Using a dst buffer sized for the compressed rather than decompressed size.

Related errors


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