UUID v4 vs UUID v7: Which One Should Your App Actually Use in 2026?
Published on May 14, 2026 by The Kestrel Tools Team • 8 min read
You’re reaching for a UUID. Maybe it’s a new users.id, a Stripe-style evt_ event ID, an idempotency key for a webhook handler, or a filename for an uploaded image. Your fingers type uuid() out of muscle memory and you move on with your day. Six months later, your DBA pings you about index bloat on a 40-million-row table and you start to wonder: did I pick the right kind of UUID?
Most of us learned UUIDs in the v4-everywhere era, when “random 128 bits, you’re done” was the entire mental model. That model is now ten years out of date. RFC 9562 — published in May 2024 — formalized UUID v6, v7, and v8, and the database performance numbers have been quietly mounting in v7’s favor ever since. But v4 hasn’t gone anywhere either, and it shouldn’t.
This is the uuid v4 vs v7 decision guide we wish existed: short, opinionated, with real example output, written for the person who actually has to commit the migration. You can generate v4 and v7 UUIDs side by side on Kestrel Tools while you read.
UUID v4 vs UUID v7: which one should you use?
Use UUID v7 by default in 2026. It’s a 128-bit identifier just like v4, but the first 48 bits encode a Unix millisecond timestamp, which makes v7 IDs sortable by creation time and dramatically friendlier to B-tree database indexes. Stick with UUID v4 only when you specifically need creation time to be unguessable — public-facing share tokens, anti-enumeration IDs, or any case where an attacker correlating two IDs by timestamp would matter.
That one paragraph covers about 90% of real decisions. The rest of this post is the why, the example output, and the edge cases.
What UUID v4 and UUID v7 actually look like
Both are 128 bits, both serialize as a 36-character canonical string with hyphens, both are defined in RFC 9562 (which obsoleted RFC 4122). The difference shows up in the bytes.
UUID v4 example output (random):
6f1a3d8e-9b1f-4f3a-bd24-7e5c0a4b1d6e
f24c1e5b-7d92-4a05-9c3a-1ef8b2d403aa
2a91c5e0-4d6e-4b1a-9f88-c047a8d3ee21
The 4 after the second hyphen marks it as a version-4 UUID. The remaining 122 bits are pure randomness from a CSPRNG.
UUID v7 example output (time-ordered):
01959c2d-4a7c-7c1d-8d9e-3a4b1f2e5c80
01959c2d-4a8e-7e22-8a31-9b6c4f0e7d11
01959c2d-4a8e-7e23-9c5b-7d2f3a14b6c9
Notice the first three groups are nearly identical across all three v7 IDs. That’s not a collision — that’s the timestamp. The first 48 bits (01959c2d-4a7c through 01959c2d-4a8e) are a Unix millisecond timestamp. The 7 after the second hyphen marks the version. The remaining 74 bits are random.
This tiny structural change is the whole reason v7 exists.
Why UUID v7 is faster as a database primary key
This is the part nobody tells you in a UUID tutorial. UUID v4’s randomness is great for uniqueness and terrible for B-tree indexes, which is what every relational database uses for primary keys.
When you insert a new row, the database has to put the new key into the index in sorted order. If the key is random (v4), the new row could land anywhere in a 40-million-row index. The database loads a random page from disk, modifies it, writes it back. Repeat that for every insert and you get page splits, fragmentation, and a hot dirty-page churn that destroys cache hit ratios.
With UUID v7, every new key sorts after every previous one (within the same millisecond, the random tail breaks ties). Inserts always land at the right edge of the index. The database touches one page — the rightmost one — over and over. That page stays hot in cache. Page splits effectively stop.
Independent benchmarks consistently show UUID v7 inserts running 2x to 10x faster than UUID v4 inserts on InnoDB and PostgreSQL B-tree indexes once tables grow past a few million rows. Index size on disk also shrinks meaningfully because of reduced fragmentation. None of this matters at 10,000 rows. All of it matters at 10 million.
If your UUID column is the primary key — or any indexed column — and you’d been picking v4 by reflex, v7 is a free upgrade.
When UUID v4 is still the right answer
UUID v7 has one trait that’s a feature in databases and a leak everywhere else: the timestamp is recoverable from the ID. Anyone who sees a v7 UUID can extract the millisecond it was created. That’s almost always fine, but in a few cases it isn’t:
- Public, guessable resources. A v7 share token for a private document tells an attacker exactly when the document was created — useful for correlation attacks against other timestamps in your system.
- Anti-enumeration IDs. If you’re using a UUID specifically because you don’t want sequential IDs (
/orders/1,/orders/2, …) to leak business volume, a v7 leaks the same kind of signal. The order rate per minute is recoverable from any pair of v7 IDs. - Privacy-sensitive identifiers. Anonymous user IDs, anonymized analytics events, throwaway session tokens — anywhere creation time itself is sensitive metadata.
In all of those, stick with v4. Use v4 specifically when you need the timestamp to be unknown, not when you’ve simply always typed uuid().
A practical pattern: use v7 for internal primary keys (users.id, events.id) and v4 for any ID that crosses a trust boundary as part of a URL (share_token, password_reset_token, unsubscribe_token).
UUID v4 vs UUID v7: decision matrix
| Use case | UUID v4 | UUID v7 |
|---|---|---|
| Primary key in a large database table | Slower inserts, worse cache locality | Recommended. Sorted inserts, faster, smaller index |
| Distributed system event ID | Hard to debug across logs | Recommended. Sortable by creation time |
| Idempotency key for webhooks | Fine — both work | Fine — slightly easier to debug |
| Share token in a public URL | Recommended. Timestamp is hidden | Avoid — leaks creation time |
| Anti-enumeration order ID | Recommended. Hides volume signal | Avoid — leaks creation rate |
| Anonymous analytics ID | Recommended. No creation time leak | Avoid |
| Filename for uploaded images | Fine — both work | Slightly nicer because they sort |
| Migration from auto-increment integer | Fine, but loses sort order | Recommended. Preserves insertion order |
If the row in your table has “recommended” in the v7 column, that’s almost always the right pick.
Generating UUID v7 in your language of choice
The ecosystem has caught up to RFC 9562 in 2025–2026. Concrete starting points:
- Node.js: native
crypto.randomUUID()only emits v4. Useuuidv10+ (uuid.v7()) or@paralleldrive/cuid2if you want a different format entirely. - Python:
uuid.uuid4()is built in. For v7, useuuid6oruuid_v7from PyPI; CPython’s stdlib added native v7 support in 3.13. - PostgreSQL:
gen_random_uuid()is v4. PostgreSQL 18 (released 2025) ships nativeuuidv7(); on older versions, thepg_uuidv7extension is the standard choice. - Java:
UUID.randomUUID()is v4. Usecom.github.f4b6a3:uuid-creatorfor v7. - Go:
github.com/google/uuidv1.6+ supports v7 viauuid.NewV7(). - Rust: the
uuidcrate v1.6+ exposesUuid::now_v7()behind thev7feature flag.
If you just need a v7 UUID right now without writing any code, Kestrel Tools’ UUID Generator supports both v4 and v7 in the browser. It runs entirely client-side, so the IDs never leave your machine — handy when you’re generating real production keys.
How to migrate an existing v4 column to v7 (or not)
Short answer: don’t bother migrating existing rows. UUID v4 and v7 share the same column type (UUID in Postgres, BINARY(16) or CHAR(36) elsewhere), so they coexist cleanly. The realistic migration is:
- Switch new inserts to v7. Change your application’s
uuid()call site (or the column default in Postgres 18:DEFAULT uuidv7()). - Leave old v4 rows alone. They’re already inserted; rewriting them gains you nothing and breaks every external reference.
- Wait. As the table grows, the proportion of v7 rows climbs, page splits drop, and the index gradually rebalances on its own. Run
REINDEXopportunistically if you want the speedup sooner.
This incremental approach is why v7 adoption is comparatively safe — you never have a “big bang” migration day.
Common pitfalls when adopting UUID v7
A few things that bite teams switching to v7:
- Storing as
VARCHAR(36)instead of binary. Both v4 and v7 should be stored as 16 raw bytes (UUIDin Postgres,BINARY(16)in MySQL). Storing as a 36-character string roughly triples storage and kills the index speedup. - Treating v7 as monotonically unique within a millisecond. It isn’t, by default. Within the same millisecond the trailing 74 bits are random; collisions are astronomically unlikely but not impossible. RFC 9562 describes an optional monotonic counter mode if you need strict ordering — most libraries default to random.
- Exposing v7 IDs in URLs without thinking. This is the big one. If your
/share/<uuid>link suddenly leaks creation timestamps after a v7 migration, that’s a real privacy regression. Audit your URL surface before flipping the default. - Mixing v7 and integer auto-increment as a primary key in the same table. Pick one. Mixing creates ambiguous sort orders and makes pagination by ID unreliable.
So which one should you use?
In 2026, the default has flipped. Reach for UUID v7 for primary keys, event IDs, and anywhere insertion order is useful. Keep UUID v4 for share tokens, anti-enumeration IDs, and identifiers where leaking the creation time would matter.
If you want to feel the difference, generate a few of each and watch them sort: paste a batch of v4 UUIDs into a sorted list and they look like noise; paste a batch of v7 UUIDs and they line up by creation time on their own. That’s the entire pitch.
You can try this in the browser right now with the Kestrel Tools UUID Generator — it generates v4 and v7 client-side, side by side, no signup, no upload. If you’ve been defaulting to v4 since 2014, this is the smallest possible experiment to see what you’ve been missing.