Default to UUIDv4 for opaque random identifiers, UUIDv5 when you need the same input to always produce the same ID, and UUIDv7 for new time-sortable primary keys. UUIDv1 and v6 carry node and timestamp metadata worth avoiding in public-facing systems, while v8 exists purely for custom, documented payloads. All of this comes from RFC 9562, the current normative specification.
TL;DR:
- UUIDv7 is now the preferred option for new systems needing time-sortable primary keys, offering better indexing than random UUIDv4.
- Using UUIDv1 or v6 in public-facing systems risks exposing machine identifiers and timestamps, making them unsuitable for URLs or APIs.
- For deterministic identifiers, UUIDv5 with SHA-1 is recommended over v3's MD5 due to stronger hashing, especially in cross-service scenarios.
- Benchmarking with actual workload data is essential because index performance gains from using v7 depend heavily on database type and write volume.
- Validating UUID versions and variants entails checking specific bits and format, with library support like Python's uuid module simplifying this process.
Table of Contents
- What Is a UUID and How Is It Structured?
- Comparing UUID Versions 1 Through 8
- How Do You Choose the Right UUID Version for the Job?
- How Do You Detect and Validate a UUID's Version?
- How Does UUID Choice Affect Database Performance?
- What Privacy and Security Risks Come With UUIDs?
- How Do You Migrate Between UUID Versions Safely?
- How Pingfloat's Tools Help You Test UUID Choices
- My Take on Where UUID Adoption Is Headed
- Generate and Validate UUIDs Before You Commit to a Scheme
- Sources
- FAQ
What Is a UUID and How Is It Structured?
A UUID, or universally unique identifier, is a 128-bit value used to identify information without requiring a central authority to hand out numbers. It is almost always written as 32 hexadecimal digits split into five groups, in the pattern 8-4-4-4-12, such as f47ac10b-58cc-4372-a567-0e02b2c3d479. RFC 9562 formally defines this layout along with every version currently in use, from v1 through v8, plus special nil and max forms.

Two fields inside that 128-bit string matter more than the rest: the version and the variant. The version tells you which algorithm produced the UUID (time-based, random, name-based, and so on) and sits in a predictable spot in the text form. The variant is a separate field that identifies which layout rules the UUID follows overall, distinguishing RFC-compliant UUIDs from older Microsoft or NCS formats. It is easy to conflate the two, but they answer different questions: version asks "how was this made," variant asks "which rulebook does this follow."
RFC 9562, published in 2024, supersedes the older RFC 4122 and formally adds versions 6, 7, and 8 to the specification, alongside the original v1 through v5. That update is the reason UUID choice is suddenly a live question again: for over a decade, v4 (random) and v1 (time-based) were the only mainstream options for a general-purpose ID, and neither is well suited to being a sortable database key. A few structural facts are worth keeping in front of you as you read the rest of this article:
- A UUID is always 128 bits, encoded as 32 hex characters in five dash-separated groups.
- The version digit appears as the first character of the third group.
- The variant bits occupy the top bits of the fourth group and mark RFC-compliant UUIDs.
- RFC 9562 defines eight versions (v1 through v8), not all of which are implemented in every language runtime.
Once you have version and variant straight, the rest of this comparison is really about one question for each version: does it give you randomness, determinism, or time order, and what does that cost you?
Comparing UUID Versions 1 Through 8
Every UUID version answers the same underlying design problem (generate a value that is effectively unique without coordination) with a different trade-off between privacy, reproducibility, and sort order. Here is what each one actually does.
- v1 encodes a timestamp, a clock sequence, and a node identifier, and is time-ordered but leaks machine-level metadata.
- v3 and v5 are name-based and deterministic: same namespace plus same name always produces the same UUID.
- v4 is random or pseudorandom and is the default choice for opaque, non-sequential identifiers.
- v6 and v7 are the modern time-ordered options, with v7 the one RFC 9562 recommends for new systems.
- v8 is a blank canvas for application-specific layouts that still respect the UUID envelope.
UUIDv1: time-based, legacy-friendly, but leaky. UUIDv1 packs a 60-bit timestamp measured in 100-nanosecond intervals since the Gregorian calendar's adoption in 1582, a clock sequence to guard against clock rollback, and a 48-bit node identifier that was traditionally derived from a machine's MAC address, according to RFC 9562. That combination makes v1 UUIDs naturally time-ordered, which is genuinely useful for logs or anything you want to sort chronologically by creation. The catch is that a v1 UUID can reveal the generating machine's network interface and a precise creation timestamp to anyone who can parse it, which is a real privacy concern if these IDs ever reach a client, a URL, or a public API response. Pick v1 only when you control the full chain from generation to storage and genuinely need backward compatibility with older systems that already use it.
UUIDv2: DCE security, mostly theoretical. UUIDv2 is a DCE Security variant that embeds a local domain and identifier (commonly a POSIX UID or GID) in place of part of the timestamp. It exists for compatibility with older Distributed Computing Environment systems and is rarely implemented in modern standard libraries. Unless you are integrating with a legacy DCE-based system, there is no practical reason to reach for v2, and most runtimes, including Python's uuid module, do not expose a generator for it at all.
UUIDv3: name-based with MD5. UUIDv3 takes a namespace UUID and a name string, hashes them together with MD5, and produces a deterministic UUID: the same namespace and name always yield the same result. That determinism is the entire point. If you need to generate a stable ID for, say, a URL or an email address, without a lookup table, v3 gets you there without any randomness involved. The trade-off is that MD5 is a broken hash for cryptographic purposes (collision resistance is weak), so v3 is fine for deduplication and stable identifiers but a poor choice anywhere collision resistance actually matters.
UUIDv4: random, and the sensible default. UUIDv4 fills the bits (other than the fixed version and variant fields) with random or pseudorandom data, and it is the version most developers reach for by default, and for good reason: no metadata leakage, no coordination required, and no structure for an attacker to exploit. RFC 9562 recommends using a cryptographically secure pseudorandom number generator (CSPRNG) when the UUID will be security-sensitive or exposed externally, rather than a standard pseudorandom generator. In Python, uuid.uuid4() already draws from the operating system's CSPRNG, so the default call is safe out of the box. Use v4 for API resource IDs, session identifiers that are not themselves credentials, and any opaque primary key where you do not need sort order.
UUIDv5: name-based with SHA-1, the better v3. UUIDv5 does exactly what v3 does, deterministic generation from a namespace and a name, but uses SHA-1 instead of MD5. SHA-1 is also considered cryptographically weak today, but it is stronger than MD5 and is the version RFC 9562 recommends when you need name-based determinism. If you are choosing between v3 and v5 for a new system, choose v5; the only reason to still generate v3 is matching an existing dataset that already uses it. Both versions require a namespace UUID, either one of the standard namespaces defined in the RFC (DNS, URL, OID, X.500) or a custom one you generate once and reuse consistently.
UUIDv6: a sortable bridge for v1 systems. UUIDv6 rearranges the same fields as v1, timestamp, clock sequence, and node, into a different bit order so that the timestamp's most significant bits come first. That reordering means v6 UUIDs sort correctly as plain byte strings, which fixes the index locality problem v1 always had while staying close enough to v1's structure to ease migration. RFC 9562 frames v6 mainly as a compatibility option for systems already committed to v1 semantics, not as the first choice for something new.
UUIDv7: the recommended default for new time-sortable keys. UUIDv7 embeds a 48-bit Unix epoch timestamp in milliseconds in the most significant bits, followed by 74 bits of random data, giving you a UUID that sorts chronologically and still carries enough randomness to avoid collisions within the same millisecond; you can validate such epoch-ms values using a timestamp converter. RFC 9562 recommends v7 as the preferred choice for new systems that want UUIDs to behave well as database keys, because sequential-is values are far kinder to B-tree indexes than fully random ones. Some implementations add a monotonic counter inside the same millisecond to guarantee strict ordering, but that behavior is implementation-specific rather than something the standard mandates, so do not assume two different libraries will order colliding timestamps identically.
UUIDv8: bring your own layout. UUIDv8 reserves the UUID envelope (the fixed version and variant bits) and leaves everything else undefined, for applications that want a custom bit layout while remaining a technically valid UUID. RFC 9562 does not specify any universal semantics for v8: it is on you to document exactly what each bit or field means and validate that your generation and parsing code agree with that documentation. Use v8 only when v1 through v7 genuinely do not fit your requirements, and treat the documentation as a hard requirement, not an afterthought, since nothing about the wire format will tell a future maintainer what your custom fields mean.
How Do You Choose the Right UUID Version for the Job?
Most UUID decisions come down to answering one question first: does this identifier need to be opaque, deterministic, or sortable by creation time? Everything else follows from that.
- Start with semantics, not habit. If you just need a unique, unguessable value with no meaning attached, that is v4. If the same logical entity must always produce the same ID without a lookup, that is v5. If you want IDs that sort in creation order for database or log purposes, that is v7.
- Check for privacy exposure. If the ID will ever appear in a URL, API response, or client-side code, rule out v1 and v6 unless you have specifically evaluated and accepted the metadata they expose.
- Check index locality needs. If this is a primary key in a high-write relational database, v4's randomness will fragment your indexes: prefer v7, or accept the cost if other constraints force v4.
- Check reproducibility needs. If two services need to derive the same ID independently, v5 with an agreed namespace beats coordinating over a network call.
- Check runtime support. Confirm your language version actually implements the UUID version you want before committing, since v6, v7, and v8 are newer additions.
Pro Tip: When in doubt for a brand-new table's primary key, generate a few thousand v4 and v7 values with a UUID generator and eyeball how each set sorts. It makes the locality argument concrete in about two minutes.
For web APIs exposing resource identifiers to the public, v4 remains the safest default because it reveals nothing about when or where the resource was created. For database primary keys under write-heavy load, v7 is worth the runtime-support check because it keeps new rows clustered near the end of an index rather than scattered across it. For distributed tracing or anything that needs a reproducible ID computed independently by multiple services, v5 avoids a coordination step entirely.
How Do You Detect and Validate a UUID's Version?
Reading a UUID's version does not require a library: the version digit is the first character of the third hyphen-separated group, meaning it is the character immediately after the second hyphen. In f47ac10b-58cc-4372-a567-0e02b2c3d479, the third group is 4372, so the leading 4 tells you this is a UUIDv4. The variant bits live at the start of the fourth group and, for RFC-compliant UUIDs, the leading bits of that character are fixed to a specific pattern that distinguishes this layout from older, non-standard variants.
A solid validation pass checks three things: that the string matches the canonical 8-4-4-4-12 hex format, that the version digit is one of the values you expect to accept, and that the variant bits match the RFC-compliant pattern rather than a legacy Microsoft or NCS layout. A simple regular expression can check the format and version position at once, matching five hex groups of the right lengths with a literal version digit anchored in the third group, though a regex alone will not validate variant bits with full precision.
You rarely need to write this parsing yourself. Python's uuid module exposes a .version property on any UUID object, and Java's UUID class offers an equivalent version() method, both of which read the version field for you. One caveat worth knowing if you store UUIDs as raw binary rather than text: some systems reorder bytes differently for sorting or storage efficiency, so a byte-for-byte comparison between two UUIDs stored in different systems can fail even when the text representations would match. Always confirm byte order when comparing binary UUIDs across language boundaries or database drivers.
- Read the version digit at the first character of the third hyphenated group.
- Check the variant bits at the start of the fourth group to confirm RFC compliance.
- Use
uuid.versionin Python orUUID.version()in Java rather than hand-rolled parsing. - Confirm byte order explicitly before comparing binary-stored UUIDs across systems.
How Does UUID Choice Affect Database Performance?
The version you pick has a direct, measurable effect on how a database index behaves, because most relational databases store primary keys in a B-tree, and B-trees perform best when new values arrive in roughly ascending order. A fully random UUIDv4 inserted as a primary key lands in an unpredictable location in that tree every time, which fragments pages, bloats the index, and increases the working set the database has to keep in memory. RFC 9562 explicitly calls out that non-time-ordered UUIDs can produce poor index locality and recommends benchmarking with your own database and workload rather than assuming a fixed penalty.
UUIDv6 and UUIDv7 address this by keeping the timestamp bits at the front of the value, so consecutively generated UUIDs sort close to each other and new rows cluster at the end of the index rather than scattering across it. That behavior looks a lot like the locality you get from an auto-incrementing integer key, while still giving you a UUID's collision resistance and distributed-generation benefits. The actual magnitude of the improvement depends heavily on your database engine, page size, and write volume, which is exactly why RFC 9562 stops short of promising a universal number and points you toward benchmarking instead.
A few mitigations are worth knowing regardless of which version you land on:
- Prefer v7 over v4 for high-write primary keys where index bloat is a measured concern.
- If you must keep v4 for external-facing opacity, consider a separate internal sequential key for storage and indexing.
- Benchmark on your actual database engine and write pattern before committing, since gains vary by workload.
What Privacy and Security Risks Come With UUIDs?
UUIDs are identifiers, not secrets, and the biggest mistake teams make is treating an unguessable-looking value as if it were equivalent to an access token. A UUID, especially v4 or v7, being hard to guess does not mean it was designed or vetted as an authentication credential, and it should never be the only thing standing between a request and access to sensitive data.
Version choice adds a second, more specific risk: UUIDv1 embeds a node identifier historically derived from a MAC address, along with a high-resolution timestamp, directly in the value itself. If a v1 UUID ever appears in a public URL, API response, or exported dataset, anyone who parses it can potentially learn which machine generated it and exactly when, per RFC 9562. That is rarely a risk worth taking for anything client-facing, and it is one of the main reasons v4 or v7 is the safer public default.
Practical mitigations are straightforward:
- Use v4 or v7 for any identifier that will be exposed in a URL, API response, or client application.
- Never treat a UUID, regardless of version, as an authentication or authorization credential on its own.
- Scrub v1 or v6 UUIDs from logs and exports if they were ever used internally and might leak node or timing data.
- Rotate identifiers and review access patterns if you suspect a UUID scheme has been reverse-engineered or enumerated.
How Do You Migrate Between UUID Versions Safely?
Migrating an existing v1-based system is different from starting fresh, and the two paths deserve different plans. If you are stuck with v1 data and need better index behavior without a full rewrite, v6 is a field-compatible reorder, meaning the underlying timestamp and node data survive the move while sort order improves. If you are building something new, skip that bridge entirely and adopt v7 directly, since RFC 9562 recommends it for new systems and it avoids v1's node-identifier exposure from the start.
Before committing either way, confirm your language and runtime actually support the version you want. Python added uuid6(), uuid7(), and uuid8() helpers in version 3.14 of the uuid module, while intentionally leaving out a uuid2() generator, so older Python versions will need a third-party library for anything past v5. Java's UUID API supports version inspection natively but check your specific JDK version for v6 through v8 generation support before relying on it.
A safe rollout generally follows these steps:
- Confirm runtime and library support for the target version across every service that will generate or parse it.
- Dual-write old and new formats during a transition window and validate uniqueness across both.
- Benchmark index performance on a staging copy of production data before flipping primary key generation.
- Monitor for collisions or ordering anomalies for at least one full deployment cycle after rollout.
How Pingfloat's Tools Help You Test UUID Choices
Reading about v4 randomness or v7 ordering is one thing; seeing it is another. Pingfloat's UUID / GUID / ULID generator lets you produce batches of v4, v6, v7, and v8 values instantly, so you can eyeball sort order or randomness before writing a line of application code. The hash generator computes MD5 and SHA-1 output directly, useful for confirming how v3 and v5 derive their deterministic results from a namespace and name. If you are working with v7's embedded timestamp, the Unix timestamp converter converts between epoch milliseconds and human-readable dates, which helps confirm a generated UUID's timestamp decodes to what you expect.
None of this replaces reading RFC 9562 directly, but a few minutes of hands-on generation tends to make the version trade-offs click faster than prose alone.
My Take on Where UUID Adoption Is Headed
The RFC 9562 update finally gave developers a sortable UUID option without resorting to vendor-specific formats like ULID, and I think v7 will quietly become the default primary key choice for new systems over the next few years, the same way v4 became the default opaque ID a decade ago. The old advice, "just use v4 for everything," was never wrong exactly, it was just incomplete for anyone who cared about index performance.
My practical stance stays simple: v4 for opaque public identifiers, v5 for anything that needs to be deterministic across services, v7 for new sortable keys. I would treat v8 with real caution. A custom layout with no documentation is a liability the moment the person who built it moves to another team. If you adopt v8, write down the bit allocation somewhere a future engineer will actually find it. Generate a batch with a UUID generator before you commit to a scheme in production code, it costs nothing and settles arguments fast.
— Andreas
Generate and Validate UUIDs Before You Commit to a Scheme
Picking the wrong UUID version is the kind of mistake that does not show up until your primary key index has millions of fragmented rows, so testing before you commit matters more than it seems to at the time. Pingfloat's free tools let you do that testing without installing anything or creating an account.

The UUID / GUID / ULID generator produces v4, v6, v7, and v8 values on demand so you can compare randomness and sort behavior side by side, while the hash generator computes the MD5 and SHA-1 values behind v3 and v5 so you can verify a deterministic UUID by hand before trusting it in code. A few ways teams typically use them together:
- Generate a batch of v7 values and confirm they sort in creation order as expected.
- Compute an MD5 or SHA-1 hash manually to verify a v3 or v5 UUID matches your expected namespace and name combination.
- Cross-check a decoded v7 timestamp against the Unix timestamp converter before trusting it in a migration script.
Open the UUID generator and run your own comparison against the version you are about to standardize on, it takes less time than reading another comparison article.
Sources
For anyone implementing this rather than just reading about it, these are the primary references behind the guidance above:
- RFC 9562: Universally Unique IDentifiers (UUIDs)
- uuid — UUID objects according to RFC 9562 — Python 3.14.6 documentation
FAQ
What are the differences between UUID version 4 and 5?
UUIDv4 is randomly generated and produces a different value every time, making it the standard choice for opaque identifiers with no reproducibility requirement. UUIDv5 is name-based and deterministic, using SHA-1 to hash a namespace and a name so the same inputs always produce the same UUID, per RFC 9562.
What is UUID version 7?
UUIDv7 is a time-ordered UUID that embeds a 48-bit Unix epoch timestamp in milliseconds followed by random bits, so values generated later sort after values generated earlier. RFC 9562 recommends it as the preferred version for new systems that need UUIDs to behave well as database keys.
What are the key differences between UUID versions 7 and 8?
UUIDv7 has a fixed, standardized structure: a millisecond timestamp plus random bits, defined so any compliant implementation produces comparable, sortable output. UUIDv8 has no defined internal structure beyond the required version and variant bits, leaving the rest of the layout entirely up to the application, which means two different v8 implementations are not interchangeable unless they explicitly agree on a format.
How can I identify the version of a UUID?
The version is encoded as the first character of the third hyphen-separated group in a UUID's text form, for example the 4 in ...-4372-.... Most standard libraries expose this directly, such as Python's uuid.version property or Java's UUID.version() method, documented in the Python uuid module, so you rarely need to parse the string by hand.
