Ttooleras
Generators

UUID v4 vs v7: The Default Has Quietly Changed

UUIDv7 replaced v4 as the smart default in 2024. This guide walks through every UUID version, the collision math, database implications, and alternatives like ULID and NanoID — with honest guidance on when UUIDs are the wrong answer entirely.

Tooleras22 min read5,094 words
Advertisement

The insert query had been taking longer every month. Not by much — maybe 5 milliseconds here, 8 there. For the first six months of the product's life, nobody noticed. At around 80 million rows in the users table, someone finally ran EXPLAIN ANALYZE on a batch insert and the numbers were ugly: half the time was going into B-tree index maintenance, and the index itself was twice the size it needed to be.

The primary key was a UUID. Every new user got a uuid_generate_v4() value — 122 bits of randomness spread like salt across the key space, so every insert landed at a random leaf node and split a page. At 100 million rows that fragmentation adds up to tangible latency, bloated indexes, and cache pressure that you feel in every range scan.

The fix was two lines of DDL and a swap of the default value function. Switch new rows from UUIDv4 to UUIDv7 — same 128 bits, same uniqueness guarantees, but the first 48 bits are now a millisecond timestamp. Inserts append to the right side of the B-tree like an auto-incrementing integer would. Fragmentation stops accumulating. The existing v4 values keep working forever; only the new ones benefit, and over time the hot part of the index gets tidier.

UUIDv7 isn't new anymore. RFC 9562 standardized it in May 2024, Postgres 18 shipped native uuidv7() in late 2024, and every major language has had library support since 2023. The part that is new is the consensus: for anything going into a database primary key, v7 is the default and v4 is the special case. A lot of existing writing online still defaults to v4, because it was written before the RFC landed, and that's the gap this post is here to close.

We'll cover every UUID version (yes, every one — v1 through v8), the actual math behind collision probability, why v4 hurts database performance, how v7 fixes it, per-database support in 2026, the alternatives worth knowing (ULID, NanoID, KSUID, Snowflake), and — this is the section most guides skip — when UUIDs are the wrong answer entirely. If you just want to generate some IDs, our UUID generator handles v1, v4, v5, and v7 in the browser. If you want to understand the decision, read on.

What a UUID actually is, in 30 seconds

A UUID is 128 bits. That's 16 bytes, or 32 hexadecimal digits, conventionally grouped 8-4-4-4-12 with hyphens:

019297c9-2c7a-7a62-b1d4-4b3c5a8e0f91

Two of those hex digits tell you what kind of UUID you're looking at. The version is the first character of the third group — 7 in the example above. The variant is the first character of the fourth group — b here, which (along with 8, 9, and a) means this UUID follows the IETF standard (RFC 9562 for new ones, RFC 4122 for legacy).

Once you know where to look, reading a UUID is trivial:

019297c9-2c7a-7a62-b1d4-4b3c5a8e0f91
                │           │
                └── version │
                            └── variant

Version 4 means it's fully random. Version 7 means the leading bits are a timestamp. Version 5 means it's a deterministic hash. Version 1 is the old MAC-and-timestamp format. The rest of the bits are either random, encoded data, or a combination.

The format is agreed upon globally. Microsoft calls the same thing a GUID. Databases store them as a 16-byte binary column. Languages expose them as 32-char hex strings. Internally: always 128 bits.

Version by version: what's actually in each one

Eight versions exist in the spec. Most developers use two of them. The others are worth knowing because they solve problems you'll eventually hit.

Version 1 — timestamp + node

UUIDv1 was the original design from RFC 4122. The bits are structured as a 60-bit timestamp (100-nanosecond intervals since October 15, 1582 — yes, really, the date of the Gregorian calendar reform), a 14-bit clock sequence, and a 48-bit node identifier that was originally supposed to be the machine's MAC address.

Two problems emerged. One: exposing a MAC address leaks information about what hardware generated the ID, which is a privacy issue. Two: if you generated UUIDs on a virtual machine or a container without a stable MAC, uniqueness assumptions broke. Modern implementations randomize the node field to defuse both problems, but at that point you've lost most of what made v1 distinct.

Don't start new projects with v1. If you want time-ordering, v7 is better in every way that matters. V1 persists in legacy codebases and occasionally in systems that predate v7.

Version 3 — name-based with MD5

UUIDv3 is deterministic. You feed it a namespace UUID and a name string, it hashes them together with MD5, and you get the same UUID every time for the same inputs. The point is idempotency — two independent systems can generate the same identifier for the same entity without coordinating.

The problem is MD5. It's been cryptographically broken since the mid-2000s. You can construct collisions deliberately. For a non-adversarial context (internal content addressing, migration key mapping) it's still fine because the risk is theoretical. For anything touching security, use v5 instead.

Version 4 — random

The workhorse. 122 of the 128 bits are random (6 bits are reserved for version and variant), drawn from whatever cryptographic random source your language provides — crypto.getRandomValues in the browser, crypto/rand in Go, secrets in Python, /dev/urandom on Linux.

V4 has been the default for fifteen years for a reason: it's simple, it needs no coordination, and the collision probability is absurdly low. It's the right choice for session tokens, request trace IDs, one-shot identifiers, and anything that isn't a database primary key. The only downside — the one that made v7 necessary — is that pure randomness is bad for B-tree indexes, which we'll get to.

Version 5 — name-based with SHA-1

Exactly like v3 but with SHA-1 instead of MD5. Still deterministic, still takes a namespace and a name. SHA-1 is also considered broken for cryptographic purposes but for UUID generation the attack models don't apply — we're not signing anything, just producing a 128-bit fingerprint of a name.

Use v5 when you need the same input to produce the same UUID across independent systems. Common cases:

  • Content addressing — hash a URL or file path into a stable ID
  • Idempotency keys — same operation parameters produce the same request ID
  • Migration — map legacy IDs to new UUIDs deterministically
  • Cross-system joins — two services derive the same UUID for the same business entity without a shared database

Our hash generator computes raw SHA-1 if you want to see the underlying hash before UUID formatting. Note that v5 truncates and modifies the hash to fit UUID format — the v5 output is not a direct SHA-1 of the input.

Version 6 — v1 reordered

UUIDv6 is v1 with the timestamp bits rearranged so the UUID sorts lexicographically in time order. It exists mostly for teams that had v1 values and wanted to switch to something sortable without rewriting everything.

V6 is a footnote. V7 does the same job with better entropy, a simpler bit layout, and no MAC address to worry about. Skip v6 unless you're specifically migrating from v1.

Version 7 — timestamp + random

The new default. Bit layout:

48 bits   4 bits   12 bits   2 bits   62 bits
┌───────┬────────┬─────────┬────────┬────────┐
│  ms   │ ver=7  │ rand_a  │ var=10 │ rand_b │
│ unix  │        │         │        │        │
└───────┴────────┴─────────┴────────┴────────┘

The first 48 bits are a Unix millisecond timestamp — the same format Date.now() returns in JavaScript. Then 4 bits for the version identifier. Then 12 random bits, 2 variant bits, and 62 more random bits. Total random entropy: 74 bits. That's less than v4's 122 but still plenty for uniqueness — the expected collisions at this level of entropy require generating trillions of UUIDs within the same millisecond, which is physically hard.

The timestamp goes in the most significant bits, which is the magic. When sorted as a 128-bit number, v7 UUIDs come out in roughly creation order. When stored as a database primary key, new rows append to the right side of the B-tree instead of scattering. You get the distributed-generation benefits of UUIDs with the insert-performance profile of auto-incrementing integers.

Paste a v7 UUID into our Unix timestamp converter with a small manual step — take the first 12 hex characters (the first 48 bits), convert to decimal, and you have the creation timestamp in milliseconds. It's that simple.

Version 8 — custom

V8 is a blank canvas. The spec defines the version field and the variant field; everything else is up to the implementer. It exists for teams that want RFC-compliant IDs with custom structure — maybe a tenant ID baked into the leading bytes, maybe a sharding prefix, maybe a specific timestamp format that doesn't match v7's layout.

Nobody should pick v8 unless they have a specific reason and know what they're doing. It's an escape hatch, not a default.

Collision probability, with numbers that mean something

The birthday paradox explains why you need fewer UUIDs to hit a collision than pure intuition suggests. With N bits of entropy, the collision threshold (50% chance of at least one duplicate) is roughly 2^(N/2) values — not 2^N. That's the square root, which is a much smaller number.

For UUIDv4 with 122 bits of entropy:

  • 50% collision probability at about 2^61 UUIDs = 2.3 × 10^18 values = 2.3 quintillion.
  • Generate one billion UUIDs per second nonstop, that's ~73 years to reach the threshold.
  • Generate one million UUIDs per second — which is more than most real applications sustain — that's ~73,000 years.

For UUIDv7 with 74 bits of random entropy:

  • 50% collision probability at about 2^37 UUIDs per millisecond.
  • The timestamp differentiates UUIDs generated in different milliseconds, so collisions only matter within the same millisecond.
  • You'd need to generate about 130 billion UUIDs in a single millisecond to hit 50% — physically impossible on current hardware.

The upshot: if you see duplicate UUIDs in production, it's always a broken random number generator, not actual statistical collision. The classic culprit is an app using Math.random() in JavaScript instead of crypto.getRandomValues(). Math.random() has maybe 32 useful bits of state depending on the engine, which reduces your effective entropy enough to make collisions genuinely possible. Every UUID library in every modern language uses cryptographic randomness by default. Roll your own and you're taking unnecessary risk.

UUIDv7 in depth

Let's generate a v7 from scratch, in JavaScript, with no library. It fits in about 15 lines:

function uuidv7() {
  const ms = BigInt(Date.now());
  const randA = crypto.getRandomValues(new Uint16Array(1))[0] & 0x0fff;
  const randB = crypto.getRandomValues(new BigUint64Array(1))[0] & 0x3fffffffffffffffn;

  const hex = [
    ms.toString(16).padStart(12, "0"),                          // 48 bits timestamp
    "7" + randA.toString(16).padStart(3, "0"),                  // version 7 + 12 random
    (0x8000n | (randB >> 48n)).toString(16).padStart(4, "0"),   // variant + high random
    (randB & 0xffffffffffffn).toString(16).padStart(12, "0"),   // low 48 random bits
  ].join("-");

  return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-"
       + hex.slice(13, 17) + "-" + hex.slice(18, 22) + "-"
       + hex.slice(23);
}

Walk through the logic: grab the current millisecond timestamp as a bigint, grab 12 random bits for rand_a, grab 62 random bits for rand_b, then stitch the version nibble (7) and variant bits (10 binary = prefix with 0x8000 for the word containing them) into the right positions.

You don't want to ship this. The real uuid package for Node handles the monotonicity rules that RFC 9562 recommends for UUIDs generated within the same millisecond — you're supposed to use a counter to ensure lexicographic ordering even under high throughput. Rolling your own skips that and can produce out-of-order v7s at the microsecond level. For production, use the library. For understanding what a v7 actually looks like under the hood, the 15-line version above tells you everything.

One practical note on the timestamp: 48 bits at millisecond resolution gives you room until year 10,889 AD. You will not run out.

If you want to inspect a v7 UUID you already have, pull out the first 12 hex characters, parse them as a hexadecimal number, and that's the Unix timestamp in milliseconds. Feed it to our timestamp converter to turn it into a human-readable date. You now know exactly when that ID was created, which is often useful when debugging event ordering or correlating logs across services.

The database primary key question

This is the debate that drove v7's adoption, so it's worth going through carefully.

A B-tree index on a primary key stores rows sorted by the key value. When you insert a row, the database finds the right leaf page and writes the entry there. If the new key value sorts greater than every existing key, it lands on the rightmost page and appends cheaply — that page stays hot in memory, fills up, and eventually splits into two when full.

Random UUID values break this. Every UUIDv4 insert lands on a random leaf page. Pages get touched and dirtied unpredictably. The hot working set is effectively the entire index. Pages split more often because writes arrive out of order, and those splits leave the index fragmented with half-full pages. At small scale none of this matters; at millions of rows it matters somewhat; at hundreds of millions it can dominate your database's I/O profile.

UUIDv7 puts the timestamp first. New v7 values are numerically greater than recent v7 values, so inserts append to the right side of the B-tree just like sequential integers. The index stays sequential, fragmentation stops accumulating, and the hot working set reduces to the recent pages. Benchmarks from Credativ and Neon show insert throughput improvements of 2–10x on large tables when switching from v4 to v7.

Per-database support, as of 2026

PostgreSQL 18 (November 2024+) — native uuidv7() function. Just use it:

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuidv7(),
    email TEXT UNIQUE NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

For older Postgres (12–17), install the pg_uuidv7 extension or generate the UUID in application code.

MySQL 8.0+ — no native v7 function. The closest native option is UUID_TO_BIN(UUID(), TRUE) which reorders v1 components to be sortable, but that's not actually v7 and it still embeds the MAC address. The better path is to generate v7 in application code and insert it as a BINARY(16) column. Many teams standardize on a uuid_v7() stored procedure defined once and referenced everywhere.

SQL ServerNEWID() returns a custom non-standard UUID. NEWSEQUENTIALID() returns IDs in increasing order within a server but isn't v7 and isn't portable. For v7 in SQL Server, generate in application code and store as UNIQUEIDENTIFIER. Watch for the mixed-endian storage: SQL Server flips byte order in the first three groups when storing, so the same 16 bytes display differently than in other systems. It's cosmetic but surprising the first time you hit it.

SQLite — no native UUID type at all. Store as TEXT (36 bytes) or BLOB (16 bytes). Generate v7 in application code. SQLite is often used for local-first apps where a sortable ID is particularly valuable, so the extra step is worth it.

MongoDB — ObjectIds are already time-sorted by design (4-byte timestamp + 5 random + 3 counter). If you need UUID format specifically, generate v7 and store as a UUID BSON type.

When v4 is still fine

Not every use case needs v7. Session tokens, trace IDs, API request IDs, correlation IDs, short-lived cache keys — anything that isn't a primary key on a massive table — v4 is just fine. The index fragmentation argument only applies when the UUID is the clustered or primary index. A v4 on an unindexed column or as a secondary identifier costs nothing.

Alternatives worth knowing

UUIDs aren't the only game. A few alternatives solve specific problems better, and knowing when to reach for them separates a senior engineer's design from a tutorial's.

FormatSizeEncodingTime-sortedNotes
UUIDv4128 bitshexnothe classic; random
UUIDv7128 bitshexyesthe new default for PKs
ULID128 bitsCrockford base32yes26 chars, URL-safe, popular alternative to v7
NanoIDconfigurableURL-safe base64 variantno21 chars default; smaller = tradeoff
KSUID160 bitsbase62yes27 chars; 32-bit timestamp limits to year 2136
Snowflake64 bitsdecimal integeryesneeds worker ID registry
CUID2variablebase36nocollision-resistant client-side
TSID64 bitsinteger or base32yesfits in BIGINT; popular in Java ecosystems

A few worth calling out:

ULID (ulid/spec) was the most popular v7 alternative before v7 existed. Same 128-bit format, same time-sorting idea, but Crockford base32 instead of hex gives you 26 case-insensitive alphanumeric characters (no O/0, I/L confusion) that are URL-safe by default. For greenfield projects in 2026, v7 is usually the better choice because it's an IETF standard with native database support. ULID still wins if you want a shorter human-readable form — 01HE7VZJ3W5A9RWK8G9QEMJW5A reads more cleanly than a hex UUID.

NanoID (ai/nanoid) is different — not a standard, just a pragmatic 21-character URL-safe ID generator. 21 chars of the default alphabet gives ~126 bits of entropy, comparable to UUIDv4. It's the right pick for public-facing URLs where you want compact tokens that don't scream "database ID." If you're already using our random string generator for short unique tokens, NanoID formalizes that pattern.

Snowflake IDs fit in 64 bits, which matters if you really care about storage. They encode time + worker ID + sequence. The catch is that every worker needs a unique ID, which means a registry, which means coordination, which is the problem UUIDs were invented to avoid. Twitter created Snowflake for tweets; Discord uses it for message IDs. Use it when 64-bit storage actually matters and you have infrastructure for worker ID assignment. Most apps don't.

CUID2 (paralleldrive/cuid2) is a client-side generator that uses SipHash over per-fingerprint state to minimize collision risk in untrusted environments — good for client-generated IDs where you don't control the entropy source. It's also time-sortable and shorter than UUIDs. Less standard, but growing adoption in frontend-heavy apps.

When UUIDs are the wrong answer

This section is where most guides pretend UUIDs are always right. They aren't.

If you're building a single-instance app with a single database that you have no plans to shard, BIGSERIAL (Postgres) or AUTO_INCREMENT (MySQL) is usually the better choice. The reasons:

Size. An integer primary key is 4 or 8 bytes. A UUID is 16. Across a large table with several indexes, that doubles the space your indexes take in memory. Smaller index = more fits in cache = faster lookups.

Simplicity. Integer IDs are human-readable. You can cite "user 42" in a bug report. You can spot an off-by-one in a migration. UUIDs all look the same, so you end up copy-pasting them, which eats time.

Natural ordering. Auto-increment integers are trivially sortable. v7 gets you most of the way there but still costs a hex-to-bigint conversion if you want to sort numerically.

The argument for UUIDs is distribution. If multiple services might generate new records simultaneously, or if you want to mint an ID on the client before it hits the database, or if you plan to merge databases, UUIDs buy you those capabilities for the cost of the extra 8 bytes. But if you aren't doing any of those things, don't pay the cost. Auto-increment is the more boring, more reliable choice for plenty of real-world applications.

Exposing integer IDs in URLs is sometimes called out as a security risk — someone can increment /users/42 to /users/43 and see if that user exists. This is a IDOR vulnerability and the fix is authorization, not opaque IDs. UUIDs in URLs make the enumeration slightly harder, but any system that relies on unguessable IDs for security is broken. Auth is what protects resources; UUIDs are a distraction if you conflate the two.

Generating UUIDs in code

Every modern language has this solved. Don't write your own.

Node.js and the browser

// v4 — built into Node 14.17+ and all modern browsers
const id = crypto.randomUUID();
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"

// v7 — use the uuid package (v11+)
import { v7 as uuidv7 } from "uuid";
const id7 = uuidv7();

crypto.randomUUID() is the standard across the JS ecosystem. On the browser side it's available in all evergreen browsers since 2021. On Node it's been there since 14.17 (May 2021) and stabilized by 19. For v7, the uuid package added support in version 11 and handles the per-millisecond monotonicity correctly.

Python

import uuid

# v4 — standard library
uid = uuid.uuid4()
# UUID('f47ac10b-58cc-4372-a567-0e02b2c3d479')

# v7 — use uuid_utils until Python 3.14 adds uuid7 natively
from uuid_utils import uuid7
uid7 = uuid7()

Python 3.14 (release scheduled for October 2025) adds uuid.uuid7() to the stdlib. Until then, uuid_utils or uuid6-python are the established libraries.

Go

import "github.com/google/uuid"

// v4
id := uuid.New()

// v7
id7, err := uuid.NewV7()

The google/uuid package added v7 support in late 2024. It's the de facto standard in the Go ecosystem.

Rust

use uuid::Uuid;

// v4
let id = Uuid::new_v4();

// v7 — requires the "v7" feature in Cargo.toml
let id7 = Uuid::now_v7();

If you just need a few right now

Skip the code entirely. Our UUID generator produces single or bulk UUIDs in v1, v4, and v7 directly in your browser, with format options for uppercase, hyphens, braces, and URN. For seeding test data, generating idempotency keys, or just grabbing one to paste into a curl command, that's faster than cracking open an editor.

Security notes

A few things worth spelling out because they're easy to get wrong:

v4 is fine for session tokens. 122 bits of cryptographic entropy is massively more than you need for unguessability. A 16-byte random token of any format (UUID, NanoID, raw hex) provides the same security if generated from crypto.getRandomValues or equivalent. Don't pad session tokens with extra randomness "for safety"; 122 bits is already past the point of diminishing returns.

v1 and v6 leak information. The MAC address portion (in strict RFC 4122 v1 without node randomization) identifies the generating machine. The timestamp reveals when the ID was created, which matters if you're ordering by token creation and an attacker can enumerate. V7 also leaks creation time — usually harmless, but if creation-time inference is a concern for your threat model, v4 is the answer.

v3 and v5 are not password hashes. Both use broken hash algorithms (MD5 and SHA-1) and both truncate their output. Do not use them to derive secrets, authentication tokens, or anything that attackers might try to reverse. For password hashing, use bcrypt or argon2 via our password strength checker and related tools — those are purpose-built for the adversarial context.

UUIDs in URLs do not provide security. Making an ID unguessable is not the same as authorizing a request. If your app leaks data because an attacker incremented an ID, switching to UUIDs only raises the bar slightly — the real fix is proper access control. Every route that returns resource data should verify the requesting user is allowed to see it, regardless of whether the ID is a number or a UUID.

FAQ

What's the difference between UUID and GUID?

Same thing, different name. GUID is Microsoft's term, used across .NET, SQL Server, and Windows APIs. UUID is the IETF standard name used basically everywhere else. The 128-bit format is identical. The only practical difference is that SQL Server stores GUIDs in mixed-endian byte order, so the string representation of the same 16 bytes differs between SQL Server and most other systems — a cosmetic issue that sometimes surprises people debugging cross-system joins.

Can two UUIDs actually collide?

Mathematically yes; practically no. For UUIDv4, the 50% collision threshold is around 2.3 quintillion values. Generating a billion UUIDs per second, it takes about 73 years to reach that threshold. If you observe duplicate UUIDs in a real application, the explanation is always a broken random number generator — typically Math.random() used where crypto.getRandomValues() belongs — rather than real statistical collision.

Should I use v4 or v7 as a primary key?

V7 for anything heading into a primary key on a table you expect to grow. Time-ordering keeps B-tree indexes appending to the right side instead of fragmenting across random leaves. The performance difference is negligible at small scale and substantial at tens or hundreds of millions of rows. V4 is still fine for secondary columns, session tokens, and anything that isn't indexed on the clustered key.

How do I check if a UUID is a v7?

The first character of the third group (position 14 in the canonical string form) is the version digit. If it's 7, it's a v7. You can also parse the first 48 bits as a Unix millisecond timestamp; if the date makes sense, it's almost certainly v7 (or v6, which also front-loads time but is rare).

Is UUIDv7 the same as ULID?

Close but not identical. Both are 128 bits with a millisecond timestamp prefix and the rest random. ULID uses 48-bit timestamp + 80-bit random (more random bits than v7's 74). ULID uses Crockford base32 encoding instead of hex, giving you 26 case-insensitive alphanumeric characters. Functionally similar, but ULID predates the IETF standard, while v7 is now the formal RFC 9562 version.

Can I extract the timestamp from a UUIDv7?

Yes. Take the first 12 hexadecimal characters of the UUID, interpret as a hexadecimal number, and that's the Unix timestamp in milliseconds. Divide by 1000 for seconds, then convert to a human-readable date. It's a trivial operation and occasionally useful for debugging event ordering.

Does PostgreSQL support UUIDv7 natively?

As of PostgreSQL 18 (released late 2024), yes — the uuidv7() function is built in. Earlier versions (9.6 through 17) can use the pg_uuidv7 extension, or generate v7 values in application code and insert them as UUID typed columns. For new projects, upgrade to 18 if you can; for existing systems, the extension is a reasonable bridge.

Is UUIDv4 safe for use in session tokens?

Yes, provided your language's v4 generator uses cryptographic randomness. Node's crypto.randomUUID(), Python's uuid.uuid4(), Go's uuid.New(), and Rust's Uuid::new_v4() all do. 122 bits of random entropy is more than sufficient for session tokens — well beyond the threshold where brute-force becomes infeasible.

What's wrong with UUIDv1?

Two things. First, the node portion was originally the machine's MAC address, which leaks hardware identity. Second, if the node field isn't handled carefully in virtualized environments, the uniqueness guarantees can break. Modern implementations randomize the node field to fix both issues, but at that point v7 gives you time-ordering with better entropy and no legacy baggage. Use v7 for new work.

Should I migrate existing v4 primary keys to v7?

Usually no. The existing keys don't become "wrong" — they still work, they still uniquely identify rows. Migrating would require rewriting every foreign key, every cached reference, every log entry. The fragmentation damage on a v4 index is done; switching the default to v7 for new inserts stops the bleeding without the migration cost. Over time, the hot part of the index gets cleaner as it fills with v7 values.

Which UUID version should I use if I need deterministic IDs?

V5. Given the same namespace UUID and the same name string, v5 produces the same UUID every time. Useful for idempotency keys, content addressing, or cross-system joins without shared state. Avoid v3 — same idea but uses MD5, which is obsolete.

How big is a UUID in bytes?

128 bits = 16 bytes in binary form. As a string, the canonical form is 36 characters (32 hex digits plus 4 hyphens). Some storage formats strip the hyphens for 32 characters. Others add braces (common in .NET) for 38 characters.

Is there a "UUIDv9" or later coming?

Not formally. RFC 9562 defines v1 through v8, with v8 being the "custom" escape hatch. No work is publicly in progress on additional versions. If a new version is ever standardized, it will likely address a specific need that v7 doesn't cover.

How does UUIDv7 handle multiple UUIDs in the same millisecond?

RFC 9562 section 6.2 recommends — but doesn't mandate — a counter-based monotonicity scheme. Within a single millisecond, each new v7 UUID gets a counter value in the random_a bits, ensuring lexicographic ordering matches generation order. Mature libraries implement this correctly; rolling your own skips it and can produce out-of-order v7s under high throughput.

Can I use UUIDs in URLs?

Yes, and many apps do. UUIDs are URL-safe as long as you stick to the hyphen-separated hex form (all characters are URL-safe). The 36-character length is manageable for internal URLs but uglier than shorter alternatives like NanoID or Hashids if the URL is user-facing. For public URLs where aesthetics matter, consider NanoID (21 chars URL-safe) or a separate slug column.

One more thing

UUIDs solve a specific problem — generating unique identifiers without coordination — and the problem has eight different versions of solutions because different flavors of the problem need different solutions. For most new work in 2026:

  • Database primary keys: UUIDv7
  • Session tokens, API keys, trace IDs: UUIDv4 (or NanoID if you want shorter)
  • Deterministic IDs (content addressing, idempotency): UUIDv5
  • Everything else: UUIDv4

If your project genuinely doesn't need distribution, don't use UUIDs at all — BIGSERIAL is smaller, faster, and easier to debug. If you need URL-friendly public IDs, NanoID is worth a look. If you're building in a Java ecosystem where 64-bit IDs matter, TSID exists for you.

Our UUID generator produces v1, v4, and v7 in bulk with all the format options you'll actually want. If you're debugging something related to token formats and need to look inside a JWT along the way, our JWT decoder guide covers that territory. The underlying Unix timestamp converter handles the v7 timestamp extraction if you need to check when a token or ID was created.

RFC 9562 changed the default. Your next schema probably wants v7.

uuid v4 vs v7uuid v7 primary keywhich uuid versionuuid collision probabilityuuid vs uliduuid vs nanoiduuid v7 postgresuuid generatorrfc 9562uuid database primary keyhow to generate uuid v7uuid versions explained
Advertisement

Practice with free tools

200+ free developer tools that run in your browser.

Browse all tools →