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.
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:
- Java 7 or newer. The JAR has the
Automatic-Module-Nameorg.lz4.javaand OSGi metadata. - The JAR bundles native libraries for Linux (amd64, i386, aarch64, ppc64le, s390x, riscv64), macOS (x86_64, aarch64) and Windows (amd64). On other platforms, the pure Java implementation is used automatically.
- On Java 24 and newer, the JVM prints a warning when the native library is loaded. Pass
--enable-native-access=ALL-UNNAMED(or the name of your module) to silence it.
Note: Before 1.8.1, there was also an
lz4-pure-javaartifact 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.
| 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.
- Fast compressor (
fastCompressor()): low memory footprint (~16 KB), very fast, reasonable compression ratio. - High compressor (
highCompressor()): LZ4 HC, higher memory footprint (~256 KB) and roughly 10× slower than the fast compressor, but a better compression ratio. The compression level can be tuned; higher levels cost significantly more CPU.
Both produce the same block format, and their output can be read by the same decompressor.
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.
LZ4Factory and XXHashFactory provide the following implementations, which all produce compatible output:
nativeInstance(): JNI bindings to the reference C implementation.safeInstance(): A pure Java port.unsafeInstance(): A Java port usingsun.misc.Unsafe. For LZ4, this is currently an alias for the safe instance out of caution.
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).
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();
}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.
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);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.
- Implementations are interchangeable: data compressed with the native implementation can be decompressed with the Java one, and vice versa.
- Compressed output is not guaranteed to be byte-for-byte identical across implementations, versions or platforms, but it can always be decompressed by any of them.
Security fixes are published as GitHub security advisories and noted in the release notes. Please report vulnerabilities privately through GitHub's vulnerability reporting.
- Report bugs and request features through GitHub issues.
- Ask questions in GitHub Discussions.
- Pull requests are welcome. See CONTRIBUTING.md for the requirements for contributions.
- Javadoc: lz4, xxhash
- Release notes (1.8.1 and later)
- Changelog (1.8.0 and earlier)
- Benchmarks of the original project (1.8.0): compression, decompression, xxhash
You need:
- A JDK 25 to run Maven.
- A JDK 7 registered in your Maven toolchains.xml,
used to compile the library. Alternatively, pass
-Dmaven.compiler.source=8 -Dmaven.compiler.target=8to build with the default JDK. - A C compiler, to build the native library for your platform.
git submodule update --init
./mvnw verifyThe released JAR contains native libraries for all supported platforms. These are built in GitHub Actions, see
build-all-and-publish.yml.