Benchmarking UUID Versions in Java with JMH

Every Java developer has written this:
UUID id = UUID.randomUUID();
It’s readable. It’s simple. It’s built-in. It works. But there are seven standardized UUID versions, each with a fundamentally different cost profile.
Choosing blindly can mean paying up to 4× more compute time than necessary at generation or worse, generating identifiers that silently destroy your database index performance through constant page splits.
⚡ TL;DR (Quick Recap)
- Fastest: v6 and v1 at ~22–23 ns/op — driven by timestamps, no cryptography.
- Surprising: SHA-1 (v5) is nearly 2× faster than MD5 (v3) due to hardware acceleration.
- Common trap: UUID.randomUUID() (v4) is the slowest at ~105 ns/op due to synchronized lock contention on SecureRandom.
- Modern choice: v7 is the right pick for database keys. It costs more to generate than v6, but saves massive overhead on B-tree index maintenance.
The 7 Versions: What Makes Them Different?
There are seven standardized UUID versions, each driven by a different underlying strategy.
Timestamp-Based (v1, v2, v6)
These rely on reading the system clock, building a bit layout and appending metadata (like a MAC address or POSIX domain). Because they avoid cryptographic operations and heavy entropy generation, they are generally the fastest.
Hash-Based (v3, v5)
Instead of generating a new random identifier, they produce a deterministic UUID from existing data using hashing (MD5 for v3, SHA-1 for v5). The primary cost driver here is the hashing algorithm itself.
Random-Based (v4, v7)
These depend heavily on entropy generation. v4 is fully random, while v7 combines a Unix timestamp prefix with random trailing bits. Generating secure randomness requires OS-level entropy, making these the most expensive to compute.
Benchmark Setup
To find out, we will use the popular uuid-creator library, which provides highly optimized implementations for all seven versions.
It measures the average execution time per operation in nanoseconds, running in a shared benchmark state, with 5 forks (each doing 1 warmup fork), warming up for 10 iterations of 1 second each, and then measuring over 10 iterations of 1 second each.
Each UUID version is isolated behind an interface:
public interface UUIDGenerationStrategy {
UUID generate();
}Implementing them in code looks like this:
// Generating modern UUIDs using uuid-creator
UUID v6 = UuidCreator.getTimeOrdered();
UUID v7 = UuidCreator.getTimeOrderedEpoch();
Test parameters:
- JMH 1.37 on Java 25
- JVM: -Xms1g -Xmx1g -XX:+UseG1GC
The Results
The benchmark neatly separates the versions into three distinct performance tiers.
The Fastest Group
Three implementations clearly dominated the benchmark:
- v6 (Time-ordered) –22.4 ns/op
- v1 (Time-based) –23.3 ns/op
- v2 (DCE Security) –25.4 ns/op
The Middle Ground
Only one implementation landed in the middle tier:
- v5 (Name-based SHA1) –55.1 ns/op
The Slowest Group
Three implementations clustered between 85 and 105 ns/op:
- v7 (Time-ordered Epoch) –85.5 ns/op
- v3 (Name-based MD5) –102.8 ns/op
- v4 (Random-based) –105.0 ns/op
What the Results Actually Mean
Tier 1 — Timestamp-Based (22–25 ns)
v1, v2 and v6 are fast because fetching system time and manipulating bits is cheap. However, modern implementations don’t just use System.currentTimeMillis(). The RFC 4122 spec requires 100-nanosecond precision.
// Core of a timestamp-based UUID
long timestamp = getSystemTimestampIn100NsIntervals();
// A clock sequence increments to prevent collisions within the same tick
short clockSequence = getNextClockSequence();
// Then packed into MSB/LSB via bitwise operators...
Why v6 beats v1: Notice that v6 is roughly 1 ns faster, but the real benefit is sortability. v1 puts the low bits of the timestamp first (little-endian), which destroys locality in databases. v6 rearranges the timestamp to place the high bits first (big-endian), making it naturally sortable by time.
Tier 2 — Hash-Based and the SHA-1 Surprise (55 ns)
v5 (SHA-1) at 55 ns is nearly 2× faster than v3 (MD5) at 103 ns. This is counterintuitive — most developers assume MD5 is faster because it produces a shorter hash.
The reason is hardware acceleration.
On many modern CPUs and JVM configurations, SHA-1 can benefit from hardware-assisted implementations, which may explain why v5 outperformed v3 in this benchmark.
Tier 3 — Random-Heavy (85–105 ns)
v4 (UUID.randomUUID()) is the absolute slowest option. The culprit isn't the instantiation of SecureRandom—Java actually caches it—but rather the synchronization lock.
// What java.util.UUID.randomUUID() actually does internally
private static class Holder {
// Lazily initialized static singleton
static final SecureRandom numberGenerator = new SecureRandom();
}
// Inside randomUUID()
SecureRandom ng = Holder.numberGenerator;
ng.nextBytes(randomBytes); // ← The contention point under load
Depending on the JDK implementation, SecureRandom may introduce additional synchronization or internal coordination costs under high concurrency.
v7 uses a timestamp component plus random bits, reducing the amount of randomness required compared to v4. The exact bit allocation depends on the UUID v7 implementation and RFC 9562 layout.
The Database Argument: Why v7 is the Modern Standard
If v7 is in the slowest tier for raw generation, why is it universally recommended as the modern best practice for database primary keys?
The recommendation for v7 is about database index efficiency, not CPU generation speed.
When a v4 UUID is inserted into a B-tree index, it lands at a completely random position. Under load, this causes massive page fragmentation, constant tree rebalancing and cache misses.
A v7 UUID uses a millisecond-precision timestamp prefix. Inserts become append-like and sequential.
- v4 inserts: random B-tree position → page splits, cache misses
- v7 inserts: time-ordered position → near-sequential, cache-friendly
The cost comparison that matters isn’t v7 vs v6 at generation time — it’s v7 vs v4 at query and insert time.
Final Takeaways
UUID generation feels trivial, but it has system-wide architectural impacts.
- v7 for every new database primary key. It’s a one-line swap in uuid-creator. The B-tree gains are permanent and compound under load.
- Keep v4 where unpredictability is the requirement. Session tokens, CSRF values, API keys — identifiers where entropy is the feature and the value will never be a database index.
- v6 for sortable, non-database identifiers. Event IDs, log correlation IDs, distributed trace tokens — anything that benefits from time-ordering but isn’t a B-tree PK.
- v5 over v3, always. Same determinism guarantee, roughly half the cost, thanks to hardware SHA-1 acceleration.
The fastest UUID isn’t necessarily the best UUID. Once identifiers hit storage systems, database behavior matters far more than nanoseconds spent generating them.
You can find all the code on GitHub.
Originally posted on marconak-matej.medium.com.