skip to content

What does the allocationSize attribute of JPA's @SequenceGenerator control in Hibernate, and what goes wrong if it disagrees with the INCREMENT BY of the underlying database sequence?

level: middleimportance: must knowfreq 45%

answer

  1. allocationSize default = 50
  2. Sequence must be increment by N
  3. Mismatch -> overlapping blocks -> duplicate key
  4. increment_size_mismatch_strategy: EXCEPTION/LOG/FIX
  5. Blocks are lost on restart -> gaps

basics

~20 s

allocationSize is how many ids Hibernate reserves per sequence call and hands out from memory — default 50. If the database sequence increments by 1 while Hibernate assumes 50, the reserved block overlaps values a later call returns, producing duplicate primary keys.

solid answer

~50 s

With `allocationSize = N` Hibernate calls `nextval` once and then serves the next N ids from memory, cutting sequence round trips by a factor of N. To make that safe, the database sequence must be declared `increment by N`: each `nextval` then carves out a block that no other caller can receive, which is what keeps multiple application instances from colliding. If the sequence is `increment by 1` but the mapping says 50, Hibernate treats one returned value as ownership of a 50-wide block while the database will hand the very same values to the next caller — duplicate key violations, usually intermittently and under load. Hibernate can detect the mismatch at boot when it can read the sequence metadata (`hibernate.id.sequence.increment_size_mismatch_strategy` = EXCEPTION/LOG/FIX). The default is 50, which surprises people migrating from `allocationSize = 1`. Larger blocks mean fewer round trips and bigger gaps after a restart; ids were never gapless anyway.

code

java · 7 lines
java
@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_gen")
    @SequenceGenerator(name = "order_gen", sequenceName = "order_seq", allocationSize = 50)
    private Long id;
}

go deeper

for a junior

Know that allocationSize is how many ids Hibernate grabs per sequence call, and that the sequence must increment by the same number.

for a middle

Explain the block arithmetic, the duplicate-key failure mode on mismatch, and the default of 50.

for a senior

Add the boot-time mismatch strategy, gaps and non-monotonic ids across nodes, rolling-deploy hazards when changing the value, and when allocationSize = 1 is required for interop.

for a principal

Weigh round-trip savings against id-space behaviour and operational risk, and treat the migration script and the mapping as a single artifact that must be changed together.

## What the number means `@SequenceGenerator(name = "...", sequenceName = "order_seq", allocationSize = 50)` says: *each time I call the sequence, I am claiming 50 identifiers*. Hibernate calls `nextval` once, computes the range it owns, and assigns ids from memory until the block is exhausted, then calls again. This is the hi/lo idea: the database round trip is amortised over many rows, which matters because id allocation otherwise happens once per `persist()`. The JPA default for `allocationSize` is **50**, not 1. Hibernate honours it, which is why an entity mapped with a bare `@GeneratedValue(strategy = SEQUENCE, generator = "g")` and a hand-written `create sequence g` (increment 1 by default in most engines) is a mismatch waiting to happen. ## Why the sequence must increment by the same amount Safety comes from the database, not from Hibernate. When the sequence is `increment by 50`, successive `nextval` calls return 50, 100, 150 — values 50 apart — so whoever gets 100 can safely use the 50 values that no other call can produce. Sequences are atomic and cluster-wide, so two application nodes, or two threads, get disjoint blocks with no coordination. If the sequence increments by 1, `nextval` returns 50, then 51. Node A believes it owns a 50-wide block starting from 50; node B gets 51 and believes it owns a block from 51. The ranges overlap and the second insert into that range fails with a unique/primary-key violation. The bug is load-dependent and looks random, which makes it expensive to diagnose — hence Hibernate's boot-time check. ## The boot-time check When Hibernate can read the sequence's metadata from the JDBC catalog, it compares the declared increment with `allocationSize`. `hibernate.id.sequence.increment_size_mismatch_strategy` controls the response: - `EXCEPTION` — fail startup on mismatch (the safe setting). - `LOG` — warn and continue. - `FIX` — silently adapt the in-memory allocation size to the database's increment. `FIX` is convenient and dangerous as a habit: it hides a schema/mapping divergence that a different environment might not have. Treat the migration script and the mapping as one artifact that must agree. ## Consequences of a large block **Gaps.** Blocks are per JVM and are lost on shutdown, redeploy or crash — a node that used 3 of its 50 ids leaves 47 unused forever. Rolled-back transactions also consume values, since sequences are non-transactional. Generated surrogate keys were never gapless; if a business requires contiguous numbering, that is a separate, deliberately serialised counter and a different column. **Non-monotonic ids across nodes.** With blocks, node A may be handing out 1050–1099 while node B is on 1100–1149, so id order does not reflect insertion order. Do not use "higher id = created later" as a cross-node guarantee, and be careful with keyset pagination that assumes it. **Bigger jumps if you resize.** Changing `allocationSize` means changing the sequence's `increment by` in the same migration, and thinking about nodes still running the old value during a rolling deploy — mixed increments are exactly the overlap scenario above. The safe path is a maintenance window, or a new sequence. ## Choosing a value 50 is a sensible default. Bigger (100–500) helps genuine bulk-insert workloads where sequence round trips are measurable; beyond that the gain flattens while gaps and non-monotonicity grow. `allocationSize = 1` means one `nextval` per row — Hibernate uses a no-op optimizer, ids are dense and monotonic per sequence, and it is the correct choice when another application or hand-written SQL also draws ids from the same sequence and expects strict single-stepping. And note the scope of the trick: it only applies to SEQUENCE (and TABLE) generation. `IDENTITY` has no allocation size — every insert asks the database — which is one more reason sequences win on high-volume insert paths.

  • Two application instances share the same sequence with allocationSize = 50. How do they avoid handing out the same id?
    They never coordinate directly; the database does it. Each `nextval` is atomic and advances the sequence by 50, so every call returns a value 50 apart from every other, and each caller owns the block below (or above) its returned value. Blocks are therefore disjoint by construction — provided the sequence's INCREMENT BY really is 50.
  • Is it acceptable that a restart wastes the remainder of an allocated block?
    Yes for surrogate keys. Generated ids are already non-contiguous because sequences are non-transactional and rollbacks consume values, so no correctness property depends on density — only the id column's range does, and a bigint has room to spare. Requirements for gapless numbers (invoices, legal documents) must be met by a separate serialised counter, not the primary key.

saying these in an interview costs you the question

  • Assuming allocationSize defaults to 1
  • Creating the sequence with increment by 1 while mapping allocationSize 50
  • Thinking Hibernate coordinates blocks between JVMs itself
  • Expecting generated ids to be gapless or globally ordered by insertion time
  • Using FIX for the increment-mismatch strategy as a permanent solution

context