# Zero-Allocation Hashing puts non-allocating hash functions behind one API

> OpenHFT's Zero-Allocation Hashing wraps CityHash, FarmHash, MurmurHash3, xxHash, wyHash and MetroHash behind a single Java interface that allocates nothing during hashing and keeps no per-thread state. It suits engineers hashing flat byte sequences inside latency sensitive paths, and it stops short of objects whose fields are scattered across the heap.

**OpenHFT/Zero-Allocation-Hashing** — Zero-allocation hashing for Java

- Repository: https://github.com/OpenHFT/Zero-Allocation-Hashing
- Stars: 848 · Forks: 140
- Language: Java
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/openhft-zero-allocation-hashing

## One interface over seven hash implementations, with no ThreadLocal

The project exists to hash bytes without disturbing the garbage collector. The README states the difference plainly: unlike Guava hashing, this library performs no object allocation during the hash computation and does not use `ThreadLocal`. That last point is the subtle one. ThreadLocal is the standard trick for keeping reusable buffers out of the way, but it pins state per thread, which costs memory, adds lookup overhead on every call, and complicates classloader unloading in application servers.

Zero-Allocation Hashing removes both problems by not needing scratch state at all, and it exposes the result through a small set of functional interfaces. A `long` valued hash function interface covers 64-bit hashes, and a `long[]` valued interface covers hashes wider than 64 bits, where the digest has to be spread across an array.

The scope is deliberately narrow. The API hashes any sequence of bytes in Java, including all kinds of primitive arrays, buffers, `CharSequence` and more. The audience is therefore narrow too: engineers working on order books, wire protocols, in-memory indexes and other places where a hash call happens per message and a young generation collection lands in the middle of it.

## The algorithm catalogue and its two interface shapes

Seven implementations ship behind the common interface, and the list in the README is explicit about versions where versions matter. CityHash is at version 1.1, with a note that 1.1.1 is a C++ language specific maintenance release. FarmHash contributes two algorithms, `farmhashna` introduced in FarmHash 1.0 and `farmhashuo` introduced in FarmHash 1.1. MetroHash is included using the metrohash64_2 initialisation vector, and that choice of initialisation vector is exactly the kind of detail that silently changes your output if you switch to another implementation elsewhere.

MurmurHash3 is offered in 128-bit and low 64-bit forms, wyHash is at version 3, and xxHash appears twice, as the original xxHash and as the newer xxh3 and xxh128 in 128-bit and 64-bit variants.

The split between the `long` interface and the `long[]` interface is the practical consequence of the wider digests. A 64-bit result is a primitive that can be returned and compared without touching the heap, while a 128-bit result is an array, so the library offers a destination form for that case. If your use case is content addressing across a network, a 64-bit hash is usually enough; if you need collision resistance at a higher width, you are in the `long[]` path and the allocation story gets more complicated than the name of the project suggests.

## Native access where it helps, byte order independence everywhere

The implementation uses native access where possible, but the README states that it is also platform-endianness-agnostic. Those two goals are in tension and the project resolves the tension explicitly: the result is consistent whatever the byte order, at the cost of what it calls only moderately affecting performance.

For a team running mixed hardware, that is the right trade. A hash computed on a big-endian machine and a hash computed on a little-endian machine have to agree, otherwise a partition key computed on one node will not find the row it belongs to on another. Many faster hash libraries assume a fixed byte order and leave that as a deployment constraint. Here it is a property of the library rather than a rule the user has to remember.

The cost is that the library cannot simply load words in machine order. The README lists internal documentation under `src/main/docs`, including `architecture-overview.adoc`, `invariants-and-contracts.adoc`, `algorithm-profiles.adoc`, `unsafe-and-platform-notes.adoc` and `testing-strategy.adoc`. The `unsafe-and-platform-notes.adoc` file is where the specifics of the native access and the platform behaviour are kept, and the `algorithm-profiles.adoc` file is where the per-algorithm characteristics are described. The repository also carries an `AGENTS.md` and a `CLAUDE.md` file, and a Maven build with `pom.xml` and `mvnw`.

## Adding the dependency and computing a first hash

The README gives both a Gradle and a Maven form, and the version it pins is 0.27ea1. In a Gradle build the dependency block is:

```groovy
dependencies {
    implementation 'net.openhft:zero-allocation-hashing:0.27ea1'
}
```

The equivalent Maven coordinates are:

```xml
<dependency>
  <groupId>net.openhft</groupId>
  <artifactId>zero-allocation-hashing</artifactId>
  <version>0.27ea1</version>
</dependency>
```

Both are published to Maven Central under the group `net.openhft` and the artifact name `zero-allocation-hashing`, and the README links the Javadoc for the latest version on javadoc.io. The library targets Java 8 and above.

The first call is a single line, hashing the characters of a string with the wyHash version 3 implementation:

```java
long hash = LongHashFunction.wy_3().hashChars("hello");
```

`LongHashFunction.wy_3()` selects the implementation and `hashChars` consumes a `CharSequence` without converting it to a byte array first. The result is a primitive long, so nothing is allocated to hold it. Once that works, the sensible next step is to write the same value through two different algorithms and confirm they differ, then to check that a value computed on one machine matches a value computed on another, which is the property the endianness agnostic design exists to guarantee.

## The speed table is dated and leaves two rows unanswered

The README publishes a throughput table, and it deserves careful reading rather than quoting. The measurements were taken on an Intel Core i7-4870HQ CPU at 2.50GHz. xxHash leads at 9.5 GB/s with a bootstrap cost of 6 ns, FarmHash `na` follows at 9.0 GB/s, FarmHash `uo` at 7.2 GB/s, CityHash at 7.0 GB/s, and MurmurHash at 5.3 GB/s with a 12 ns bootstrap. The bootstrap column is the interesting one for a trading or network path, because it measures the fixed setup cost of a call, which is what a small message actually pays.

The MetroHash and WyHash rows are not filled in. Both cells point to issue 28 in the repository, so the numbers exist somewhere but the table does not carry them. A table with two unresolved cells is a small but real signal about how the performance section is maintained.

The hardware matters as much as the missing rows. A 2.50GHz mobile-class Core i7 from an earlier generation is not a machine most readers run today, and the README gives no date for the run. Treat the numbers as the project's own record of a past measurement rather than a prediction for your hardware. The `performance-benchmarks.adoc` document in `src/main/docs` is the place the project keeps its benchmark detail, and `testing-strategy.adoc` covers how the algorithms are validated, with the README stating that the implementations are tested against LTS JDKs 8, 11, 17 and 21, plus the latest non-LTS JDKs, on both little-endian and big-endian platforms.

## Where the README says this is the wrong tool

The project documents its own limits, which is refreshing and useful. Three cases are named explicitly.

The first is objects whose data is scattered in memory between managed objects. The README gives this shape as an example:

```java
    class Person {
        String givenName, surName;
        int salary;
    }
```

There is no simple way to hash such a class with this library. The fields live in separate objects, so the library would need a serialisation step, and a serialisation step is exactly the allocation the project is built to avoid. If your key is a POJO, the work of flattening it into bytes has to happen somewhere, and doing it here means doing it without this library's help.

The second case is byte sequences of unknown length, with `Iterator<Byte>` given as the simplest example. A hash function needs to know where the input ends, and an iterator of boxed bytes does not tell it in advance.

The third case is a stream that must be transformed on the way in, for example encoding or decoding with a specific coding, where the hash should cover the transformed bytes without first dumping them to memory. The library hashes byte sequences that already exist. Streaming transform and hash is a different problem, and one this project does not attempt.

## Version numbering, the ea branch and upgrade cost

The repository is not archived and the last push was on 2026-10-09, with the default branch named `ea`. The release list is where the versioning story gets confusing. The releases are `zero-allocation-hashing-2026.0` on 2026-01-28, `zero-allocation-hashing-0.26.3` on 2025-07-22, and `zero-allocation-hashing-0.27ea1` on 2025-07-09. The 0.26.3 release came after the 0.27ea1 release, and the 2026.0 version uses a calendar year scheme rather than the numeric sequence. Meanwhile the quick start in the README still pins 0.27ea1, an early access build, even though a 2026.0 release exists.

That is a concrete upgrade cost, and it is small: the published coordinate in the README is an ea version, so a team copying the quick start verbatim ships a pre-release rather than the latest release. The project publishes release notes through a Chronicle Software subscription link, and the internal `change-log-template.adoc` shows that changes are tracked in a changelog. Whether 0.27ea1 and 2026.0 differ in algorithm behaviour is not something the README settles, so it is worth diffing the two before you settle on a version.

The licence is Apache 2.0, which is permissive, allows commercial use and redistribution, and carries the notice and patent grant terms. It does not come with a warranty, and the README does not state a support policy or a compatibility guarantee for algorithm output. If any of your systems persist hashes computed by this library, treat the algorithm choice and version as part of your on-disk format, because switching implementations changes the values without changing the API.

## Conclusion

Adopt Zero-Allocation Hashing when the bytes you hash live in one contiguous block, an array, a buffer or a CharSequence, and when the hash sits on a path where allocation and garbage collection pauses are unacceptable. Do not adopt it for scattered POJOs, for sequences whose length is not known upfront, or for streams that must be transformed on the way in. Verify first that the pinned version you choose matches the artifact you actually publish, since the README still points at 0.27ea1 while a 2026.0 release exists, then confirm the endianness independent result for your own platform and key.

## FAQ

### How do I add Zero-Allocation Hashing to a Maven or Gradle project?

The README shows a Gradle dependency block and a Maven dependency block, both using the group net.openhft and the artifact zero-allocation-hashing, with the version 0.27ea1 in the examples. The library is published to Maven Central and targets Java 8 and above.

### Which hash algorithms does Zero-Allocation Hashing support?

The implementations listed are CityHash 1.1, FarmHash na and uo, MetroHash with the metrohash64_2 initialisation vector, MurmurHash3 in 128-bit and low 64-bit forms, wyHash version 3, and xxHash including xxh3 and xxh128. A long valued interface covers 64-bit hashes and a long[] valued interface covers wider ones.

### Does Zero-Allocation Hashing use ThreadLocal or allocate during hashing?

No. The README states that unlike Guava hashing there is no object allocation during the hash computation and that ThreadLocal is not used, which is what allows the library to keep the same behaviour under concurrency without per-thread scratch state.

### Can I hash a POJO such as a Person object with Zero-Allocation Hashing?

Not directly. The README lists objects whose data is scattered in memory between managed objects as a case where the library should not be used, since flattening such an object into bytes requires a step that allocates.

### Does Zero-Allocation Hashing produce the same hash on little-endian and big-endian machines?

Yes. The implementation uses native access where possible but is platform-endianness-agnostic, giving consistent results whatever the byte order, with the README noting that this only moderately affects performance.

## Sources

- [Issues](https://github.com/OpenHFT/Zero-Allocation-Hashing/issues)
- [License: Apache-2.0](https://github.com/OpenHFT/Zero-Allocation-Hashing/blob/ea/LICENSE)
- [OpenHFT/Zero-Allocation-Hashing on GitHub](https://github.com/OpenHFT/Zero-Allocation-Hashing)
- [README](https://github.com/OpenHFT/Zero-Allocation-Hashing/blob/ea/README.md)
- [Releases](https://github.com/OpenHFT/Zero-Allocation-Hashing/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/openhft-zero-allocation-hashing
