Last Updated:
Hibernate @SequenceGenerator AllocationSize: Why Default Behavior Causes Database Sequence Inconsistency (vs JPA Specification)
In Java Persistence API (JPA) and Hibernate, generating unique primary keys efficiently is critical for application performance and data integrity. One common approach is using database sequences with the @SequenceGenerator annotation. While JPA provides a standard specification for sequence-based ID generation, Hibernate's default handling of the allocationSize attribute often leads to subtle but dangerous inconsistencies with database sequences.
This blog dives deep into the root cause of these inconsistencies, contrasts Hibernate's behavior with the JPA specification, and provides actionable solutions to align your application with best practices. Whether you're building a monolithic app or a distributed system, understanding this nuance will save you from hard-to-debug duplicate ID errors, data corruption, or performance bottlenecks.
Note: This article references the JPA specification, which has evolved into Jakarta Persistence (currently at version 3.2 for Hibernate 7.x and version 4.0 for Hibernate 8.0). The core concepts described here apply across JPA 2.x and Jakarta Persistence 3.x/4.x.
Table of Contents#
- Understanding @SequenceGenerator in JPA
- Hibernate's Default AllocationSize Behavior
- The Inconsistency: Hibernate vs. JPA Specification
- Real-World Scenarios Where This Matters
- How to Fix: Aligning Hibernate with JPA
- Best Practices for Sequence Generation
- Conclusion
- References
Understanding @SequenceGenerator in JPA#
What is @SequenceGenerator?#
@SequenceGenerator is a JPA annotation used to define a database sequence generator for generating primary key values. It works in tandem with @GeneratedValue(strategy = GenerationType.SEQUENCE) to specify how the sequence is accessed and used.
Key Attributes#
| Attribute | Purpose | JPA Default Value |
|---|---|---|
name | Unique name for the generator (referenced by @GeneratedValue). | N/A (required) |
sequenceName | Name of the database sequence object. | hibernate_sequence (Hibernate default) |
allocationSize | Number of sequence values to "pre-allocate" per database call. | 50 |
initialValue | Starting value of the sequence. | 1 |
How It's Supposed to Work (JPA Intent)#
The allocationSize attribute dictates how many primary key values Hibernate will "reserve" in memory after a single call to the database sequence. For example:
If allocationSize = 50, Hibernate will:
- Call the database sequence once to get the next value (e.g.,
1). - Generate IDs
1, 2, ..., 50without further database calls. - When the reserved batch is exhausted, call the sequence again to get the next value (e.g.,
51), and generate51, 52, ..., 100, and so on.
This reduces round-trips to the database, improving performance—especially in high-throughput systems.
Hibernate's Pooled Optimizer#
When allocationSize is greater than 1, Hibernate uses a pooled optimizer internally to manage ID allocation. The pooled optimizer interprets the value returned by the database sequence as a "high-value" marker—the upper bound of the current ID pool. For example, if the sequence returns 51 and allocationSize = 50, Hibernate understands that IDs 1–50 are available for use.
This optimizer is what enables the performance benefits of batch allocation, but it also means that the database sequence must increment by allocationSize for the optimizer to work correctly.
Hibernate's Default AllocationSize Behavior#
Hibernate, as a JPA implementation, is supposed to adhere to the JPA specification. However, its default handling of @SequenceGenerator can lead to inconsistencies if not configured carefully. Here's why:
The Default AllocationSize is 50 (Per JPA)#
The JPA specification explicitly states that the default allocationSize is 50. Hibernate respects this default when @SequenceGenerator is used. For example:
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
@SequenceGenerator(name = "product_seq", sequenceName = "PRODUCT_SEQ") // allocationSize defaults to 50
private Long id;
// ...
}Here, allocationSize is implicitly 50.
Hibernate 5 vs. Hibernate 6+ Default Behavior#
An important change occurred between Hibernate versions:
-
Hibernate 5: Used a single global sequence (
hibernate_sequence) by default for all entities, withINCREMENT BY 1. This meant all entities shared the same sequence, and the defaultallocationSize = 50was often mismatched with the database sequence. -
Hibernate 6 and later: Each entity now gets its own sequence by default (e.g.,
product_SEQ,order_SEQ), and the sequence is created withINCREMENT BY 50to match the defaultallocationSize. This change eliminates the mismatch for new applications but requires migration attention for existing ones.
The Critical Oversight: Database Sequence Increment#
The JPA specification requires that the database sequence's INCREMENT BY value matches allocationSize. For example, if allocationSize = 50, the sequence must be defined with INCREMENT BY 50 to ensure Hibernate's reserved batches don't overlap or conflict.
But here's the problem: Most developers create database sequences with the default INCREMENT BY 1 (e.g., in PostgreSQL, Oracle, or MySQL 8+). For example:
-- Common but problematic sequence definition
CREATE SEQUENCE PRODUCT_SEQ
START WITH 1
INCREMENT BY 1; -- Default increment is 1When Hibernate uses allocationSize = 50 with a sequence that increments by 1, a dangerous mismatch occurs.
The Inconsistency: Hibernate vs. JPA Specification#
The Root Cause: Mismatched Increment and AllocationSize#
JPA requires alignment between allocationSize and the sequence's INCREMENT BY value. When they don't match, Hibernate's batch allocation logic breaks. Let's walk through an example with allocationSize = 50 (default) and sequence INCREMENT BY 1:
Step 1: Hibernate Starts Up#
- Hibernate calls
PRODUCT_SEQ.NEXTVALand gets1. - It reserves IDs
1–50for future use.
Step 2: Hibernate Exhausts the Batch#
- After assigning IDs
1–50, Hibernate needs more IDs. - It calls
PRODUCT_SEQ.NEXTVALagain, which returns2(since the sequence increments by1). - Hibernate now reserves IDs
2–51.
The Problem: Overlapping IDs#
Hibernate now tries to assign 2, which was already allocated in the first batch (1–50). This leads to duplicate primary key errors—a critical data integrity issue.
If multiple application instances are running, the problem worsens: Both instances might call NEXTVAL and get 1, then both reserve 1–50, leading to massive duplicate ID conflicts.
Why This Violates JPA#
The JPA specification clarifies that allocationSize is the "number of sequence values to allocate on each trip to the database." For this to work safely, the sequence must increment by allocationSize to avoid overlapping ranges. Hibernate's default behavior (using 50) is compliant with JPA, but user error in sequence configuration (using INCREMENT BY 1) causes the inconsistency.
Real-World Scenarios Where This Matters#
1. Distributed Systems (Multiple App Instances)#
In microservices or clustered environments, multiple app instances will:
- Each call the sequence and reserve overlapping ranges (e.g.,
1–50). - Assign duplicate IDs when saving entities, leading to constraint violations or silent data corruption.
2. Application Restarts#
If the app restarts before exhausting a reserved batch (e.g., reserved 1–50 but only used 1–10), the unassigned IDs (11–50) are lost. On restart, Hibernate calls NEXTVAL again, gets 2, and reserves 2–51, creating gaps (11–50) and potential overlaps.
3. Manual Sequence Adjustments#
If a DBA manually increments the sequence (e.g., to fix a gap), Hibernate's cached range may conflict with the new sequence value, leading to duplicates.
4. Migration from Hibernate 5 to Hibernate 6+#
When migrating from Hibernate 5 to Hibernate 6 or later, applications that relied on the default global hibernate_sequence may encounter issues because Hibernate 6 expects entity-specific sequences. If the database sequences aren't updated to match the new default INCREMENT BY 50, the same mismatch problem can occur.
How to Fix: Aligning Hibernate with JPA#
The solution is simple: Explicitly set allocationSize and ensure the database sequence's INCREMENT BY matches it. Here's how:
Option 1: Use Efficient Batch Allocation (Recommended for Performance)#
Set allocationSize = N and configure the sequence to INCREMENT BY N.
Step 1: Define the Sequence with Matching Increment#
-- Align sequence increment with allocationSize
CREATE SEQUENCE PRODUCT_SEQ
START WITH 1
INCREMENT BY 50; -- Matches allocationSizeStep 2: Explicitly Set allocationSize in @SequenceGenerator#
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
@SequenceGenerator(
name = "product_seq",
sequenceName = "PRODUCT_SEQ",
allocationSize = 50, // Explicitly match sequence increment
initialValue = 1
)
private Long id;
// ...
}Now:
- Hibernate calls
NEXTVALand gets1, reserves1–50. - Next call returns
51(sequence increments by50), reserves51–100. - No overlaps or duplicates—even in distributed systems.
Option 2: Disable Batching (For Strict No-Gap Requirements)#
If your use case requires no gaps (e.g., financial transactions), set allocationSize = 1 and sequence INCREMENT BY 1:
CREATE SEQUENCE PRODUCT_SEQ
START WITH 1
INCREMENT BY 1;@SequenceGenerator(
name = "product_seq",
sequenceName = "PRODUCT_SEQ",
allocationSize = 1 // No batching; one sequence call per ID
)Tradeoff: This increases database round-trips, reducing performance in high-throughput scenarios.
Best Practices#
-
Never Rely on Defaults
Always explicitly setallocationSizein@SequenceGeneratorto avoid ambiguity. -
Align Sequence Increment with AllocationSize
Use database migration tools (Flyway, Liquibase) to enforce this alignment in scripts. -
Document the Sequence Configuration
Note theallocationSizeand sequenceINCREMENT BYvalue in code comments or database docs. -
Test in Distributed Environments
Simulate multiple app instances to catch duplicate ID issues early. -
Avoid Manual Sequence Edits
Use automated tools to adjust sequences if gaps occur, and coordinate with application restarts. -
Plan for Hibernate Version Migrations
When upgrading from Hibernate 5 to Hibernate 6+, review and update your sequence configurations to align with the new default behavior (entity-specific sequences withINCREMENT BY 50).
Conclusion#
Hibernate's default @SequenceGenerator behavior (using allocationSize = 50) is compliant with the JPA specification, but inconsistencies arise when the database sequence's INCREMENT BY value isn't aligned with allocationSize. By explicitly configuring allocationSize and matching the sequence increment, you ensure safe, efficient ID generation—even in distributed systems.
Remember: Alignment between allocationSize and sequence increment is not optional for JPA/Hibernate sequence generation.