Primary Keys & @GeneratedValue
Primary Keys & @GeneratedValue
Every JPA entity must have a primary key — the value that uniquely identifies a row in its mapped table. How that key is generated at INSERT time is one of the first decisions you make when mapping a domain model, and the wrong choice shows up as a performance bottleneck or a concurrency bug months later. This lesson covers the mechanics of @Id, every strategy available under @GeneratedValue, and the real-world trade-offs that should drive your choice.
The Bare Minimum: @Id
Any persistent field annotated with @Id becomes the primary key. You can place the annotation on the field itself (field access) or on the getter (property access); Hibernate uses whichever style you pick for all other mappings in that entity too. Modern practice strongly prefers field access.
With no @GeneratedValue, the application must assign id before calling persist(). That is sometimes intentional (natural keys, UUIDs generated in code), but for surrogate integer keys it is impractical.
@GeneratedValue and the Four Strategies
Annotating the @Id field with @GeneratedValue delegates key generation to the JPA provider. The strategy attribute accepts four values from the GenerationType enum.
IDENTITY — Let the Database Column Auto-Increment
GenerationType.IDENTITY maps to a database AUTO_INCREMENT (MySQL/MariaDB) or GENERATED ALWAYS AS IDENTITY (PostgreSQL 10+, SQL Server) column. The database assigns the key on insert and Hibernate reads it back immediately.
The DDL Spring Boot generates for this on MySQL looks like:
SEQUENCE — A Database Sequence Object
GenerationType.SEQUENCE delegates to a named database sequence object (natively supported by PostgreSQL, Oracle, H2, and SQL Server 2012+; not available in MySQL before version 8). Hibernate calls NEXT VALUE FOR sequence_name before the INSERT, so it knows the key before the row is written and can batch inserts freely.
The allocationSize parameter is critical for performance. With allocationSize = 50, Hibernate fetches one sequence value from the database and then increments in memory for the next 49 inserts before hitting the database again. The database sequence must increment by the same amount (INCREMENT BY 50).
Corresponding DDL:
AUTO — Provider Picks the Strategy
GenerationType.AUTO is the default when you omit the strategy attribute. Hibernate inspects the dialect and chooses what it considers best for the target database. On PostgreSQL it picks SEQUENCE; on older MySQL it falls back to a special shared TABLE-based sequence.
TABLE — A Fallback for Portability
GenerationType.TABLE emulates a sequence using a regular database table. It works on every database, including MySQL, but it requires pessimistic row-level locking on each insert — one extra SELECT … FOR UPDATE plus one UPDATE per key request. It is the slowest strategy and almost never the right choice for new projects.
Using UUID as a Primary Key
Hibernate 6 and JPA 3.1 add first-class UUID support. Declare the field as java.util.UUID and use GenerationType.UUID (or the Hibernate-specific @UuidGenerator). The JPA provider generates a UUID before the INSERT, giving you the same batching freedom as SEQUENCE.
UUIDs are useful when entities must be created across multiple database shards or service instances without a central sequence. The cost is larger index pages (16 bytes vs 8 bytes for BIGINT) and — with random UUIDs — highly fragmented B-tree indexes. For write-heavy tables on relational databases, BIGINT + SEQUENCE is still faster.
Strategy Selection Guide
- PostgreSQL / Oracle / SQL Server (modern): SEQUENCE with a tuned
allocationSize. Best performance, supports batching. - MySQL / MariaDB: IDENTITY is the pragmatic choice. Accept that batch inserts are disabled, or use SEQUENCE only if you are on MySQL 8+ and willing to create sequence objects manually (they are available but not the DDL default).
- Distributed / multi-shard: UUID. Eliminates coordination overhead at the cost of index size.
- Portable legacy code: TABLE as a last resort.
- New Spring Boot projects (PostgreSQL): SEQUENCE +
allocationSize = 50is the idiomatic default.
Composite Keys
JPA also supports composite primary keys via @IdClass or @EmbeddedId. These are covered in detail in a later lesson on embeddables. Avoid composite keys on new tables — surrogate integer or UUID keys are simpler to work with and perform better on joins.
Summary
@Id marks the primary-key field; @GeneratedValue hands key assignment to the provider. IDENTITY is the simplest strategy but blocks batch inserts. SEQUENCE is the most flexible and performant — tune allocationSize to reduce database round-trips. AUTO is convenient in demos but unpredictable across Hibernate versions; always be explicit in production. UUID solves distributed ID generation at the cost of index efficiency. Pick based on your database vendor and write pattern, then move on — the rest of entity mapping does not care which strategy you chose.