Home/Learn/Hibernate & JPA/Entity Lifecycle States

Entity Lifecycle States

Intermediate
Fundamentals

An 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.

Java — Entity Lifecycle States
@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.

Java — Dirty Checking
@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.

Java — persist vs merge
// 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 Aria
1

What are the four JPA/Hibernate entity lifecycle states?

EasyTCS
2

What is dirty checking in Hibernate and how does it work?

MediumAmazon
3

What is the difference between persist() and merge()?

MediumFlipkart
4

You call em.remove(entity) but get an exception. What is the likely cause?

MediumInfosys
5

Why should you use @Transactional(readOnly=true) for read-only service methods?

HardGoldman Sachs

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.

Loading discussion…