Entity Lifecycle States
IntermediateAn entity moves through four states: transient (new, no ID), managed (tracked by context), detached (disconnected after context closes), and removed (scheduled for DELETE).
Overview
Every entity instance in Hibernate/JPA exists in one of four lifecycle states. Understanding the state your entity is in at any given moment is critical because the EntityManager's behaviour — whether it tracks changes, fires SQL, or throws exceptions — depends entirely on the current state. The four states are: **Transient** (new Java object, not yet associated with any persistence context); **Managed** (tracked by the active persistence context — dirty changes trigger automatic SQL on flush); **Detached** (was once managed but the context has closed or the entity was explicitly detached — changes are NOT tracked); **Removed** (scheduled for DELETE on next flush). Misunderstanding these states is the root cause of many common Hibernate bugs.
The Four States and Transitions
Key transitions:
**new → Managed**: `em.persist(entity)` — Hibernate starts tracking the entity and will INSERT on flush.
**Managed → Detached**: context closes (end of @Transactional), `em.detach(entity)`, or `em.clear()` — entity changes are no longer tracked.
**Detached → Managed**: `em.merge(detachedEntity)` — Hibernate loads or locates the managed version and copies the detached entity's state into it. Returns the managed instance.
**Managed → Removed**: `em.remove(entity)` — Hibernate schedules a DELETE on flush. The entity must be in managed state first (you can't remove a detached entity).
**Removed → Managed**: `em.persist(removedEntity)` — cancels the pending DELETE.
@Service
@Transactional
public class OrderService {
@PersistenceContext
private EntityManager em;
public void demo() {
// 1. Transient — new object, no persistence context
Order order = new Order(); // state: TRANSIENT
order.setReference("ORD-1");
// 2. Managed — persist transitions to MANAGED
em.persist(order); // state: MANAGED; INSERT on flush
order.setStatus("PENDING"); // dirty check — UPDATE on flush (auto!)
// 3. Detached — clear all entities from context
em.flush(); // SQL executed now
em.detach(order); // state: DETACHED — changes no longer tracked
order.setStatus("SHIPPED"); // NOT tracked — no SQL
// 4. Re-attach — merge returns a MANAGED copy
Order managed = em.merge(order); // state: MANAGED (new reference!)
// The original 'order' is still DETACHED; use 'managed' going forward
// 5. Removed — schedule DELETE
em.remove(managed); // state: REMOVED; DELETE on flush
}
}Dirty Checking — How Automatic Updates Work
Hibernate's dirty checking is the mechanism that detects changes in managed entities and generates UPDATE statements automatically without you calling save(). At flush time (end of transaction, explicit em.flush(), or before a query if FlushMode is AUTO), Hibernate compares each managed entity's current field values against a snapshot taken when the entity was first loaded. If any field differs, an UPDATE is generated. This is the reason you do not need to call entityRepo.save(entity) after changing a managed entity — but it also means unintended field changes will silently emit UPDATEs.
@Service
@Transactional
public class OrderService {
public void updateStatus(Long orderId, String newStatus) {
Order order = orderRepo.findById(orderId).orElseThrow(); // MANAGED
order.setStatus(newStatus);
// NO save() call needed — dirty check detects the change
// Hibernate generates: UPDATE orders SET status=? WHERE id=?
} // ← transaction commits here → flush → SQL executes
// Careful: even unintended changes trigger UPDATE
public Order getOrder(Long orderId) {
Order order = orderRepo.findById(orderId).orElseThrow();
log.info(order.getCustomer().getName()); // accesses LAZY proxy — ok
// If you accidentally call a setter here, an UPDATE fires!
return order;
}
// Use @Transactional(readOnly = true) to prevent dirty checking on read-only ops
@Transactional(readOnly = true)
public Order getOrderReadOnly(Long orderId) {
return orderRepo.findById(orderId).orElseThrow();
// Hibernate skips dirty check on flush — performance optimisation
}
}merge() vs persist() — A Common Confusion
New developers often reach for `save()` (Spring Data JPA) for both new and existing entities. Under the hood, Spring Data's `save()` calls `persist()` if the entity has no ID, and `merge()` if it has an ID. Understanding the difference matters when working with detached entities:
**persist()** — transitions a transient entity to managed. Throws an exception if called with a detached entity (already has an ID and is in the DB).
**merge()** — copies detached entity state into a managed instance. Always returns the managed copy. The passed-in detached entity remains detached.
// persist vs merge in action
Order transient = new Order("ORD-99"); // no id → transient
em.persist(transient); // OK: INSERT queued
Order detached = orderRepo.findById(1L).get(); // load and detach
em.clear(); // detach everything
detached.setStatus("CANCELLED");
// em.persist(detached); // ❌ EntityExistsException — already has ID
Order managed = em.merge(detached); // ✅ copies state to managed instance
// managed.getStatus() == "CANCELLED"
// UPDATE queued on flush
// Spring Data save() internals:
// if (entityInformation.isNew(entity)) return em.persist(entity);
// else return em.merge(entity);Key Points to Remember
- 1Four states: Transient (new, no context), Managed (tracked, changes auto-flushed), Detached (was managed, not tracked), Removed (DELETE pending).
- 2Dirty checking: Hibernate compares managed entity fields to a snapshot at flush time — changed fields generate UPDATE automatically.
- 3merge() returns a new managed reference; the detached instance passed in stays detached — always use the returned value.
- 4You cannot remove a detached entity — call merge() first to re-attach it, then remove() on the managed copy.
- 5@Transactional(readOnly=true) skips dirty checking and snapshot creation — use it for all read-only service methods for better performance.
- 6LazyInitializationException occurs when a LAZY proxy is accessed after the persistence context (transaction) has closed.
Interview Questions
Sign in to ask AriaWhat are the four JPA/Hibernate entity lifecycle states?
What is dirty checking in Hibernate and how does it work?
What is the difference between persist() and merge()?
You call em.remove(entity) but get an exception. What is the likely cause?
Why should you use @Transactional(readOnly=true) for read-only service methods?
Ask Aria about Entity Lifecycle States
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.