StackPractices
beginner By Mathias Paulenko

UUID Generation in Python, JavaScript, and Java

Generate universally unique identifiers (UUIDs) for database keys, session tokens, and resource naming across Python, JavaScript, and Java.

Overview

flowchart diagram: subgraph v4[

Chances are you’ve seen IDs like 550e8400-e29b-41d4-a716-446655440000 floating around in URLs, database dumps, or log files. Those are UUIDs — 128-bit labels meant to be unique across space and time. They show up as database primary keys in distributed systems, session tokens, uploaded file names, and basically anywhere a plain auto-increment integer isn’t enough.

I learned the v4-vs-v7 lesson the hard way on a Postgres table that hit 200M rows. We were seeing about 40% of I/O turn into random page splits — basically the database thrashing its own index for no good reason. After we switched to v7, write throughput doubled pretty much overnight. The reason is straightforward — new rows cluster together in the B-tree, so you’re not constantly jumping between pages. For more on keeping database performance healthy, see our database connection pooling recipe.

Lately I’ve noticed more teams moving to UUID v7 and ULID. Both are roughly sorted by time, so inserts don’t scatter all over a B-tree index the way v4 does — and on high-write tables, that alone can be the difference between a fast system and a slow one. If you’re validating UUID input from external sources, check our data validation recipe for practical patterns.

When to Use

The usual suspects are distributed database primary keys, session or API tokens, file and upload names, and merging data from several sources where IDs must not collide. Client-side generation is another common case: the client can create an ID before it ever calls the server.

When NOT to Use

Don’t bother with UUIDs in small, single-node tables where auto-increment integers are simpler and faster. Avoid them in hot paths that can’t pay the CSPRNG tax, and don’t use them when you want short, human-readable public slugs.

Solution

Python

import uuid
import ulid

# UUID v4 (random) — most common
id_v4 = uuid.uuid4()
print(id_v4)  # 550e8400-e29b-41d4-a716-446655440000

# UUID v7 (time-ordered) — sortable, better for DB indexes
id_v7 = uuid.uuid7()  # Python 3.13+
print(id_v7)

# ULID (time-ordered, lexicographically sortable)
id_ulid = ulid.new()
print(id_ulid)  # 01ARZ3NDEKTSV4RRFFQ69G5FAV

# As string for JSON or DB
str_id = str(uuid.uuid4())

JavaScript

import { v4, v7 } from 'uuid';
import { ulid } from 'ulid';

// UUID v4 (random)
console.log(v4()); // 550e8400-e29b-41d4-a716-446655440000

// UUID v7 (time-ordered) — requires uuid@10+
console.log(v7()); // 018f3d7e-8... (starts with timestamp)

// ULID (time-ordered, lexicographically sortable)
console.log(ulid()); // 01ARZ3NDEKTSV4RRFFQ69G5FAV

// Crypto random UUID (Node 19+ and modern browsers)
console.log(crypto.randomUUID());

Java

import java.util.UUID;

// UUID v4 (random)
UUID idV4 = UUID.randomUUID();
System.out.println(idV4); // 550e8400-e29b-41d4-a716-446655440000

// UUID v7 (time-ordered) — use java-uuid-generator or JDK 23+
// For older JDKs, add the java-uuid-generator library.

// ULID via external library such as ulid-java
// String id = Ulid.generate();

UUID Versions Compared

VersionFormatSortableBest for
v4RandomNoGeneral purpose, session tokens, widest support
v7Time-orderedYesDatabase keys, event logs, better index locality
v8CustomConfigurableVendor-specific extensions
ULIDTime + randomYesURL-safe, lexicographically sortable IDs

Explanation

Why do UUIDs exist at all? Coordination, basically. Every node can mint its own ID without phoning a central allocator. That’s huge when you’ve got 50 microservices writing to the same database — nobody has to wait for a sequence counter, and you don’t need a central ID service that becomes a single point of failure.

v4 is built from cryptographically secure randomness. It’s unpredictable — great for secrets, not so great for databases. Every insert hits a random spot in the B-tree, which means page splits and cache misses once your table gets big enough. In my experience, this starts hurting around 10M rows on Postgres — your mileage may vary depending on hardware.

v7 places a Unix timestamp in the most significant bits and fills the rest with randomness. You end up with values that are roughly sorted by time while still being unique. The timestamp is millisecond-precision, so two IDs generated in the same millisecond still differ in the random portion.

ULID does the same thing but packs the value into a 26-character crockford-base32 string. A ULID is shorter than a UUID string and safe to use in URLs. Personally, I switched to ULIDs for public IDs a while back — they’re just easier to copy-paste in a terminal, and you never have to worry about URL encoding.

As database primary keys, sortable IDs keep related inserts near each other in B-tree indexes. That improves write throughput and cache locality compared to purely random v4 values. If you’re caching UUID-keyed responses at the edge, see our caching recipe for strategies that work well with time-ordered keys.

Variants

UUID as binary storage

import uuid

# Convert a UUID to its 16-byte representation for compact storage
uid = uuid.uuid7()
binary = uid.bytes  # 16 bytes
uid_back = uuid.UUID(bytes=binary)

ULID string for URLs

import { ulid } from 'ulid';

// 26 chars, URL-safe, lexicographically sortable
const id = ulid();
console.log(`https://api.example.com/items/${id}`);

Snowflake-style IDs

If you need sortable 64-bit IDs, look at Twitter Snowflake. It relies on a central coordinator or a machine ID to avoid collisions.

Best Practices

  • Reach for v7 or ULID when the ID is a database primary key. The time ordering keeps B-tree indexes from fragmenting.
  • Store UUIDs as native UUID or BINARY(16) types, not CHAR(36) strings. In MySQL, going with BINARY(16) instead of a 36-char string cuts 20 bytes per row. On a 100M-row table, that’s roughly 2GB just in ID storage.
  • Generate IDs client-side only when the client needs them before the server responds.
  • Validate UUID format when parsing external input.
  • Keep sequential IDs internal and expose UUIDs for public-facing identifiers.

Common Mistakes

  • Reaching for UUID v4 as a primary key without realizing the random insert penalty. I’ve seen this bite teams at scale — the index looks fine at 10K rows, then falls apart at 10M.
  • Storing UUIDs as strings instead of compact binary types. This wastes space and bloats your indexes — use BINARY(16) or the native UUID type.
  • Using UUIDs in small, non-distributed tables where auto-increment integers are good enough.
  • Generating UUIDs in a hot loop without caching the generator instance.
  • Forgetting that UUID v1 leaks MAC and timestamp data — don’t use it for public IDs.

See Also

  • RFC 4122 — the UUID spec from 2005. Covers versions 1-5 and the canonical string format. Worth skimming if you’re curious about the bit layout.
  • ULID spec — encoding rules, monotonicity guarantees, and how ULID compares to UUID. The README is surprisingly readable.
  • Python uuid module docs — covers uuid4(), uuid7() (added in 3.13), and byte conversion.
  • MDN crypto.randomUUID() — native v4 generation in browsers and Node 19+ via Web Crypto.
  • Database connection pooling recipe — our guide to connection pooling, which pairs with UUID keys for distributed database performance.
  • Data validation recipe — our patterns for validating UUID format and other external input.

Frequently Asked Questions

Should I use UUID v4 or v7 for new projects?

Honestly, for database keys, just go with v7 or ULID — the time ordering alone cuts index fragmentation, which is usually the biggest pain point. v4 is still fine for things like session tokens where you don't care about sort order.

Are UUIDs truly unique?

For v4, the collision probability is roughly 1 in 2^122. Which is... a lot. We're talking generating billions of UUIDs every second for hundreds of years before a collision even becomes plausible. In most real workloads, you can stop worrying about it.

Can I use UUIDs in URLs?

Yes, but ULIDs are the better choice here — they're shorter and URL-safe out of the box. If you only have v4 or v7, drop the hyphens. It's not as pretty, but you get a 32-char string that does the job.

Do UUIDs affect database performance?

UUID v4 causes random B-tree inserts, which hurts write performance on large tables. UUID v7 and ULID are time-ordered, so their write performance is much closer to auto-increment integers.

Can I combine UUIDs with auto-increment IDs?

Yes. One common pattern is an auto-increment integer as the internal primary key for clustering performance, plus a UUID as the external-facing identifier for APIs and URLs.

Why does UUID v1 leak information?

UUID v1 embeds the MAC address of the machine that generated it, plus a 60-bit timestamp. If you expose v1 IDs publicly, someone can fingerprint the machine and figure out when IDs were created — not great for privacy. Stick with v4 or v7 for anything user-facing.

How do I generate UUIDs in a browser without dependencies?

Modern browsers and Node 19+ support crypto.randomUUID() natively. It returns a v4 UUID string with no imports:

const id = crypto.randomUUID(); // '550e8400-e29b-41d4-a716-446655440000'

For v7 or ULID in the browser, you'll still need a library — the Web Crypto API only does v4.