PersistentObjectException detached entity passed to persist thrown by JPA and Hibernate
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
The PersistentObjectException: detached entity passed to persist error means you called EntityManager.persist() on an entity that already has a database identity (a non-null @Id). The fix is to use merge() instead of persist() for detached entities, or to ensure the entity is truly new before persisting it.
Understanding Entity Lifecycle States
Every JPA entity exists in one of four states. The PersistentObjectException happens when you confuse two of them.
| State | Has @Id? | Managed by EntityManager? | In Database? |
| New / Transient | No (or null) | No | No |
| Managed / Persistent | Yes | Yes | Yes |
| Detached | Yes | No | Yes |
| Removed | Yes | Yes (scheduled for delete) | Yes (until flush) |
The key distinction: persist() expects a new entity with no database identity. If the entity has an @Id value and is not currently managed, JPA considers it detached and throws PersistentObjectException.
The Five Most Common Causes
1. Persisting an Entity Fetched from a Closed Session
This is the most frequent cause. You fetch an entity, close the EntityManager (or the transaction ends), then try to persist the same object in a new context:
Fix: Use merge() instead of persist():
2. Parent-Child Cascade with a Detached Parent
When you use CascadeType.PERSIST or CascadeType.ALL on a relationship, persisting the child also cascades persist() to the parent. If the parent is detached, the cascade fails:
Fix: Either merge the parent first, or change the cascade type:
3. Using GenerationType.AUTO with a Pre-Set ID
If your entity uses @GeneratedValue but you manually set the ID before persisting, Hibernate treats it as detached:
Fix: Do not set the @Id field when using @GeneratedValue. Let Hibernate assign it.
4. Detached Entities from DTOs or Deserialization
When you receive an entity from a REST API request, JSON deserialization creates a new Java object with a populated ID. This object is not managed by any persistence context:
Fix: Use merge() or find-then-update:
5. Bidirectional Relationships with Inconsistent State
With bidirectional @OneToMany / @ManyToOne mappings, failing to synchronize both sides can cause detached references:
Fix: Always synchronize both sides of a bidirectional relationship:
persist() vs merge(): When to Use Each
| Method | Input State | What It Does | Returns |
persist() | Transient (new) | Inserts a new row, assigns ID | void (original object becomes managed) |
merge() | Detached or transient | Copies state into a managed entity, inserts or updates | Managed copy (original stays detached) |
Critical difference in merge() behavior: it returns a new managed instance. The original object remains detached:
Spring Data JPA: save() Handles This Automatically
If you use Spring Data JPA, the save() method internally checks whether to call persist() or merge():
Spring determines "new vs existing" by checking if the @Id is null (for generated IDs) or by implementing Persistable<ID> for assigned IDs:
Common Pitfalls
- Using
merge()everywhere "just to be safe":merge()always issues a SELECT before the INSERT/UPDATE to check for existing records. For batch inserts of new entities,persist()is significantly faster because it skips this check. - Ignoring the return value of
merge(): The object you passed tomerge()is still detached. Always use the returned managed instance for further operations. - Using
CascadeType.ALLon@ManyToOne: This cascadespersist()to the parent entity, which is almost always already persisted. UseCascadeType.ALLon the@OneToManyside and be selective on@ManyToOne. - Confusing
persist()withsave()semantics: JPA haspersist()andmerge(). Spring Data JPA'ssave()is a convenience method that calls one or the other. If you are using raw JPA (no Spring), you must choose the right method yourself. - Forgetting
@GeneratedValue: Without this annotation, JPA does not generate IDs automatically. An entity with a null@Idand no@GeneratedValuemay be treated differently depending on the provider.
Summary
PersistentObjectExceptionmeanspersist()received an entity with an existing database identity (non-null@Id).- Use
persist()only for new entities with no ID. Usemerge()for detached entities that need reattaching. - The most common trigger is cascading
persist()to a detached parent viaCascadeType.ALLorCascadeType.PERSIST. merge()returns a new managed instance. Always use the returned object, not the original.- Spring Data JPA's
save()method handles thepersist()vsmerge()decision automatically. - For deserialized entities from REST APIs, prefer find-then-update over
merge()for explicit control over what gets modified.
Related reading
- pg_config executable not found
- pg.InternalError SSL SYSCALL error EOF detected
- PHP code to convert a MySQL query to CSV
- PHP date format when inserting into datetime in MySQL
- Peterson algorithm in Java?
- Places where JavaBeans are used?
- PHP MySQL Google Chart JSON - Complete Example
- PHP MySQL transactions examples

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.