To insert an object with Hibernate, we pass a new entity to EntityManager.persist() inside a transaction, and Hibernate writes the INSERT statement for it. An entity is a Java class mapped to a table with @Entity. After persist(), Hibernate tracks the object and gives it an id, and it sends the INSERT when it synchronizes its changes with the database, which is called a flush.
We insert entities whenever our app saves something new, such as a pet that arrives at an animal shelter or a person who signs up to adopt one.
The following example maps a Pet entity with a sequence id and inserts one pet.
@Entity
public class Pet {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
private String species;
private int age;
private boolean adopted;
}
Pet max = new Pet("Max", "dog", 3);
emf.runInTransaction(em -> {
em.persist(max); // select next value for Pet_SEQ
Long id = max.getId(); // 1
}); // commit: insert into Pet (adopted,age,name,species,id) values (?,?,?,?,?)
Notice that persist() reads the next id from Pet_SEQ at once, whereas the INSERT waits for the flush at commit. For a Hibernate insert, the id strategy decides whether the SQL runs at persist() or at the flush.
Next, we see how persist() tracks an entity and how it differs from merge(). After that, we insert pets with three id strategies, read the generated id and write rows with HQL and native insert statements.
1. How Does persist() Insert an Entity?
Hibernate keeps the objects it tracks in the persistence context, a per-EntityManager map from id to object. The method persist() puts a new object into this map, and from then on the object is managed. Every entity is always in one of three entity states.
- A transient object was created with new, so Hibernate does not know it and it has no row.
- A managed object is in the persistence context. Hibernate inserts or updates its row at flush.
- A detached object has a row, but the persistence context that loaded or saved it is closed.
Only a transient object goes to persist(). An object that already has a row goes to merge(), which copies its values onto a managed instance. For example, a pet that a volunteer enters on the intake form is transient, whereas a pet that a REST client sends back after an edit is detached.

The two methods differ in what they return and in what happens to the object we pass.
| persist(pet) | merge(pet) | |
|---|---|---|
| Accepts | A new object only | A new or a detached object |
| Returns | void | A managed copy |
| The object we pass | Becomes managed | Stays as it was |
| New object, id null | INSERT | INSERT; only the copy gets the id |
| Detached object | EntityExistsException | SELECT, then UPDATE |
2. Hibernate Insert Example
The example is a pet adoption shelter, built with Hibernate 7.4, Java 25 and an in-memory H2 database. The complete project on GitHub runs every insert in this section, prints the SQL and checks the results with 20 JUnit tests (mvn -q compile exec:java, mvn test).
2.1. The Shelter Entities
Each entity uses a different id strategy, because the strategy changes when the INSERT runs. The AdoptedPet entity maps an archive table that we fill with an insert query in section 2.5.

Only the id mapping differs between the entities, and it is the part that matters for inserts.
// Pet: a database sequence (Pet_SEQ) supplies the id
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
// Adopter and AdoptedPet: an identity (auto-increment) column supplies the id
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Kennel: our code sets the kennel number
@Id
private Integer number;
We build the EntityManagerFactory in code with HibernatePersistenceConfiguration, so the project has no persistence.xml or hibernate.cfg.xml.
EntityManagerFactory emf = new HibernatePersistenceConfiguration("pet-shelter")
.managedClasses(Pet.class, Adopter.class, Kennel.class, AdoptedPet.class)
.jdbcUrl("jdbc:h2:mem:shelter;DB_CLOSE_DELAY=-1")
.jdbcCredentials("sa", "")
.schemaToolingAction(Action.CREATE_DROP)
.showSql(true, false, false)
.createEntityManagerFactory();
The method emf.runInTransaction() opens an EntityManager, begins a transaction, runs our code, commits and closes the EntityManager. The method emf.callInTransaction() does the same and returns a value.
2.2. When Does Hibernate Send the INSERT?
Hibernate collects the changes in the persistence context and writes them at flush time. A flush happens at commit and whenever we call em.flush(), and Hibernate also flushes before a query that reads the same table. The exception is an identity id. The database creates the id only when it inserts the row, so Hibernate has to run the INSERT inside persist().

The same persist() call prints the SQL at different moments for the three entities.
emf.runInTransaction(em -> {
em.persist(max); // select next value for Pet_SEQ
Long id = max.getId(); // 1
}); // insert into Pet (adopted,age,name,species,id) values (?,?,?,?,?)
emf.runInTransaction(em -> {
em.persist(lokesh); // insert into Adopter (name,id) values (?,default)
Long id = lokesh.getId(); // 1
}); // commit, no more SQL
emf.runInTransaction(em -> {
em.persist(new Kennel(1, "large")); // no SQL
}); // insert into Kennel (size,number) values (?,?)
We can send a pending INSERT before the commit. The row is still part of the transaction, so a rollback removes it.
emf.runInTransaction(em -> {
em.persist(luna); // select next value for Pet_SEQ
em.flush(); // insert into Pet (adopted,age,name,species,id) values (?,?,?,?,?)
});
emf.runInTransaction(em -> {
em.persist(coco);
Long count = em.createQuery("select count(*) from Pet", Long.class)
.getSingleResult(); // insert into Pet ... , then select count(*) from Pet p1_0
}); // 3, the query sees the new pet
2.3. Getting the Generated Id
After persist() returns, the entity already holds its generated id, for both SEQUENCE and IDENTITY. We read it from the getter, or from PersistenceUnitUtil when we only have an Object.
Long id = emf.callInTransaction(em -> {
Pet daisy = new Pet("Daisy", "dog", 5);
em.persist(daisy);
return daisy.getId(); // 4
});
Object maxId = emf.getPersistenceUnitUtil().getIdentifier(max); // 1
The call for Daisy printed no select next value. The default SEQUENCE mapping uses an allocation size of 50. Hibernate reads the sequence twice for the first block and once for every 50 new ids after that, so most persist() calls for Pet send no SQL at all.
2.4. Inserting or Updating a Detached Object With merge()
We inserted max in a transaction that is closed, so max is detached. For example, a volunteer corrects the age of Max to 4 in the shelter app, and the app passes the detached object to merge(). Hibernate loads the row and copies our values onto a managed instance, which it writes back with an UPDATE at commit.
max.setAge(4);
emf.runInTransaction(em -> {
Pet copy = em.merge(max); // select p1_0.id,p1_0.adopted,p1_0.age,... from Pet p1_0 where p1_0.id=?
boolean same = copy == max; // false
boolean copyManaged = em.contains(copy); // true
boolean maxManaged = em.contains(max); // false
}); // update Pet set adopted=?,age=?,name=?,species=? where id=?
The method merge() also inserts a new object, but it inserts a copy. The object we passed keeps a null id, so we always continue with the returned instance.
Pet bella = new Pet("Bella", "cat", 1);
Pet saved = emf.callInTransaction(em -> em.merge(bella)); // insert into Pet ...
Long bellaId = bella.getId(); // null
Long savedId = saved.getId(); // 5
2.5. Inserting Rows With an HQL Insert Statement
HQL, the Hibernate Query Language, has an insert statement that writes rows without creating entity objects. The HQL insert statement has two forms.
- The insert … values form adds one row for each tuple in the values clause.
- The insert … select form adds one row for each row that the select returns, possibly none.
Insert statements are part of HQL, not of JPQL, the standard Jakarta Persistence query language. Hibernate still runs them through em.createQuery(), and executeUpdate() returns the number of inserted rows. We can leave out the id when the entity uses a sequence or an identity column.
int rows = em.createQuery("""
insert Pet (name, species, age, adopted)
values ('Rocky', 'dog', 4, false), ('Milo', 'cat', 3, false)""")
.executeUpdate(); // 2
// insert into Pet(name,species,age,adopted,id) values ('Rocky','dog',4,false,?), ('Milo','cat',3,false,?)
The select form copies data from another entity. In our example, every adopted pet goes to the archive.
int rows = em.createQuery("""
insert into AdoptedPet (name, species)
select p.name, p.species from Pet p where p.adopted = true""")
.executeUpdate(); // 2, AdoptedPet now holds Daisy and Max
// insert into AdoptedPet(name,species) select p1_0.name,p1_0.species from Pet p1_0 where p1_0.adopted=true
For SQL that HQL cannot express, a native SQL query runs the statement as written, with table and column names instead of entity names.
int rows = em.createNativeQuery("insert into Kennel (number, size) values (?, ?)")
.setParameter(1, 3)
.setParameter(2, "medium")
.executeUpdate(); // 1
All three statements run inside a transaction and write rows to the table. They do not create managed objects, so we load the new rows with a query or em.find() when we need them.
3. Hibernate Insert FAQs
3.1. What Replaced Session.save() in Hibernate 7?
Session.save(), update() and saveOrUpdate() were deprecated in Hibernate 6 and removed in Hibernate 7, so code that calls them no longer compiles. Hibernate 7 replaces them with persist() and merge().
| Hibernate 5 and 6 | Hibernate 7 |
|---|---|
| session.save(pet) | session.persist(pet) or em.persist(pet) |
| session.update(pet) | session.merge(pet) |
| session.saveOrUpdate(pet) | persist() for a new object, merge() for a detached one |
| Serializable id = session.save(pet) | persist(pet), then pet.getId() |
The Session still has persist(), so old code can unwrap it. The method StatelessSession.insert() is the closest match to the old save(), because it runs the INSERT at once and returns the id, without a persistence context.
emf.runInTransaction(em -> em.unwrap(Session.class).persist(oscar)); // insert into Pet ...
Object id = emf.unwrap(SessionFactory.class)
.fromStatelessTransaction(ss -> ss.insert(oliver)); // insert into Pet ...
// id = 9
3.2. Why Does persist() Not Insert Anything?
Without a transaction, persist() never writes the row. The object becomes managed, but nothing flushes it, and closing the EntityManager discards it. In Hibernate 7.4, this holds even for an IDENTITY id.
try (EntityManager em = emf.createEntityManager()) {
em.persist(new Pet("Ghost", "dog", 1)); // no SQL
em.persist(new Adopter("Ghost")); // no SQL
}
// select count(*) from Pet where name = 'Ghost' -> 0
// select count(*) from Adopter where name = 'Ghost' -> 0
A rollback has the same effect, even after the INSERT was sent. For example, an adoption form that fails validation after persist() throws an exception, and the rollback removes the adopter row that was already inserted.
emf.runInTransaction(em -> {
em.persist(new Adopter("Anna")); // insert into Adopter (name,id) values (?,default)
throw new IllegalStateException("adoption form incomplete");
}); // rollback, adopters = 1 (only Lokesh)
3.3. Can I Call persist() on an Entity That Already Has an Id?
No, if the id is generated. Hibernate treats an entity with a generated id that is not null as detached, and persist() rejects it right away.
emf.runInTransaction(em -> {
max.setAge(4);
em.persist(max);
});
jakarta.persistence.EntityExistsException: Detached entity passed to persist: com.howtodoinjava.hibernate.insert.Pet
We fix it with em.merge(max) as in section 2.4. The same error appears when we set the id of a new object by hand, for example from a request body, so we leave the id null for new objects. An entity with an assigned id, such as Kennel, does not get the detached check, so a duplicate id shows up later, as FAQ 3.4 shows.
3.4. What Happens When Two Entities Have the Same Id?
The result depends on where the other object with that id is. Two objects with the same id in one persistence context fail at the second persist().
em.persist(new Kennel(2, "small"));
em.persist(new Kennel(2, "medium"));
jakarta.persistence.EntityExistsException: A different object with the same identifier value was already associated with this persistence context for entity [com.howtodoinjava.hibernate.insert.Kennel with id '2']
When the row with that id is already in the database, Hibernate cannot know it at persist(). The database rejects the INSERT at commit, and the cause of the error is Hibernate’s ConstraintViolationException.
jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [Unique index or primary key violation: "PUBLIC.CONSTRAINT_8 PRIMARY KEY ON PUBLIC.KENNEL(NUMBER) ( /* key:1 */ 1, 'large')"; SQL statement:
insert into Kennel (size,number) values (?,?) [23505-252]] ...]
Use a generated id unless the business value is unique by nature, and check for an existing row with em.find() before inserting an assigned id.
3.5. Should I Use IDENTITY or SEQUENCE for Inserts?
We use SEQUENCE when the database supports sequences, as H2, PostgreSQL and Oracle do. With IDENTITY, Hibernate cannot batch the INSERT statements, because each persist() must run its own INSERT to get the id.
| IDENTITY | SEQUENCE | |
|---|---|---|
| SQL at persist() | The INSERT | select next value, only once per 50 ids |
| INSERT runs | Inside persist() | At flush or commit |
| JDBC batching of inserts | Not possible | Possible |
| Database support | Identity or auto-increment column | Sequences (PostgreSQL, Oracle, H2) |
4. Conclusion
To insert a new object, we call persist() in a transaction and read the generated id from the entity afterwards. A detached object goes to merge(), and we keep the copy it returns. The id strategy decides when the INSERT runs. An IDENTITY id inserts at persist(), while SEQUENCE and assigned ids wait for the flush, which also makes batching possible.
For rows that need no entity object, the two forms of the HQL insert statement and native SQL inserts write to the table without managed objects. The old Session.save() is gone in Hibernate 7, and persist() or StatelessSession.insert() replaces it.
5. References
- EntityManager JavaDoc (Jakarta Persistence 3.2)
- Jakarta Persistence 3.2 specification: Persisting an Entity Instance
- PersistenceUnitUtil JavaDoc (Jakarta Persistence 3.2)
- Hibernate ORM 7.4 User Guide: Insert statements
- Hibernate ORM 7.4 StatelessSession JavaDoc
- Hibernate ORM 7.0 Migration Guide
Happy Learning !!
Hi Lokesh,
is it possible using entityManager?
Yes, you have to use persist() method, it works similarly.
Initial SessionFactory creation failed.java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
Exception in thread “main” java.lang.ExceptionInInitializerError
at hibernateutil.HibernateUtil.buildSessionFactory(HibernateUtil.java:22)
at hibernateutil.HibernateUtil.(HibernateUtil.java:10)
at hibernatetesinsert.TestHibernateInsert.main(TestHibernateInsert.java:12)
Caused by: java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
at org.hibernate.cfg.Configuration.(Configuration.java:120)
at hibernateutil.HibernateUtil.buildSessionFactory(HibernateUtil.java:17)
… 2 more
Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
… 4 more
i am getting above error
Add apache commons logging (link) to classpath.
how to connect front end to hibernate
Hi ,
Can you tell how to write inner join in criteria.
Sir this is not Hibernate insert query example here u are just inserting data using setter method of modal class .
hibernate insert query is different.