Identifiers appear in users, orders, files, events, messages, audit records, and distributed systems. A numeric sequence works inside one database, but it requires coordination when several services create data at the same time. The Python uuid module provides immutable 128-bit objects that can be generated without a central authority and transported as text, bytes, or integers.
This guide explains when to use UUIDv4, UUIDv5, UUIDv6, and UUIDv7, why UUIDv1 can expose information, how to validate strings, store values in databases, sort identifiers, and avoid confusing uniqueness with security. It complements our guides about Python lists, collections, copying objects, time zones, and temporary files.
What a UUID is
UUID stands for Universally Unique Identifier. The format contains 128 bits and is commonly displayed as 32 hexadecimal digits separated by hyphens:
550e8400-e29b-41d4-a716-446655440000The goal is decentralized generation with an extremely low collision probability. It is not an absolute mathematical guarantee for every version and implementation, but it is sufficient for most systems.
Generating UUIDv4
For a general random identifier, uuid4() is the most common choice.
import uuid
identifier = uuid.uuid4()
print(identifier)
print(type(identifier))
print(identifier.version)The returned value is a UUID object, not merely a string. Python generates UUIDv4 values with cryptographically secure randomness, reducing collisions and avoiding direct exposure of time or network-address data.
String, bytes, integer, and URN forms
A UUID instance exposes several useful representations.
import uuid
value = uuid.uuid4()
print(str(value))
print(value.hex)
print(value.bytes)
print(value.int)
print(value.urn)str() produces the standard hyphenated format. hex returns 32 characters without hyphens. bytes uses 16 bytes. int exposes the complete 128-bit integer. urn produces a value such as urn:uuid:....
Validating input with UUID()
When an API or command line receives an identifier, convert it before using it.
from uuid import UUID
def parse_uuid(text: str) -> UUID:
try:
return UUID(text)
except (ValueError, AttributeError, TypeError) as error:
raise ValueError("Invalid UUID") from errorThe constructor accepts hyphenated strings, compact hexadecimal text, braces, and URN prefixes. After conversion, comparisons and version checks are consistent.
UUIDv1 and privacy
uuid1() combines time, a clock sequence, and a node identifier. Historically, the node can be derived from the machine’s MAC address.
import uuid
value = uuid.uuid1()
print(value)
print(value.node)
print(value.time)The official uuid documentation warns that UUIDv1 may compromise privacy by including the network address. Avoid it in new systems unless compatibility requires its semantics.
UUIDv3 and UUIDv5: deterministic IDs
UUIDv3 and UUIDv5 return the same identifier for the same namespace and name. Version 3 uses MD5; version 5 uses SHA-1 and is generally preferred between the two.
import uuid
url_id = uuid.uuid5(
uuid.NAMESPACE_URL,
"https://example.com/products/42",
)
print(url_id)This behavior is useful for migrations, idempotent imports, URL-derived keys, and synchronization between systems. Names must be canonicalized: case, trailing slashes, Unicode normalization, and encoding differences produce different UUIDs.
Predefined namespaces
The module includes four well-known namespaces:
NAMESPACE_DNSfor domain names;NAMESPACE_URLfor URLs;NAMESPACE_OIDfor ISO OIDs;NAMESPACE_X500for X.500 names.
An organization can also generate and preserve its own namespace UUID. Treat it as part of the data contract because changing it changes every derived identifier.
UUIDv6: reordered time fields
UUIDv6 rearranges UUIDv1 time fields to improve database index locality. It was added to Python in version 3.14.
import uuid
value = uuid.uuid6()
print(value)
print(value.version)It is most useful for systems that already depend on UUIDv1 semantics and need a more database-friendly byte order. New applications without v1 compatibility should usually evaluate UUIDv7.
UUIDv7: Unix time and sorting
UUIDv7 embeds a Unix timestamp in milliseconds in the most significant bits and combines the remaining space with randomness and monotonicity mechanisms.
import datetime as dt
import uuid
value = uuid.uuid7()
created_at = dt.datetime.fromtimestamp(
value.time / 1000,
tz=dt.timezone.utc,
)
print(value)
print(created_at)Values generated in sequence tend to sort chronologically, improving B-tree locality compared with UUIDv4. This does not turn the identifier into a reliable audit timestamp; keep an explicit datetime column.
Monotonicity within one millisecond
An application can generate many UUIDv7 values during the same millisecond. Python’s implementation uses a counter to preserve monotonicity where the platform lacks finer precision.
Useful ordering is not a gapless sequence. Different processes, restarts, and clock adjustments may require additional business rules. Use explicit sequence numbers when the domain needs strict total order.
UUIDv8: custom formats
UUIDv8 reserves fields for experimental or vendor-specific layouts.
import uuid
value = uuid.uuid8(
0x12345678,
0x9ABC,
0x11223344,
)
print(value)Arguments have bit limits, and excess bits are truncated. By default, components are not generated with a cryptographically secure random generator. UUIDv8 is therefore not a replacement for UUIDv4 in security-sensitive contexts.
NIL and MAX UUIDs
Python 3.14 also provides special values:
import uuid
print(uuid.NIL)
print(uuid.MAX)NIL has every bit set to zero and can represent “no UUID” in protocols that require 128 bits. MAX has every bit set to one and can serve as an upper sentinel. In databases, prefer a real NULL when absence belongs to the model.
Comparison and sorting
UUID objects compare through their integer value.
values = [uuid.uuid7() for _ in range(5)]
ordered = sorted(values)
assert ordered == valuesComparing a UUID with an unrelated type raises TypeError. Normalize incoming values before sorting or using them as dictionary keys.
Database storage options
Common representations include:
- a native UUID database type;
- 16 binary bytes;
- 36-character hyphenated text;
- 32-character hexadecimal text.
The native type usually provides validation and useful operators. Binary storage saves space but requires careful byte-order handling and may be harder to inspect. Text is convenient but larger.
UUIDs as primary keys
UUIDs allow clients and services to create records before contacting a central database. They also avoid directly revealing record counts, unlike sequential integers.
UUIDv4 causes random index insertion and can increase fragmentation. UUIDv7 improves locality but remains larger than a 64-bit integer. Benchmark the trade-off with your database, row count, and query pattern.
A UUID is not authorization
A hard-to-guess identifier does not replace access control. Even a UUIDv4 is only the name of a resource.
# wrong: grant access because the UUID exists
# correct: verify user, tenant, role, and ownershipAPIs must enforce authentication and authorization for every operation. Do not use a UUID as a password, session token, or cryptographic secret.
Collisions and unique constraints
The collision probability of UUIDv4 is extremely low, but the database should still enforce a primary key or UNIQUE constraint. The generator reduces probability; the constraint protects integrity.
If a conflict occurs, generate another value and retry in a controlled transaction. Never remove uniqueness enforcement because “UUIDs cannot collide.”
Deterministic IDs and idempotency
UUIDv5 works well when data has a stable natural key.
def customer_id(system: str, code: str) -> uuid.UUID:
name = f"{system.strip().lower()}:{code.strip()}"
return uuid.uuid5(uuid.NAMESPACE_URL, name)Canonicalization must be documented and tested. When a future version changes the rule, version the namespace or include a rule version in the name.
JSON serialization
The standard json module does not serialize UUID objects automatically. Convert them to strings at the boundary.
import json
import uuid
record = {"id": str(uuid.uuid4()), "name": "Ana"}
text = json.dumps(record)
print(text)Parse the string back with UUID(). Web frameworks and ORM serializers often support UUID fields directly.
Command-line usage
Since Python 3.12, the module can run as a script. Python 3.14 added versions 6, 7, and 8 plus generation of multiple values.
python -m uuid
python -m uuid -u uuid7
python -m uuid -C 10
python -m uuid -u uuid5 -n @url -N https://example.comThis is useful in tests, migrations, and deployment scripts without writing a separate program.
RFC 9562 and compatibility
RFC 9562, published in 2024, replaced RFC 4122. It retains traditional versions and standardizes UUIDv6, UUIDv7, UUIDv8, NIL, and MAX.
Older code may still display the constant RFC_4122 for compatibility. The name remains, while the current layout is described by the newer RFC.
Common mistakes
- Using UUIDv1 without considering privacy.
- Choosing UUIDv4 and expecting time ordering.
- Using UUIDv7 as the only audit timestamp.
- Treating a UUID as an access token.
- Storing unvalidated text.
- Removing database uniqueness constraints.
- Using UUIDv5 without canonicalizing the name.
- Adopting UUIDv8 without documenting the format.
Best practices
- Use UUIDv4 for general random identifiers.
- Use UUIDv5 for deterministic name-based IDs.
- Consider UUIDv7 for databases that benefit from time ordering.
- Avoid UUIDv1 in new systems because of privacy concerns.
- Use native UUID database types when possible.
- Keep uniqueness constraints.
- Separate identity from authorization.
- Test parsing, versions, round trips, and ordering.
Conclusion
The Python uuid module provides different identifier families for different contracts. UUIDv4 gives secure randomness, UUIDv5 produces deterministic values, UUIDv6 improves the time layout of legacy UUIDv1, and UUIDv7 combines Unix time with database-friendly locality. UUIDv8 remains a space for custom formats.
The right choice depends on generation, ordering, reproducibility, and privacy requirements. With validation, an appropriate database type, unique constraints, and independent authorization, UUIDs let distributed systems create identifiers without central coordination or global sequences.







