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 caseUUID v4UUID v7
Primary key in a large database tableSlower inserts, worse cache localityRecommended. Sorted inserts, faster, smaller index
Distributed system event IDHard to debug across logsRecommended. Sortable by creation time
Idempotency key for webhooksFine — both workFine — slightly easier to debug
Share token in a public URLRecommended. Timestamp is hiddenAvoid — leaks creation time
Anti-enumeration order IDRecommended. Hides volume signalAvoid — leaks creation rate
Anonymous analytics IDRecommended. No creation time leakAvoid
Filename for uploaded imagesFine — both workSlightly nicer because they sort
Migration from auto-increment integerFine, but loses sort orderRecommended. 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. Use uuid v10+ (uuid.v7()) or @paralleldrive/cuid2 if you want a different format entirely.
  • Python: uuid.uuid4() is built in. For v7, use uuid6 or uuid_v7 from PyPI; CPython’s stdlib added native v7 support in 3.13.
  • PostgreSQL: gen_random_uuid() is v4. PostgreSQL 18 (released 2025) ships native uuidv7(); on older versions, the pg_uuidv7 extension is the standard choice.
  • Java: UUID.randomUUID() is v4. Use com.github.f4b6a3:uuid-creator for v7.
  • Go: github.com/google/uuid v1.6+ supports v7 via uuid.NewV7().
  • Rust: the uuid crate v1.6+ exposes Uuid::now_v7() behind the v7 feature 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:

  1. Switch new inserts to v7. Change your application’s uuid() call site (or the column default in Postgres 18: DEFAULT uuidv7()).
  2. Leave old v4 rows alone. They’re already inserted; rewriting them gains you nothing and breaks every external reference.
  3. Wait. As the table grows, the proportion of v7 rows climbs, page splits drop, and the index gradually rebalances on its own. Run REINDEX opportunistically 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 (UUID in 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.