Genory
Development

UUID v4 or v7? A practical guide for application developers

Compare UUID v4 and v7, understand ordering limits and plan storage, fixtures and API integration.

An identifier is a design choice

A UUID can be a convenient way to assign an identifier without asking a central service for the next number. That convenience does not remove the need to choose how the identifier will be stored, compared and exposed. A database key, a browser-generated draft identifier and a public URL may all use the same textual format while serving different purposes. Start with the purpose, then choose the version and representation that fit it.

Genory's UUID generator supports version 4 and version 7. Both can be useful in development fixtures and prototypes, but they carry different information. Understanding that distinction helps you avoid relying on ordering that is not promised or treating an identifier as if it were an authentication secret.

The practical difference between v4 and v7

Version 4 uses random values apart from its required version and variant bits. Version 7 includes a Unix-millisecond timestamp followed by other bits used to distinguish values. The format is specified in RFC 9562. Genory's implementation uses cryptographic randomness for the remaining variable bits and does not promise monotonic ordering for values created within the same millisecond.

v4 uses random bits; v7 adds a timestamp. Both reserve version and variant bits.
v4 uses random bits; v7 adds a timestamp. Both reserve version and variant bits.

That last detail matters in tests. If you generate several v7 values quickly, do not assume their text order precisely captures their creation order. Values from different milliseconds have a time-oriented layout, but events can still arrive out of order in a distributed application. Store an explicit event timestamp or sequence when the business process depends on a reliable ordering rule.

Choose v4 for an uncomplicated random identifier

Version 4 is a straightforward option when an identifier should not encode creation time and the application does not need time-oriented ordering from the identifier itself. It is also useful for test fixtures that should not imply a chronological relationship. The version number is part of the format, so systems that validate UUIDs should check the versions they intend to accept rather than relying on a loose pattern of hexadecimal characters.

Use a unique constraint at the database layer even when collisions are extremely unlikely. A constraint also catches accidental reuse, faulty imports and application bugs. Random identifier generation does not prevent a developer from copying the same fixture into two records. Your system still needs a clear policy for duplicate values and a useful error when a duplicate is rejected.

Choose v7 when its timestamp is useful

Version 7 can be useful when time-oriented identifiers fit your storage or operational requirements. Evaluate that choice with your actual database and workload rather than assuming that a format alone guarantees a performance improvement. Index design, write patterns and query shape all affect the result. Benchmark a representative dataset before changing a mature production schema.

The timestamp also reveals approximate creation time. For many applications that is acceptable, but it is a property worth documenting when an identifier appears in a public URL. If you need an opaque token with a particular security purpose, choose a token mechanism designed for that purpose. Do not decide that an identifier is suitable merely because its text looks complicated.

Identifiers name records. Authorization determines who may access those records.

Decide on storage and display separately

The common UUID representation contains hexadecimal digits separated by hyphens. Your database may offer a native UUID type, a binary representation or a text column. Choose deliberately and keep conversion at clear boundaries. A native type can make validation and comparison more consistent, while text may be required by an external interface. Neither choice excuses inconsistent normalization between services.

Display formatting is another concern. Genory can show uppercase values or remove hyphens, but those controls change presentation rather than the underlying generated identifier. Confirm the receiving system's expected representation before copying a formatted value. In a test suite, compare normalized identifiers where representation is irrelevant and compare exact strings only where the formatting itself is under test.

Avoid fragile assumptions in fixtures

A fixture should explain why its identifier matters. If the test concerns a missing record, the specific UUID usually does not matter. If it concerns version acceptance, choose an explicit v4 or v7 fixture and name it accordingly. If it concerns ordering, construct timestamps deliberately instead of hoping that two calls happen far enough apart to produce a useful difference.

Do not use the current wall clock as the only source of expected ordering in unit tests. A controlled timestamp makes the scenario repeatable and removes a source of intermittent failure. Keep integration tests for the live generator separately, checking structure, version, variant and the response contract rather than comparing against one fixed random output.

Genory does not guarantee creation order within the same millisecond.
Genory does not guarantee creation order within the same millisecond.

Integrate generation through the API

For repeatable development workflows, the Genory API documentation explains request parameters and plan limits. A UUID request can specify the version and requested count. Keep error handling alongside the happy path: rate limits, exhausted quotas and malformed parameters are part of the interface contract, not exceptional situations to ignore until deployment.

When importing generated values, test the exact path used by the application. A spreadsheet may reinterpret other identifiers, and an import script may trim or transform strings unexpectedly. Validate after parsing rather than trusting that a visually correct value survived every intermediate tool. Logging a request identifier can help trace an import without logging unrelated personal data.

A migration needs more than a generator

If an existing application uses sequential integers, introducing UUIDs affects relationships, API schemas, fixtures and support workflows. Plan how old references remain resolvable and whether external clients can accept the new type. A generator can supply examples for testing that migration, but it does not decide the migration policy. Keep a compatibility period where the system design requires one.

Before finishing, review your assumptions with a small checklist: accepted versions, storage type, formatting rules, uniqueness enforcement, public exposure and ordering expectations. Then use fixtures to exercise each assumption. Choosing between v4 and v7 becomes much easier when the question is attached to a concrete workflow rather than a general preference for one identifier format over another.

Version Useful test
v4 Random identifiers round-trip unchanged
v7 Time ordering under your storage representation
Both Parsing and duplicate handling

Tools for this guide