Generate UUID v4 in Python β uuid.uuid4()
Use the free online UUID v4 Generator directly in your browser β no install required.
Try UUID v4 Generator Online βEvery time I need a collision-resistant identifier for a database row, API trace, or session token, the answer is generate UUID v4 in Python β one line, zero dependencies: uuid.uuid4(). Python's built-in uuid module uses os.urandom() for cryptographically secure randomness. For a quick UUID without writing code, the online UUID v4 generator works instantly. This guide covers the UUID object's attributes, bulk generation, JSON serialization, database storage, validation, uuid-utils (~10Γ faster Rust-backed drop-in), and the four most common mistakes β all with Python 3.8+.
- β
uuid.uuid4()is built into Python's stdlib βimport uuidis all you need, no pip install. - βThe return value is a
uuid.UUIDobject, not a string β usestr(),.hex, or.bytesto pick the representation that fits your storage layer. - βUUID v4 uses 122 random bits from
os.urandom()β cryptographically secure, no MAC address or timestamp exposure. - βFor high-throughput services,
pip install uuid-utilsis a drop-in replacement that is ~10x faster, powered by Rust. - βNever pass
uuid.uuid4(without parentheses) as a default argument directly in a dataclass or Pydantic model β it will share a single UUID across all instances.
What is UUID v4?
A UUID (Universally Unique Identifier) is a 128-bit label formatted as 32 hexadecimal digits split into five groups by hyphens: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Version 4 is the most widely used variant: 122 of those 128 bits are randomly generated, and the remaining 6 bits encode the version (4) and variant (RFC 4122). There is no timestamp and no host identifier β the identifier is entirely opaque and privacy-safe. The probability of two independently generated v4 UUIDs colliding is so small that for practical purposes it never happens, even across distributed systems generating millions of IDs per second.
event_id = "evt-" + str(random.randint(100000, 999999)) # fragile, not unique
event_id = str(uuid.uuid4()) # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e
uuid.uuid4() β The Standard Way to Generate UUID v4 in Python
The uuid module is part of Python's standard library. Calling uuid.uuid4() returns a uuid.UUID object with a full set of attributes for different representations. Converting to a string with str() produces the canonical hyphenated format that APIs, databases, and HTTP headers expect.
import uuid # Generate a UUID v4 request_id = uuid.uuid4() print(request_id) # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e print(type(request_id)) # <class 'uuid.UUID'> print(request_id.version) # 4 # Convert to string for JSON / HTTP headers print(str(request_id)) # "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" print(request_id.hex) # "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" (no dashes) print(request_id.bytes) # b';...' (16 raw bytes)
A common real-world pattern is attaching a UUID to every outbound API request so you can correlate logs across services. Here is a minimal requests session wrapper that injects a fresh UUID into every call:
import uuid
import requests
def call_api(endpoint: str, payload: dict) -> dict:
trace_id = str(uuid.uuid4())
headers = {
"X-Request-ID": trace_id,
"Content-Type": "application/json",
}
response = requests.post(endpoint, json=payload, headers=headers, timeout=10)
response.raise_for_status()
return {"trace_id": trace_id, "data": response.json()}
# result["trace_id"] lets you grep the exact request across all service logs
result = call_api("https://api.example.com/v1/orders", {"product_id": "prod_7x2k", "qty": 3})
print(result["trace_id"]) # e.g. "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"When generating UUIDs in bulk β for example, pre-populating a batch of database rows β a list comprehension is idiomatic and readable:
import uuid
# Pre-generate IDs for 1000 telemetry events
event_ids = [str(uuid.uuid4()) for _ in range(1000)]
print(f"Generated {len(event_ids)} unique IDs")
print(event_ids[0]) # e.g. "a1c2e3f4-..."
print(event_ids[-1]) # different value every timeNeed a quick UUID without running any code? Use the online UUID v4 generator to copy a fresh value in one click, or bulk-generate hundreds at once β useful for seeding test databases or populating fixture files.
uuid.uuid4() calls os.urandom(16) internally, then sets bits 6β7 of byte 8 to 10 (variant) and bits 12β15 of byte 6 to 0100 (version 4). The remaining 122 bits are random. This is why you cannot trust the version unless you parse with uuid.UUID().UUID Object Attributes and Representations
The uuid.UUID object exposes multiple representations of the same 128-bit value. Choosing the right one for your storage layer prevents silent data corruption and wasted bytes.
import uuid
u = uuid.uuid4()
print(str(u)) # "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" (36 chars)
print(u.hex) # "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" (32 chars, no dashes)
print(u.bytes) # b';Β...' (16 bytes, big-endian)
print(u.bytes_le) # b'ΒΒ...' (16 bytes, little-endian)
print(u.int) # 78823... (128-bit integer)
print(u.version) # 4
print(u.variant) # 'specified in RFC 4122'
# Round-trip: reconstruct from string
reconstructed = uuid.UUID("3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e")
print(reconstructed == u) # True (if u was that value)For PostgreSQL with psycopg2 or asyncpg, pass the UUID object directly β the driver handles the mapping to the native uuid column type. For SQLite, use str(u) (TEXT) or u.bytes (BLOB, 16 bytes vs 36 for the string). For storage efficiency at scale, .bytes is 55% smaller than the canonical string.
Validating and Parsing UUID v4 Strings in Python
Whenever a UUID arrives from user input, a URL path parameter, or an upstream API, you should validate it before using it as a database key. The idiomatic approach is to attempt construction with uuid.UUID() and catch ValueError. You can also enforce that the incoming value is specifically version 4 by checking .version.
import uuid
def parse_uuid4(raw: str) -> uuid.UUID:
"""
Parse and validate a UUID v4 string.
Raises ValueError for invalid format or wrong version.
"""
try:
u = uuid.UUID(raw)
except ValueError as exc:
raise ValueError(f"Invalid UUID format: {raw!r}") from exc
if u.version != 4:
raise ValueError(f"Expected UUID v4, got v{u.version}: {raw!r}")
return u
# Usage in a FastAPI / Flask route handler
def get_order(order_id: str):
try:
uid = parse_uuid4(order_id)
except ValueError as exc:
return {"error": str(exc)}, 400
# safe to use uid in a DB query now
return {"order_id": str(uid), "status": "processing"}uuid.UUID() accepts strings with or without hyphens, and also accepts the urn:uuid: prefix. So "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" (no dashes) and "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" both parse to the same object.UUID v4 in JSON Payloads and API Responses
The JSON standard has no UUID type β a UUID in JSON is always a string. That means you must convert the uuid.UUID object to a string before passing it to json.dumps(). The cleanest approach is a custom JSONEncoder subclass so you never have to scatter str() calls through your codebase.
import json
import uuid
from datetime import datetime
class ApiEncoder(json.JSONEncoder):
"""Serialize UUID and datetime objects in JSON responses."""
def default(self, obj):
if isinstance(obj, uuid.UUID):
return str(obj)
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
# Realistic API response with nested UUIDs
api_response = {
"request_id": uuid.uuid4(),
"created_at": datetime(2026, 3, 14, 9, 41, 0),
"order": {
"id": uuid.uuid4(),
"customer_id": uuid.uuid4(),
"items": [
{"product_id": uuid.uuid4(), "sku": "NVX-9000", "qty": 2},
],
},
}
print(json.dumps(api_response, indent=2, cls=ApiEncoder))
# {
# "request_id": "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e",
# "created_at": "2026-03-14T09:41:00",
# "order": {
# "id": "a1c2e3f4-...",
# ...
# }
# }For a one-off serialization call, the default= hook is simpler than subclassing:
import json, uuid
event_id = uuid.uuid4()
payload = {"event_id": event_id, "action": "checkout"}
# Pass a callable; called only for types json can't handle
json_str = json.dumps(payload, default=str)
print(json_str) # {"event_id": "3b1f8a9d-...", "action": "checkout"}When receiving a response from an external API, parse UUID strings back to objects so your code gets the full attribute set and type safety:
import json
import uuid
import requests
def fetch_shipment(shipment_id: str) -> dict:
"""Fetch a shipment and return with typed UUID fields."""
response = requests.get(
f"https://api.logistics.example.com/v2/shipments/{shipment_id}",
headers={"Accept": "application/json"},
timeout=10,
)
response.raise_for_status()
data = response.json()
# Parse the UUID fields back to uuid.UUID objects
try:
data["id"] = uuid.UUID(data["id"])
data["carrier_id"] = uuid.UUID(data["carrier_id"])
except (KeyError, ValueError) as exc:
raise RuntimeError(f"Malformed shipment response: {exc}") from exc
return dataTo update UUID fields in a JSON file on disk β for example, rotating a correlation ID in a config or seed file β read, mutate, and write back atomically:
import json, uuid
def rotate_correlation_id(path: str) -> str:
"""Replace or add 'correlation_id' in a JSON file. Returns the new UUID."""
try:
with open(path) as f:
data = json.load(f)
except FileNotFoundError:
data = {}
except json.JSONDecodeError as exc:
raise ValueError(f"Invalid JSON in {path!r}: {exc}") from exc
new_id = str(uuid.uuid4())
data["correlation_id"] = new_id
with open(path, "w") as f:
json.dump(data, f, indent=2)
return new_idIf you don't want to run a script every time you need to inspect a UUID from an API response, paste it directly into the UUID Decoder β it shows you the version, variant, and all fields without any code.
Generate UUID v4 from the Command Line with Python
Python's uuid module does not expose a standalone CLI subcommand like python -m json.tool, but a one-liner covers the same use case. These are useful in shell scripts, CI pipelines, and whenever you need a throwaway identifier without opening a REPL.
# Single UUID v4 python3 -c "import uuid; print(uuid.uuid4())" # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e # No-dashes (hex) format β useful for filenames and env vars python3 -c "import uuid; print(uuid.uuid4().hex)" # 3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e # Generate 5 UUIDs for a batch seed script python3 -c "import uuid; [print(uuid.uuid4()) for _ in range(5)]" # Use in a shell variable DEPLOY_ID=$(python3 -c "import uuid; print(uuid.uuid4())") echo "Deploying with ID: $DEPLOY_ID"
uuidgen (a C utility) produces UUID v4 values and is faster for pure shell scripts. Use the Python one-liner when you are already in a Python-centric environment and want consistency with how UUIDs are generated in your application code.High-Performance UUID v4 with uuid-utils
The standard library's uuid.uuid4() is fast enough for most applications β at a few microseconds per call it handles thousands of IDs per second comfortably. If you are generating UUIDs on the hot path of a high-throughput service (bulk inserts, per-event telemetry at scale, or request ID generation under heavy load), uuid-utils is a drop-in replacement backed by Rust that benchmarks at roughly 10x the speed of the stdlib.
pip install uuid-utils
# uuid_utils is a drop-in replacement for the stdlib uuid module import uuid_utils as uuid # Same API as stdlib request_id = uuid.uuid4() print(request_id) # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e print(str(request_id)) # canonical string print(request_id.hex) # no-dashes hex print(request_id.version) # 4 # Also supports v7 (time-ordered, great for DB primary keys) time_ordered_id = uuid.uuid7() print(time_ordered_id) # starts with current-timestamp prefix
isinstance(u, uuid.UUID) checks from the standard library, use the compat mode: import uuid_utils.compat as uuid. Compat mode is slightly slower than default but still faster than stdlib.UUID v4 in Dataclasses and Pydantic Models
Python dataclasses and Pydantic models both support UUID fields natively. The key pattern when using UUID as an auto-generated default is to pass the function reference, not a call result β otherwise every instance shares the same UUID.
from dataclasses import dataclass, field
import uuid
@dataclass
class WorkerJob:
job_id: uuid.UUID = field(default_factory=uuid.uuid4)
worker_id: str = "worker-01"
payload: dict = field(default_factory=dict)
job1 = WorkerJob(payload={"task": "resize_image", "src": "uploads/img_4932.png"})
job2 = WorkerJob(payload={"task": "send_email", "to": "ops@example.com"})
print(job1.job_id) # unique per instance
print(job2.job_id) # different from job1.job_id
print(job1.job_id == job2.job_id) # Falsefrom pydantic import BaseModel, Field
import uuid
class OrderEvent(BaseModel):
event_id: uuid.UUID = Field(default_factory=uuid.uuid4)
order_id: uuid.UUID
status: str
amount_cents: int
event = OrderEvent(
order_id=uuid.UUID("a1c2e3f4-5b6d-4e7f-8c9d-0a1b2c3d4e5f"),
status="payment_confirmed",
amount_cents=4995,
)
print(event.model_dump_json(indent=2))
# {
# "event_id": "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e",
# "order_id": "a1c2e3f4-5b6d-4e7f-8c9d-0a1b2c3d4e5f",
# "status": "payment_confirmed",
# "amount_cents": 4995
# }
# Pydantic v2 serializes uuid.UUID as string automaticallyCommon Mistakes When Generating UUID v4 in Python
I've seen all four of these patterns appear in code reviews and production incidents β they are easy to miss because they do not raise an error immediately.
Problem: Passing uuid.uuid4 (the function object) as a default value in a dataclass or model without wrapping it in default_factory β Python evaluates the default once at class definition time, so every instance shares the same UUID.
Fix: Use default_factory=uuid.uuid4 in dataclasses or Field(default_factory=uuid.uuid4) in Pydantic so a fresh UUID is generated per instance.
@dataclass
class Session:
# WRONG: evaluated once, all instances share this UUID
session_id: uuid.UUID = uuid.uuid4()@dataclass
class Session:
# CORRECT: factory called per instance
session_id: uuid.UUID = field(default_factory=uuid.uuid4)Problem: uuid.UUID objects do not equal plain strings, so session_id == '3b1f8a9d-...' always returns False even when the value matches β silently breaking lookups.
Fix: Always compare UUID to UUID: wrap the string with uuid.UUID() before comparing, or convert both sides to str().
# Returns False even when values match
if record["session_id"] == "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e":
revoke_session(record)target = uuid.UUID("3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e")
if record["session_id"] == target: # both are uuid.UUID
revoke_session(record)
# Or normalize everything to strings at the boundary:
if str(record["session_id"]) == str(target):
revoke_session(record)Problem: uuid_obj.hex produces a 32-character string without hyphens. If downstream code expects the canonical 36-character dashed format (most APIs and databases do), it will reject or silently misparse the value.
Fix: Use str(uuid_obj) for the canonical 36-character format unless you have an explicit requirement for the compact hex form.
# Stores "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" β no dashes
payload = {"correlation_id": request_id.hex}# Stores "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" β standard format
payload = {"correlation_id": str(request_id)}Problem: random.random() is not cryptographically secure, and secrets.token_hex(16) produces a 32-character hex string that is not a valid UUID β downstream validators that call uuid.UUID() on it will raise ValueError.
Fix: Use uuid.uuid4() whenever the receiving system expects a UUID-formatted identifier. Use secrets.token_hex() only when you explicitly need a random token that is not UUID-shaped.
import random, secrets # Not a UUID β will fail uuid.UUID() validation request_id = secrets.token_hex(16) # "a1b2c3d4e5f6..." session_id = str(random.random()) # "0.8273..." β not even close
import uuid request_id = str(uuid.uuid4()) # "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" # Valid UUID v4, cryptographically secure
UUID Generation Methods in Python β Quick Comparison
All methods below produce 128-bit identifiers but differ in entropy source, privacy characteristics, and whether they require a third-party install.
Use uuid.uuid4() for general-purpose unique identifiers in web applications, distributed systems, and database primary keys when sortability is not required. Use uuid.uuid5() (or v3) for deterministic IDs derived from a known namespace and name β for example, generating a stable ID for a canonical URL. Switch to uuid_utils.uuid7() when you need time-ordered IDs for database indexes (avoids page splits in B-tree indexes at high insert rates). Reach for uuid_utils.uuid4() when raw generation throughput is the bottleneck.
UUID v4 vs UUID v7 β Which Should You Use?
The most common practical question is whether to use UUID v4 or the newer UUID v7 for database primary keys. Here is the short answer: use UUID v4 by default; switch to UUID v7 only when index fragmentation is a measured problem.
UUID v4 values are fully random, which means inserts land at random positions in a B-tree index. At moderate insert rates (hundreds to low thousands per second) this is fine β the index fits in the buffer pool and random writes are cheap. At very high insert rates, random placement causes frequent page splits and cache misses, increasing write amplification and slowing queries.
UUID v7 embeds a millisecond-precision Unix timestamp in the most-significant bits, so rows inserted close together in time also land close together in the index. This gives B-tree indexes (PostgreSQL, MySQL, SQLite) behaviour closer to an auto-increment integer: new rows always append to the end of the index, eliminating page splits. The trade-off is that UUID v7 encodes a timestamp, which leaks creation time β avoid it for user-facing IDs where creation time is sensitive.
In Python, UUID v7 is not in the standard library yet (as of Python 3.12). Generate it with pip install uuid-utils and call uuid_utils.uuid7(). It returns an object with the same attribute set as uuid.UUID, so migration from v4 is a one-line change in the ID factory.
For a one-click alternative without any Python setup, paste your UUID string into the UUID v4 generator and validator β it generates, validates, and decodes all fields in the browser.
Frequently Asked Questions
How do I generate a UUID v4 in Python?
Call uuid.uuid4() from Python's built-in uuid module. It returns a UUID object β convert to string with str() when you need a text representation. The module ships with the standard library so no pip install is needed.
import uuid session_id = uuid.uuid4() print(session_id) # e.g. 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e print(str(session_id)) # same canonical string print(session_id.hex) # 3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e (no dashes)
What is the difference between uuid.uuid4() and str(uuid.uuid4())?
uuid.uuid4() returns a UUID object that has attributes like .hex, .bytes, .int, and .version. str(uuid.uuid4()) converts that object to a 36-character canonical string immediately, discarding the object. Keep the object if you need multiple representations; convert to string at the boundary where you pass the value to a JSON payload, database, or HTTP header.
import uuid u = uuid.uuid4() print(type(u)) # <class 'uuid.UUID'> print(u.version) # 4 print(u.hex) # 32-char hex, no dashes print(u.bytes) # 16-byte binary print(str(u)) # canonical 36-char string with dashes
Is uuid.uuid4() cryptographically secure?
Yes. Python's uuid.uuid4() uses os.urandom() internally, which reads from the operating system's cryptographically secure random number generator (/dev/urandom on Linux/macOS, CryptGenRandom on Windows). The 122 random bits make collision probability negligible for any realistic workload. Do not confuse it with random.random(), which is not cryptographically secure.
import uuid, os # uuid4 internally calls os.urandom(16) raw = os.urandom(16) # uuid4 sets the version and variant bits before returning u = uuid.UUID(bytes=raw, version=4) print(u) # valid v4 UUID from raw random bytes
How do I validate that a string is a valid UUID v4 in Python?
Parse with uuid.UUID() and check the .version attribute. If the string is not a valid UUID, uuid.UUID() raises a ValueError β catch it to handle invalid input. This also validates that the format (dashes, length) is correct.
import uuid
def is_valid_uuid4(value: str) -> bool:
try:
u = uuid.UUID(value)
return u.version == 4
except ValueError:
return False
print(is_valid_uuid4("3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e")) # True
print(is_valid_uuid4("not-a-uuid")) # False
print(is_valid_uuid4("3b1f8a9d-2c4e-1f6a-8b0d-5e7c9f1a3d2e")) # False (v1, not v4)How do I store UUIDs in a PostgreSQL or SQLite database from Python?
With PostgreSQL (via psycopg2 or asyncpg), pass the UUID object directly β the driver adapts it to the native UUID type. With SQLite, which has no native UUID type, store as TEXT using str(uuid_obj) or as BLOB using uuid_obj.bytes. SQLAlchemy has a UUID column type that handles this automatically across dialects.
import uuid
import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE events (id TEXT PRIMARY KEY, name TEXT)")
event_id = uuid.uuid4()
conn.execute("INSERT INTO events VALUES (?, ?)", (str(event_id), "user_signup"))
conn.commit()
row = conn.execute("SELECT * FROM events").fetchone()
# Reconstruct UUID object from stored string
retrieved_id = uuid.UUID(row[0])
print(retrieved_id.version) # 4Can I generate multiple UUIDs at once in Python?
Yes β use a list comprehension or a generator. Each call to uuid.uuid4() is independent and guaranteed to produce a distinct value. For bulk generation where throughput matters, uuid-utils (Rust-backed) is around 10x faster than the standard library.
import uuid
# Generate 5 unique trace IDs for a batch request
trace_ids = [str(uuid.uuid4()) for _ in range(5)]
for tid in trace_ids:
print(tid)
# Each line is a distinct UUID v4Related Tools
- βUUID v4 Generator β Generate UUID v4 values instantly in the browser β no Python environment needed. Copy a single value or bulk-generate hundreds at once.
- βUUID v7 Generator β Generate time-ordered UUID v7 values β sortable by creation time, ideal for database primary keys where index fragmentation matters.
- βUUID Decoder β Inspect any UUID β version, variant, timestamp (v1/v7), and node fields β without writing a parser from scratch.
- βJWT Decoder β Decode and inspect JWT tokens, which often carry UUID subject claims (sub) or jti identifiers alongside session UUIDs.
Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.
Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.