Part 18: UUID Best Practices – Java, Oracle, Hibernate, Microservices & Performance

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 Long as the primary key?
  • Which UUID version should be used?
  • Should Oracle store UUIDs as RAW(16) or VARCHAR2(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.

VersionPurposeTypical Usage
Version 1Timestamp + MAC addressLegacy systems
Version 3Name-based (MD5)Deterministic identifiers
Version 4RandomMost Java applications
Version 5Name-based (SHA-1)Stable identifiers

The Java standard library’s UUID.randomUUID() generates Version 4 UUIDs.


UUID vs Long

AspectLongUUID
Size8 bytes16 bytes
Human readabilityBetterPoor
SequentialYesUsually random
Distributed generationNoYes
GuessableYesNo
Database index sizeSmallerLarger
Merge data from multiple systemsDifficultEasy

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 TypeApproximate 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.

Leave a Reply

Your email address will not be published. Required fields are marked *