Database · Lesson 55 of 95
Primary Key Generation Strategies
Primary key generation strategies in Spring Boot JPA explained: IDENTITY, SEQUENCE, UUID, AUTO and assigned ids, with a hospital app and real output.
When you visit a hospital, the first thing the front desk gives you is a number: a token, a patient ID, a file number. That number is how the hospital finds you again among thousands of people. Two patients with the same number would be a disaster. A database table has the same need. Every row needs one value that is different from all others, and that value is the primary key.
Who creates that number? You can hand it out yourself, or you can let the database do it. The primary key generation strategies in JPA decide this. In this topic you will meet the main ones, see the ids they create in a real run, and learn how to choose.
What are primary key generation strategies?
A primary key is the column that identifies each row. In an entity, you mark it with @Id. A generation strategy tells JPA how the value gets created when you save a new object. You choose it with @GeneratedValue.
JPA offers these strategies through the GenerationType enum:
IDENTITY: the database makes the id when the row is inserted, using an auto-increment column.SEQUENCE: the database keeps a counter object called a sequence, and Hibernate asks it for the next numbers.UUID: Hibernate creates a random 128-bit id in the application.AUTO: Hibernate picks a strategy that fits the id type and the database.- No
@GeneratedValueat all: your code sets the id (an assigned id).
Why is it used?
Every row needs an id, and doing it by hand is risky. If two users create a row at the same moment and your code picks "last id plus one", both may get the same number. Letting the database or Hibernate create ids avoids that clash.
The choice also affects speed and design. Sequences let Hibernate reserve a block of ids and insert many rows in one batch. UUIDs can be created before the row reaches the database and never clash across servers, which helps when many systems create data. Assigned ids suit natural codes like a ward name or a country code, where the value already means something.
How it works
The strategies differ in when the id becomes known.
textIDENTITY: save() -> INSERT (no id) -> database makes id -> id returned to object SEQUENCE: save() -> ask sequence for id -> INSERT (with id)
With IDENTITY, Hibernate must run the insert right away to learn the id, because only the database can make it. With SEQUENCE, Hibernate asks the sequence for a number first, then inserts the row with the id already known. That lets it group inserts together.
UUID and assigned ids skip the database completely.
textUUID: save() -> Hibernate makes a UUID -> INSERT (with id) Assigned: your code sets the id save() -> INSERT (with your id)
For a UUID, Hibernate creates a random value in Java. For an assigned id, your own code provides it, and you must make sure it is unique.
Real-Life Example
Think about how different places hand out numbers. A bank's queue machine prints the next ticket, and you cannot choose it. That is identity: the machine owns the counter. A cloakroom keeps a roll of numbered tokens and gives out a handful to each helper at the start of the day. That is a sequence with a block size: helpers hand out numbers from their own handful without asking every time. A courier company prints a long random tracking code, which nobody else can guess or repeat. That is a UUID. And a hostel that names its rooms A1, A2, B1 is using assigned ids: the number means something, and the manager decides it.
Code Example
Let's build a small app for City Care Hospital that tries five ids: a patient with IDENTITY, a lab report with SEQUENCE, an appointment with AUTO, a receipt with UUID, and a ward with an assigned code.
textids/ ├─ pom.xml └─ src/main/ ├─ java/com/citycare/ids/ │ ├─ IdsApplication.java │ ├─ Patient.java │ ├─ LabReport.java │ ├─ Appointment.java │ ├─ Receipt.java │ ├─ Ward.java │ ├─ Repositories.java │ └─ IdRunner.java └─ resources/ └─ application.properties
File: pom.xml
xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.1</version> <relativePath/> </parent> <groupId>com.citycare</groupId> <artifactId>ids</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
File: IdsApplication.java in package com.citycare.ids
javapackage com.citycare.ids; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class IdsApplication { public static void main(String[] args) { SpringApplication.run(IdsApplication.class, args); } }
File: Patient.java in package com.citycare.ids
javapackage com.citycare.ids; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class Patient { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; protected Patient() { } public Patient(String name) { this.name = name; } public Long getId() { return id; } }
File: LabReport.java in package com.citycare.ids
javapackage com.citycare.ids; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.SequenceGenerator; @Entity public class LabReport { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "report_seq") @SequenceGenerator(name = "report_seq", sequenceName = "report_seq", allocationSize = 5) private Long id; private String testName; protected LabReport() { } public LabReport(String testName) { this.testName = testName; } public Long getId() { return id; } }
File: Appointment.java in package com.citycare.ids
javapackage com.citycare.ids; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @Entity public class Appointment { @Id @GeneratedValue private Long id; private String doctor; protected Appointment() { } public Appointment(String doctor) { this.doctor = doctor; } public Long getId() { return id; } }
File: Receipt.java in package com.citycare.ids
javapackage com.citycare.ids; import java.util.UUID; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class Receipt { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; private int amount; protected Receipt() { } public Receipt(int amount) { this.amount = amount; } public UUID getId() { return id; } }
File: Ward.java in package com.citycare.ids
javapackage com.citycare.ids; import jakarta.persistence.Entity; import jakarta.persistence.Id; @Entity public class Ward { @Id private String code; private int beds; protected Ward() { } public Ward(String code, int beds) { this.code = code; this.beds = beds; } public String getCode() { return code; } }
File: Repositories.java in package com.citycare.ids
javapackage com.citycare.ids; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; interface PatientRepository extends JpaRepository<Patient, Long> { } interface LabReportRepository extends JpaRepository<LabReport, Long> { } interface AppointmentRepository extends JpaRepository<Appointment, Long> { } interface ReceiptRepository extends JpaRepository<Receipt, UUID> { } interface WardRepository extends JpaRepository<Ward, String> { }
File: IdRunner.java in package com.citycare.ids
javapackage com.citycare.ids; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class IdRunner implements CommandLineRunner { private final PatientRepository patients; private final LabReportRepository reports; private final AppointmentRepository appointments; private final ReceiptRepository receipts; private final WardRepository wards; public IdRunner(PatientRepository patients, LabReportRepository reports, AppointmentRepository appointments, ReceiptRepository receipts, WardRepository wards) { this.patients = patients; this.reports = reports; this.appointments = appointments; this.receipts = receipts; this.wards = wards; } @Override public void run(String... args) { System.out.println("IDENTITY: " + patients.save(new Patient("Riya")).getId() + ", " + patients.save(new Patient("Imran")).getId()); System.out.println("SEQUENCE: " + reports.save(new LabReport("CBC")).getId() + ", " + reports.save(new LabReport("X-Ray")).getId()); System.out.println("AUTO: " + appointments.save(new Appointment("Dr. Rao")).getId() + ", " + appointments.save(new Appointment("Dr. Sen")).getId()); System.out.println("UUID length: " + receipts.save(new Receipt(500)).getId().toString().length()); System.out.println("Assigned: " + wards.save(new Ward("ICU", 12)).getCode()); } }
File: application.properties in src/main/resources
propertiesspring.application.name=ids spring.jpa.show-sql=true
Run it with mvn spring-boot:run.
Output:
textIDENTITY: 1, 2 SEQUENCE: 1, 2 AUTO: 1, 2 UUID length: 36 Assigned: ICU
With show-sql on, Hibernate prints how it prepared the database. These are some of the lines from the same run:
sqlcreate sequence appointment_seq start with 1 increment by 50 create sequence report_seq start with 1 increment by 5 create table patient (id bigint generated by default as identity, name varchar(255), primary key (id)) create table receipt (amount integer not null, id uuid not null, primary key (id))
Code Explained
PatientusesIDENTITY. In the SQL its id column isgenerated by default as identity, and the database hands out 1, then 2.LabReportusesSEQUENCEwith a named@SequenceGenerator. TheallocationSize = 5value means Hibernate asks the sequence once and then uses five ids before it asks again.Appointmentuses a bare@GeneratedValue, which meansAUTO. For aLongid on H2, Hibernate chose a sequence named after the entity.ReceiptusesGenerationType.UUIDwith aUUIDfield. The id is created in Java, and its text form is 36 characters long.Wardhas no@GeneratedValue, so the code supplies"ICU"itself. Saving two wards with the same code would clash on the primary key.- The
Repositories.javafile holds five small interfaces. They are package-private, which is fine, because only the runner in the same package uses them.
Which Strategy Should I Choose?
| Strategy | Who makes the id | Good for | Watch out |
|---|---|---|---|
IDENTITY | The database | Simple apps, MySQL | Inserts cannot be batched well |
SEQUENCE | A database sequence | PostgreSQL, batch inserts | Needs a sequence object |
UUID | Hibernate | Many systems, hard-to-guess ids | Bigger and slower to index |
AUTO | Hibernate decides | Quick starts | Choice may change with the database |
| Assigned | Your code | Natural codes | You must keep them unique |
Common Mistakes
- Using `AUTO` and expecting the same ids everywhere. On MySQL it may behave differently from PostgreSQL, so state your strategy when it matters.
- Mismatched sequence names. The
sequenceNamemust match the sequence in the database. If you manage tables with migration scripts, create the sequence there. - Setting the id yourself on a generated entity. If you call
save()on an object that already has an id, Spring Data treats it as an update, not an insert. - Using `String` for random ids without care. Use the
UUIDtype, which stores in a compact form on databases that support it.
Interview Questions
What is the difference between `IDENTITY` and `SEQUENCE`?
Ans:IDENTITY lets the database create the id at insert time. SEQUENCE asks a database sequence for ids in advance, so Hibernate can batch inserts.
When would you use a UUID as a primary key?
Ans:When ids must be created outside the database, must not clash across servers, or must be hard to guess. The cost is bigger indexes.
What does `GenerationType.AUTO` do?
Ans:It lets the persistence provider pick a strategy that suits the id type and the database.
Key Points to Remember
- Every entity needs a primary key marked with
@Id. @GeneratedValuesets how the id is created:IDENTITY,SEQUENCE,UUIDorAUTO.- Without
@GeneratedValue, your code must set a unique id. IDENTITYneeds an immediate insert.SEQUENCEcan reserve ids and batch inserts.- Never assume ids are secret. Check access rights on the server.
Frequently Asked Questions
Which primary key generation strategy is best for MySQL?
IDENTITY is the usual choice, because MySQL has auto-increment columns and no sequence objects. It is simple and works well.
Why are my generated ids not 1, 2, 3?
Sequences can skip numbers, for example when a transaction rolls back or when Hibernate reserves a block. Gaps are normal. Never depend on ids being continuous.
Should I use Long or UUID for the primary key?
Use Long for most apps because it is small and fast. Choose UUID when ids must be created in many places or must be hard to guess.
Can I change the strategy after the table has data?
It is possible but needs care. The new strategy must continue after the highest existing id, or new rows will clash with old ones.
Related Topics
- JPA Entity: see the rest of the annotations on an entity.
- JpaRepository: save and load entities by their id.
- Connecting Spring Boot to PostgreSQL: sequences and identity on a real server.
- Database Migration with Flyway: create tables and sequences with scripts.
Practice Problems
Try each problem on your own first. Each project has its own pom.xml, shown below.
Easy: Library Cards Starting at 1000
PageTurn Library wants its membership card numbers to start at 1000 and go up by one. Build an entity LibraryCard (a member name) whose id comes from a database sequence named card_seq. Save three members and print each card number.
Use these members: Meera, Arjun, Kabir.
Show answerHide answer
initialValue sets the first number, and allocationSize = 1 makes Hibernate ask the sequence for one id at a time, so the numbers stay in order with no gaps.File: pom.xml
xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.1</version> <relativePath/> </parent> <groupId>com.pageturn</groupId> <artifactId>cards</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
File: CardsApplication.java in package com.pageturn.cards
javapackage com.pageturn.cards; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class CardsApplication { public static void main(String[] args) { SpringApplication.run(CardsApplication.class, args); } }
File: LibraryCard.java in package com.pageturn.cards
javapackage com.pageturn.cards; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.SequenceGenerator; @Entity public class LibraryCard { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "card_seq") @SequenceGenerator(name = "card_seq", sequenceName = "card_seq", initialValue = 1000, allocationSize = 1) private Long id; private String memberName; protected LibraryCard() { } public LibraryCard(String memberName) { this.memberName = memberName; } public Long getId() { return id; } public String getMemberName() { return memberName; } }
File: LibraryCardRepository.java in package com.pageturn.cards
javapackage com.pageturn.cards; import org.springframework.data.jpa.repository.JpaRepository; public interface LibraryCardRepository extends JpaRepository<LibraryCard, Long> { }
File: CardRunner.java in package com.pageturn.cards
javapackage com.pageturn.cards; import java.util.List; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class CardRunner implements CommandLineRunner { private final LibraryCardRepository repository; public CardRunner(LibraryCardRepository repository) { this.repository = repository; } @Override public void run(String... args) { for (String name : List.of("Meera", "Arjun", "Kabir")) { LibraryCard card = repository.save(new LibraryCard(name)); System.out.println("Card " + card.getId() + " for " + card.getMemberName()); } } }
The output is:
textCard 1000 for Meera Card 1001 for Arjun Card 1002 for Kabir
Medium: Railway Station Codes as Assigned Ids
A railway app uses the station code (such as PUNE) as the primary key, so the code is an assigned id. Save two stations. Then save a station with the code PUNE again but with a new name, and show what happens to the row count and the name. Finally, guard against the problem with existsById and print a message.
Show answerHide answer
save() did not fail. It silently replaced the name, so there are still two stations. To reject duplicates you must check yourself with existsById before saving.File: pom.xml
xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.1</version> <relativePath/> </parent> <groupId>com.railapp</groupId> <artifactId>stations</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
File: StationsApplication.java in package com.railapp.stations
javapackage com.railapp.stations; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StationsApplication { public static void main(String[] args) { SpringApplication.run(StationsApplication.class, args); } }
File: Station.java in package com.railapp.stations
javapackage com.railapp.stations; import jakarta.persistence.Entity; import jakarta.persistence.Id; @Entity public class Station { @Id private String code; private String name; protected Station() { } public Station(String code, String name) { this.code = code; this.name = name; } public String getName() { return name; } }
File: StationRepository.java in package com.railapp.stations
javapackage com.railapp.stations; import org.springframework.data.jpa.repository.JpaRepository; public interface StationRepository extends JpaRepository<Station, String> { }
File: StationRunner.java in package com.railapp.stations
javapackage com.railapp.stations; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class StationRunner implements CommandLineRunner { private final StationRepository repository; public StationRunner(StationRepository repository) { this.repository = repository; } @Override public void run(String... args) { repository.save(new Station("PUNE", "Pune Junction")); repository.save(new Station("MUMBAI", "Mumbai Central")); repository.save(new Station("PUNE", "Pune Jn (renamed)")); System.out.println("Stations: " + repository.count()); System.out.println("PUNE -> " + repository.findById("PUNE").orElseThrow().getName()); if (repository.existsById("PUNE")) { System.out.println("PUNE already exists"); } } }
The output is:
textStations: 2 PUNE -> Pune Jn (renamed) PUNE already exists