Introduction
Every enterprise application needs a way to uniquely identify its data.
For many years, relational databases relied on auto-generated numeric identifiers:
1
2
3
4
5
These identifiers work well for monolithic applications with a single database.
However, modern enterprise architectures look very different.
Today’s systems are built using:
- Microservices
- Event-driven architectures
- Kafka
- Cloud-native deployments
- Multi-region databases
- Offline synchronization
- Mobile applications
- Distributed processing
In these environments, generating sequential numeric identifiers across multiple services becomes increasingly difficult.
To solve this problem, Java applications frequently use Universally Unique Identifiers (UUIDs).
But introducing UUIDs raises important architectural questions:
- Should UUID replace
Longas the primary key? - Which UUID version should be used?
- Should Oracle store UUIDs as
RAW(16)orVARCHAR2(36)? - What is the performance impact on indexes?
- Should REST APIs expose UUIDs?
- How does Hibernate map UUID fields?
- Can UUIDs be generated without a database?
This article answers these questions and provides practical recommendations for enterprise applications.
Learning Objectives
By the end of this article, you will be able to:
- Understand what a UUID is.
- Compare UUIDs with numeric identifiers.
- Choose the appropriate storage strategy.
- Map UUIDs in JPA and Hibernate.
- Design REST APIs using UUIDs.
- Understand performance implications.
- Apply UUIDs effectively in distributed systems.
What Is a UUID?
A UUID is a 128-bit globally unique identifier.
Example:
550e8400-e29b-41d4-a716-446655440000
Unlike auto-incrementing numbers, UUIDs can be generated independently without coordinating with a central database.
Why UUIDs?
Imagine three independent microservices:
Order Service
Payment Service
Shipping Service
If each service generates:
ID = 1001
those identifiers are only unique within their own database.
A UUID avoids collisions because every service can generate identifiers independently.
Generating a UUID
UUID id = UUID.randomUUID();
System.out.println(id);
Sample output:
9f1c2f2c-5d86-4d8f-8d2b-7d7bde34f5d3
Every invocation produces a practically unique value.
UUID Versions
The UUID specification defines several versions.
| Version | Purpose | Typical Usage |
|---|---|---|
| Version 1 | Timestamp + MAC address | Legacy systems |
| Version 3 | Name-based (MD5) | Deterministic identifiers |
| Version 4 | Random | Most Java applications |
| Version 5 | Name-based (SHA-1) | Stable identifiers |
The Java standard library’s UUID.randomUUID() generates Version 4 UUIDs.
UUID vs Long
| Aspect | Long | UUID |
|---|---|---|
| Size | 8 bytes | 16 bytes |
| Human readability | Better | Poor |
| Sequential | Yes | Usually random |
| Distributed generation | No | Yes |
| Guessable | Yes | No |
| Database index size | Smaller | Larger |
| Merge data from multiple systems | Difficult | Easy |
When Should You Use Long?
Numeric identifiers are often appropriate when:
- A single database owns the data.
- Insert performance is critical.
- Human readability is important.
- The application is a monolith.
Example:
@Id
@GeneratedValue
private Long id;
When Should You Use UUID?
UUIDs are recommended when:
- Building microservices.
- Synchronizing offline data.
- Integrating multiple systems.
- Publishing identifiers in APIs.
- Replicating data across regions.
Example:
@Id
private UUID id;
Oracle Storage Options
Oracle offers several ways to store UUIDs.
Option 1 – VARCHAR2(36)
Stores the textual representation.
Example:
550e8400-e29b-41d4-a716-446655440000
Advantages:
- Easy to read.
- Simple debugging.
Disadvantages:
- Larger storage.
- Bigger indexes.
- Slower comparisons.
Option 2 – CHAR(36)
Similar to VARCHAR2(36) but fixed length.
Useful when all values have identical length.
Option 3 – RAW(16)
Stores the binary value directly.
Advantages:
- 16 bytes instead of 36 characters.
- Smaller indexes.
- Better performance.
- Reduced storage.
For Oracle, this is generally the preferred option.
Storage Comparison
| Storage Type | Approximate Size |
|---|---|
VARCHAR2(36) | 36 characters |
CHAR(36) | 36 characters |
RAW(16) | 16 bytes |
For large tables, the storage difference can significantly affect index size and cache efficiency.
Hibernate Mapping
Modern Hibernate versions provide native UUID support.
Example:
@Entity
public class Customer {
@Id
private UUID id;
}
The exact database mapping depends on the configured dialect and database capabilities.
When using Oracle, verify whether the generated schema matches your preferred storage strategy (RAW(16) versus character-based columns).
Generating UUIDs
Application-generated UUIDs:
UUID.randomUUID()
Advantages:
- No database round trip.
- Independent of database sequences.
- Suitable for distributed systems.
UUIDs in REST APIs
Example response:
{
"id": "9f1c2f2c-5d86-4d8f-8d2b-7d7bde34f5d3"
}
Benefits:
- Stable external identifier.
- Harder to guess than sequential IDs.
- Easier to merge data from multiple services.
Remember that UUIDs improve identifier unpredictability but are not a substitute for authentication or authorization.
Performance Considerations
Random UUIDs can lead to:
- Fragmented indexes.
- Increased page splits.
- Larger index structures.
For high-write workloads, monitor index performance and evaluate database-specific strategies if sequential insertion order is important.
UUIDs in Event-Driven Systems
Kafka event:
{
"eventId": "9f1c2f2c-5d86-4d8f-8d2b-7d7bde34f5d3"
}
Using UUIDs simplifies:
- Event tracing.
- Correlation across services.
- Distributed logging.
Migration Strategy
Suppose an existing application uses:
private Long id;
A gradual migration strategy may involve introducing an additional UUID field for external references while retaining numeric keys internally during the transition.
The exact approach depends on compatibility requirements and downstream systems.
Common Mistakes
Storing UUIDs as Strings Without Need
If the database supports an efficient binary representation, prefer it over text storage for persistence.
Assuming UUIDs Are Always Faster
UUIDs provide distributed uniqueness, not better insert performance.
They typically require more storage and larger indexes than numeric identifiers.
Exposing Sequential Numeric IDs Publicly
Sequential IDs make resource enumeration easier.
Consider whether exposing UUIDs provides a better API design for your use case.
Best Practices
✔ Use UUIDs when independent ID generation is required.
✔ Prefer binary storage (such as RAW(16) in Oracle) when supported and appropriate.
✔ Generate UUIDs in the application layer for distributed systems.
✔ Validate Hibernate mappings against your database schema.
✔ Separate internal persistence concerns from external API contracts where necessary.
Interview Questions
Why are UUIDs popular in microservices?
Because they can be generated independently without requiring coordination with a central database.
What are the trade-offs of UUIDs compared to numeric identifiers?
UUIDs improve global uniqueness and decentralization but consume more storage and can increase index fragmentation.
Which Oracle datatype is commonly preferred for UUIDs?
RAW(16) is commonly preferred because it stores the binary value compactly.
Should every application replace numeric IDs with UUIDs?
No. The choice depends on architecture, performance requirements, integration needs, and operational constraints.
Summary
UUIDs solve an important challenge in distributed systems by allowing globally unique identifiers to be generated independently of the database. They are well suited to microservices, event-driven architectures, and multi-region deployments.
However, UUIDs are not automatically the best choice for every application. They introduce trade-offs in storage, indexing, and performance that should be evaluated alongside business and architectural requirements.
Choosing between numeric identifiers and UUIDs is an architectural decision, not simply a coding preference.
Coming Up Next
Part 19 – Optional: Eliminating NullPointerException the Right Way
We’ll return to one of Java 8’s most influential language features and explore how Optional helps model the absence of a value, improves API design, integrates with Streams, and avoids common null-handling mistakes in enterprise applications.