What's New in RFC 9562: UUID v6, v7, and v8 Explained
In May 2024, RFC 9562 replaced RFC 4122 as the current UUID specification. It didn't invalidate anything — every UUIDv1, v3, v4, and v5 value ever generated remains fully valid — but it formally added three new versions (v6, v7, v8) and, for the first time, standardized reserved nil and max UUID values. This post is a field guide to exactly what changed at the bit level, why each new version exists, and how to actually generate and store them.
Why RFC 9562 Exists
RFC 4122 (2005) defined four usable versions, all built around one of two ideas: embed a timestamp plus a machine identifier (v1), or derive the value deterministically or randomly with no ordering property at all (v3/v4/v5). Twenty years of production use surfaced a specific, recurring complaint: none of the original versions were both coordination-free and naturally sortable. v1 sorts poorly in practice because most engines don't interpret its non-monotonic field layout as a plain integer comparison. v4 doesn't sort at all. RFC 9562 exists primarily to close that gap — v6 and v7 are direct, purpose-built answers to "I want a UUID that behaves like a good database key," and v8 formalizes an escape hatch for anyone whose needs don't fit the standard layouts at all.
| RFC | Published | Status | Versions Defined |
|---|---|---|---|
| RFC 4122 | July 2005 | Obsolete | v1, v3, v4, v5 |
| RFC 9562 | May 2024 | Current | v1, v3, v4, v5, v6, v7, v8 (v2 reserved, undefined) |
UUIDv6: A Sortable Bridge for UUIDv1 Systems
UUIDv6 takes UUIDv1's exact same inputs — a 60-bit Gregorian timestamp (100-nanosecond intervals since 1582-10-15) and a node identifier — and reorders the timestamp fields into big-endian, most-significant-first order, so that a simple byte or integer comparison sorts UUIDv6 values chronologically. UUIDv1's fields were originally ordered the opposite way (least-significant time component first) for historical reasons tied to an old DCE implementation, which is exactly what breaks naive sorting.
0 32 48 52 64 80
+-----------------------------------------+------+------------+---------+
| time_high (32 bits) | time_mid (16)| ver(4)| time_low(12) |
+-----------------------------------------+------+------------+---------+
|var| clock_seq (14 bits) | node (48 bits) |
+---+------------------------+---------------------------------------------+
| Field | Bits | Description |
|---|---|---|
time_high |
32 | Most significant 32 bits of the 60-bit timestamp |
time_mid |
16 | Middle 16 bits of the timestamp |
ver |
4 | Version field, 0b0110 (6) |
time_low |
12 | Least significant 12 bits of the timestamp |
var |
2 | Variant field, 0b10 |
clock_seq |
14 | Clock sequence, same purpose as in v1 |
node |
48 | Node identifier — MAC address or random, same as v1 |
Because it's a direct field reshuffle of v1, UUIDv6 exists specifically as a migration path: systems already generating v1 values that need sortability can switch generators without changing anything about their timestamp source or node-ID strategy. RFC 9562 is explicit that new systems without existing v1 infrastructure should prefer v7 instead.
// Illustrative field-reordering logic (most libraries handle this internally)
public static Guid ReorderV1ToV6(Guid v1)
{
byte[] b = v1.ToByteArray(); // .NET's internal layout is itself little-endian for the first fields
// Real implementations should use a maintained UUIDv6 library rather than
// hand-rolling this — bit-reordering bugs are easy to introduce silently.
throw new NotImplementedException("Use a spec-compliant library, e.g. UUIDNext, for v6 generation.");
}
UUIDv7: The Modern Default for New Systems
UUIDv7 abandons the Gregorian/node-ID model entirely in favor of something simpler: a Unix timestamp in milliseconds, left-padded into the most significant 48 bits, followed by random data for everything else.
0 48 52 64
+---------------------------------------+------+-------------------+
| unix_ts_ms (48 bits) | ver(4)| rand_a (12 bits) |
+---------------------------------------+------+-------------------+
|var| rand_b (62 bits) |
+---+--------------------------------------------------------------+
| Field | Bits | Description |
|---|---|---|
unix_ts_ms |
48 | Milliseconds since the Unix epoch (Jan 1, 1970) — plain, unsigned, big-endian |
ver |
4 | Version field, 0b0111 (7) |
rand_a |
12 | Random bits, or optionally a sub-millisecond counter/precision extension |
var |
2 | Variant field, 0b10 |
rand_b |
62 | Random bits |
The critical design choice: no node ID, no clock sequence coordination — rand_a and rand_b are simply random on every call. This sidesteps the entire class of problems covered in our collision stories post around cloned-VM node IDs, while keeping the property that actually matters for databases: values generated in the same millisecond sort together, and values from later milliseconds sort after earlier ones.
RFC 9562 explicitly allows implementations to use part of rand_a as a monotonic counter or sub-millisecond timestamp extension instead of pure randomness, for applications that need strict ordering within the same millisecond — this is left as an implementation choice, not a fixed requirement.
// .NET 9+
Guid id = Guid.CreateVersion7(); // current time
Guid idAt = Guid.CreateVersion7(someDateTimeOffset); // explicit timestamp, useful for backfills/tests
// Node.js — no native crypto.randomUUID() v7 support yet as of this writing;
// use a maintained library
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
-- SQL Server: no native NEWID()-equivalent for v7 yet; generate application-side
CREATE TABLE Orders (
Id UNIQUEIDENTIFIER PRIMARY KEY, -- populated by the app with Guid.CreateVersion7()
CreatedAt DATETIME2 NOT NULL
);
-- Postgres 18+: native uuidv7() generation function
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuidv7(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
UUIDv8: The Standardized Escape Hatch
UUIDv8 doesn't define a data layout at all. RFC 9562 reserves the version nibble (0b1000) and the variant bits, and leaves the entire remaining 122 bits application-defined. Its purpose is narrow but useful: if an organization already has a custom identifier scheme — a bit-packed value combining a shard ID, a sequence, and a timestamp in a proprietary layout, for instance — wrapping it as a UUIDv8 makes it pass validation and interoperate with any tooling, database column type, or library that expects a spec-compliant UUID, without forcing that organization onto v1–v7's specific field semantics.
0 48
+------------------------------------------------------------------+
| custom_a (48 bits) |
+------------------------------------------------------------------+
| custom_b (12) |ver(4)| custom_c (62 bits, incl. var) |
+---------------------+-----+---------------------------------------+
RFC 9562 only mandates the version and variant bit placement — everything else in the layout above is illustrative of where custom data goes, not what it must contain.
public static Guid CreateV8(ulong customHigh, ulong customLow)
{
Span<byte> bytes = stackalloc byte[16];
BitConverter.TryWriteBytes(bytes[..8], customHigh);
BitConverter.TryWriteBytes(bytes[8..], customLow);
bytes[6] = (byte)((bytes[6] & 0x0F) | 0x80); // version = 8
bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80); // variant = 10
return new Guid(bytes);
}
The trade-off is explicit: you gain full flexibility and lose universal interoperability — nothing outside your own codebase knows what a given v8 UUID's bits actually mean, unlike v1–v7 where the layout itself carries meaning any compliant parser can extract.
Comparison: v1/v6 vs. v7 vs. v8
| Property | UUIDv1 | UUIDv6 | UUIDv7 | UUIDv8 |
|---|---|---|---|---|
| Timestamp resolution | 100ns (Gregorian) | 100ns (Gregorian) | 1ms (Unix) | Application-defined |
| Sorts chronologically as raw bytes | ❌ | ✅ | ✅ | Depends on layout |
| Requires node/MAC identifier | ✅ | ✅ (inherited from v1) | ❌ | Depends on layout |
| Coordination-free generation | Partial (node ID mgmt) | Partial (node ID mgmt) | ✅ | Depends on layout |
| New-system recommendation (per RFC 9562) | ❌ (legacy) | ❌ (migration path only) | ✅ | Only for custom schemes |
Practical Migration Guidance
- Already using UUIDv1 and need sortability without touching your node-ID infrastructure: UUIDv6 is a drop-in field reorder.
- Building something new: UUIDv7 is RFC 9562's own recommendation, and matches what we found in our own insert/query benchmark — it recovers most of UUIDv4's clustered-index performance penalty while keeping fully coordination-free generation.
- Already have a proprietary ID scheme and just want it to validate and interoperate as a standard UUID type: UUIDv8 wraps it without forcing a redesign.
- Existing UUIDv3/v4/v5 data: nothing to migrate. RFC 9562 didn't deprecate any of them — they remain valid, standard, and fully supported indefinitely.
RFC 9562's real contribution isn't a new algorithm — v7 is, at its core, "timestamp plus random bits," a pattern plenty of ad hoc systems had already converged on independently. Its contribution is making that pattern a formally interoperable standard, so every language, database, and library can agree on exactly what those 128 bits mean.