LZ4 Java

Maven Central

Fast LZ4 compression and xxHash hashing for Java, with JNI bindings to the reference C implementation and a pure Java fallback.

This is the community-maintained continuation of lz4/lz4-java, which is no longer maintained. The fork was originally created to fix CVE‐2025‐12183 and has since received further security fixes, an updated lz4 library, new native platforms and a modernized build. It is a drop-in replacement: the Java package names (net.jpountz.*) and API are unchanged, only the Maven coordinates are different.

Installation

Replace VERSION with the latest version shown on Maven Central or the releases page.

Maven:

<dependency>
    <groupId>at.yawk.lz4</groupId>
    <artifactId>lz4-java</artifactId>
    <version>VERSION</version>
</dependency>

Gradle:

implementation("at.yawk.lz4:lz4-java:VERSION")

If another library pulls in org.lz4:lz4-java, exclude it (or use Gradle's dependency substitution) and add this artifact instead. Both contain the same classes, so make sure only one of them ends up on the classpath.

Requirements:

Note: Before 1.8.1, there was also an lz4-pure-java artifact without the native libraries. It has been discontinued and its latest version is still vulnerable to CVE‐2025‐12183. Please open an issue if you have a use case that requires it.

Choosing an API

You want to… Use
Exchange data with other tools or languages (e.g. the lz4 CLI) LZ4FrameOutputStream / LZ4FrameInputStream (LZ4 frame format)
Compress a single byte[] or ByteBuffer and get it back LZ4CompressorWithLength / LZ4DecompressorWithLength
Full control over raw LZ4 blocks LZ4Compressor / LZ4SafeDecompressor
Hash data XXHash32, XXHash64 and their streaming variants

LZ4BlockOutputStream / LZ4BlockInputStream use a format specific to lz4-java. Prefer the frame streams for new code, unless you need compatibility with data that was already written in the block stream format.

Compressors

Both produce the same block format, and their output can be read by the same decompressor.

Decompressors

Use safeDecompressor(). It takes the compressed length as input, is safe for untrusted data, and is the fastest decompressor with the native implementation.

fastDecompressor() takes the decompressed length instead. It is kept for compatibility, but for security reasons it now always uses the (slower) safe Java implementation, regardless of which factory it came from. The nativeInsecureInstance() and unsafeInsecureInstance() factories return the old, faster fast decompressors, but must never be used with untrusted input.

Implementations

LZ4Factory and XXHashFactory provide the following implementations, which all produce compatible output:

In most cases, just use fastestInstance(), which picks the native implementation if it can be loaded and falls back to Java otherwise. Obtaining a factory is expensive, so store it in a static field and reuse it. See the LZ4Factory javadoc for caveats of the native implementation (temporary files, class loaders).

Examples

LZ4 frame streams

Files written this way can be read by the lz4 command line tool and any other LZ4 frame implementation, and vice versa.

try (OutputStream out = new LZ4FrameOutputStream(new FileOutputStream("data.lz4"))) {
    out.write(data);
}

try (InputStream in = new LZ4FrameInputStream(new FileInputStream("data.lz4"))) {
    byte[] restored = in.readAllBytes();
}

Compressing a byte array

LZ4CompressorWithLength prepends the original length to the compressed data, so you don't have to track it yourself.

LZ4Factory factory = LZ4Factory.fastestInstance();

LZ4CompressorWithLength compressor = new LZ4CompressorWithLength(factory.fastCompressor());
byte[] compressed = compressor.compress(data);

LZ4DecompressorWithLength decompressor = new LZ4DecompressorWithLength(factory.safeDecompressor());
byte[] restored = decompressor.decompress(compressed);

To protect against decompression bombs, LZ4DecompressorWithLength refuses to allocate output buffers larger than 64 MiB by default. Pass a different maximum to the constructor, or set the net.jpountz.lz4.LZ4DecompressorWithLength.maxDecompressedLength system property. For large data, use the frame streams instead.

Raw LZ4 blocks

With the low-level API, you are responsible for storing the original length alongside the compressed data.

LZ4Factory factory = LZ4Factory.fastestInstance();

// compress
LZ4Compressor compressor = factory.fastCompressor();
byte[] compressed = new byte[compressor.maxCompressedLength(data.length)];
int compressedLength = compressor.compress(data, 0, data.length, compressed, 0, compressed.length);

// decompress
LZ4SafeDecompressor decompressor = factory.safeDecompressor();
byte[] restored = new byte[data.length];
int decompressedLength = decompressor.decompress(compressed, 0, compressedLength, restored, 0);

xxHash

XXHashFactory factory = XXHashFactory.fastestInstance();
long seed = 0; // any value, but use the same one for all hashes you want to compare

// one-shot
long hash = factory.hash64().hash(data, 0, data.length, seed);

// streaming
try (StreamingXXHash64 streamingHash = factory.newStreamingHash64(seed)) {
    byte[] buf = new byte[8192];
    int read;
    while ((read = in.read(buf)) != -1) {
        streamingHash.update(buf, 0, read);
    }
    long streamedHash = streamingHash.getValue();
}

xxHash is a very fast, non-cryptographic hash function. All implementations return the same hash for the same input, on every JVM and platform.

Compatibility notes

Security

Security fixes are published as GitHub security advisories and noted in the release notes. Please report vulnerabilities privately through GitHub's vulnerability reporting.

Feedback and contributing

Documentation

Building

You need:

git submodule update --init
./mvnw verify

The released JAR contains native libraries for all supported platforms. These are built in GitHub Actions, see build-all-and-publish.yml.

License

Apache License 2.0