Back to Flush, Persist & Cache
Q1Question 1 of 5

session.evict() vs session.clear() vs detach()?

Key Concepts
  • evict() is the Hibernate Session method that removes a single entity from the persistence context.
  • detach() is the JPA-standard equivalent of evict(), also removing a single entity from tracking.
  • clear() removes all managed entities from the context at once, resetting it to empty.
  • None of these methods touch the database row; they only change what the in-memory context tracks.
  • Detaching before flushing loses unflushed changes because dirty checking no longer sees the entity.
  • In batch processing, the flush+clear pattern after every N rows keeps memory bounded and prevents OutOfMemoryError.
  • After clear(), any entity reference you still hold is detached, so touching a lazy association throws LazyInitializationException.

Definition

`evict()` and `detach()` remove one entity from tracking; `clear()` removes all; none touch the database row.
  • session.evict(entity) -- Hibernate-native method that removes a single managed entity from the persistence context, dropping its reference and dirty-checking snapshot.
  • entityManager.detach(entity) -- JPA-standard equivalent of evict(), removing one entity from tracking so Hibernate stops watching it.
  • session.clear() / entityManager.clear() -- removes all managed entities from the context at once, resetting it to empty.
  • No database effect -- all three leave the row in the database untouched; they only change what the in-memory context tracks and dirty-checks.
  • Memory reclamation -- after detach or clear, the entity and its snapshot become eligible for garbage collection.

The persistence context is like Rajesh's desk at National Bank of India where every account form he is working on sits, each with a photocopy (snapshot) for comparison. evict/detach means Rajesh takes one specific form off the desk and files it away -- he stops watching that one. clear means Rajesh sweeps the entire desk clean and starts fresh. Neither action changes the actual money in the vault -- it only changes which forms Rajesh is still actively tracking.

Understanding the Concepts

What does session.evict() do?

session.evict(entity) is the Hibernate-native method that removes a single managed entity from the persistence context. After eviction, Hibernate drops the entity's reference and its dirty-checking snapshot, so it stops tracking the entity for changes.

JAVA
package com.bank.service;

import com.bank.model.Account;
import jakarta.persistence.EntityManager;
import org.hibernate.Session;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;

@Service
public class EvictDemo {

    private final EntityManager em;

    public EvictDemo(EntityManager em) {
        this.em = em;
    }

    @Transactional
    public void demonstrateEvict() {
        Account account = em.find(Account.class, "SAV2001"); // MANAGED
        account.setBalance(new BigDecimal("100000.00"));

        Session session = em.unwrap(Session.class);
        session.evict(account); // NOW DETACHED -- change will NOT be flushed

        // At commit: no UPDATE because the entity is no longer tracked
    }
}
TEXT
Expected behavior:
- em.find() loads the account (MANAGED, snapshot taken)
- setBalance() changes the in-memory field
- session.evict() removes the entity from the context
- At commit: NO UPDATE generated -- the change is lost

What does entityManager.detach() do?

entityManager.detach(entity) is the JPA-standard equivalent of evict(). Functionally identical for one entity -- it removes the entity from the persistence context.

JAVA
package com.bank.service;

import com.bank.model.Account;
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;

@Service
public class DetachDemo {

    private final EntityManager em;

    public DetachDemo(EntityManager em) {
        this.em = em;
    }

    @Transactional
    public void demonstrateDetach() {
        Account account = em.find(Account.class, "SAV2001"); // MANAGED
        account.credit(new BigDecimal("5000.00"));

        em.flush();             // push the UPDATE to the DB first
        em.detach(account);     // NOW safe to detach -- change already flushed

        // account is detached, but the UPDATE was already sent
    }
}
TEXT
Expected behavior:
- em.flush() sends UPDATE to the database (within the transaction)
- em.detach() removes the entity from the context
- The balance change is preserved because it was flushed before detaching

What does entityManager.clear() do?

entityManager.clear() detaches everything at once. It removes all managed entities from the context, resetting it to empty. This is the method used in batch processing to free memory after each chunk.

JAVA
package com.bank.service;

import com.bank.model.Account;
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;

@Service
public class ClearDemo {

    private final EntityManager em;

    public ClearDemo(EntityManager em) {
        this.em = em;
    }

    @Transactional
    public void demonstrateClear() {
        Account a1 = em.find(Account.class, "SAV2001"); // MANAGED
        Account a2 = em.find(Account.class, "SAV2002"); // MANAGED
        Account a3 = em.find(Account.class, "SAV2003"); // MANAGED

        a1.setStatus("FROZEN");
        a2.setStatus("FROZEN");
        a3.setStatus("FROZEN");

        em.flush();   // push all 3 UPDATEs to the DB
        em.clear();   // detach ALL entities -- context is now empty

        // a1, a2, a3 are all DETACHED
        // a1.getTransactions() would throw LazyInitializationException
    }
}
TEXT
Expected behavior:
- flush() sends 3 UPDATE statements to the database
- clear() detaches all 3 entities at once
- Persistence context is now empty -- zero memory overhead
- Any reference to a1, a2, a3 is now detached

How is evict different from clear?

The difference is scope: evict/detach removes one entity, clear removes all. Use detach when you want to drop a specific heavy object but keep the rest of the context. Use clear in batch processing where you want to reset after each chunk.

| Method | Scope | API | Use Case |
|--------|-------|-----|----------|
| session.evict(e) | One entity | Hibernate-native | Drop a specific heavy entity |
| em.detach(e) | One entity | JPA standard | Drop a specific entity (portable) |
| em.clear() | All entities | JPA standard | Reset after a batch chunk |

JAVA
// Use detach for one specific entity
em.detach(heavyReport); // drop this 10MB report from the context
// Keep all other entities managed

// Use clear for batch processing
for (int i = 0; i < accounts.size(); i++) {
    em.persist(accounts.get(i));
    if (i % 500 == 0) {
        em.flush();
        em.clear(); // reset entire context for the next chunk
    }
}
TEXT
detach: surgical -- removes one entity, keeps the rest
clear: nuclear -- removes everything, context is empty

Why does a batch loop run out of memory without clear?

The persistence context holds a strong reference to every managed entity plus a snapshot. In a long loop that loads or persists thousands of rows, the context grows without bound until the transaction ends. For a million entities you hold a million objects and a million snapshots, and the JVM runs out of heap.

JAVA
package com.bank.batch;

import com.bank.model.Account;
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;

@Service
public class BatchMemoryDemo {

    private final EntityManager em;

    public BatchMemoryDemo(EntityManager em) {
        this.em = em;
    }

    // WRONG: context grows without bound
    @Transactional
    public void naiveBatch(List<String> accountIds) {
        for (String id : accountIds) {
            Account a = em.find(Account.class, id); // added to context
            a.accrueInterest(); // snapshot held
            // context grows with every iteration -- OOM for large lists
        }
    }

    // CORRECT: flush+clear keeps memory bounded
    @Transactional
    public void fixedBatch(List<String> accountIds) {
        for (int i = 0; i < accountIds.size(); i++) {
            Account a = em.find(Account.class, accountIds.get(i));
            a.accrueInterest();
            if (i % 500 == 0 && i > 0) {
                em.flush();  // push UPDATEs to DB
                em.clear();  // free memory
            }
        }
        em.flush(); // final partial chunk
    }
}
DIAGRAM
Naive approach memory:
  Heap grows linearly: 1000 entities → 2000 → 500000 → OOM

Fixed approach memory:
  Heap oscillates: 500 → flush+clear → 0 → 500 → flush+clear → 0
  Stays flat regardless of total row count

What happens if you detach before flushing?

If you detach an entity before its changes are flushed, those changes are lost. Dirty checking no longer sees the entity, so no UPDATE is generated. The safe pattern is always: flush first, then clear or detach.

JAVA
// WRONG: detach before flush -- changes lost
@Transactional
public void loseChanges() {
    Account a = em.find(Account.class, "SAV2001");
    a.setBalance(new BigDecimal("0.00"));
    em.detach(a);  // change lost! no UPDATE will be generated
}

// CORRECT: flush before detach -- changes preserved
@Transactional
public void preserveChanges() {
    Account a = em.find(Account.class, "SAV2001");
    a.setBalance(new BigDecimal("0.00"));
    em.flush();    // UPDATE sent to DB
    em.detach(a);  // safe -- change already flushed
}
TEXT
WRONG: detach before flush
- Result: balance unchanged in database, change silently lost

CORRECT: flush then detach
- Result: UPDATE accounts SET balance=0.00 WHERE id='SAV2001'

What happens after clear to existing entity references?

After clear(), any entity reference you still hold in a variable is now detached. Touching a lazy association on it throws LazyInitializationException, and calling merge() on it will issue a SELECT then an UPDATE.

JAVA
package com.bank.service;

import com.bank.model.Account;
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class PostClearDemo {

    private final EntityManager em;

    public PostClearDemo(EntityManager em) {
        this.em = em;
    }

    @Transactional
    public void demonstratePostClear() {
        Account a = em.find(Account.class, "SAV2001"); // MANAGED
        em.flush();
        em.clear(); // a is now DETACHED

        // WRONG: lazy access on detached entity
        try {
            a.getTransactions().size(); // LazyInitializationException!
        } catch (org.hibernate.LazyInitializationException e) {
            System.out.println("Cannot access lazy collection: " + e.getMessage());
        }

        // CORRECT: merge back into the context if needed
        Account managed = em.merge(a); // SELECT + managed copy
        managed.getTransactions().size(); // works -- entity is managed again
    }
}
TEXT
Expected output:
Cannot access lazy collection: could not initialize proxy - no Session

What should a senior engineer know about evict, detach, and clear?

A senior engineer uses clear() in batch loops as the standard memory-management tool, always preceded by flush(). They use single-entity detach() sparingly, only when one specific heavy object should be dropped while the rest of the context stays managed. They know that second-level cache entries are not affected by clear(), and that clear() resets the identity map so a subsequent find() for the same id will hit the database again.

JAVA
package com.bank.batch;

import com.bank.model.Account;
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;

@Service
public class SeniorBatchPattern {

    private static final int CHUNK = 500;
    private final EntityManager em;

    public SeniorBatchPattern(EntityManager em) {
        this.em = em;
    }

    @Transactional
    public void processInterest() {
        int processed = 0;
        // Stream or page-based iteration
        for (Account a : fetchNextChunk()) {
            a.accrueInterest();
            processed++;
            if (processed % CHUNK == 0) {
                em.flush();
                em.clear();
                // After clear: identity map is reset
                // find("SAV2001") would hit the DB again
            }
        }
        em.flush(); // final partial chunk
    }

    private Iterable<Account> fetchNextChunk() {
        return em.createQuery("SELECT a FROM Account a ORDER BY a.id", Account.class)
                .setMaxResults(CHUNK)
                .getResultList();
    }
}
TEXT
Senior checklist:
1. Always flush before clear -- detaching unflushed changes loses them
2. After clear, any held reference is detached -- no lazy access
3. clear() resets the identity map -- subsequent find hits the DB
4. Second-level cache is NOT affected by clear()
5. Use detach() for one heavy object; clear() for batch resets
6. Combine with readOnly=true for pure reads to skip snapshots entirely

Visual Overview

DIAGRAM
  Persistence Context Memory

  Before clear:                     After clear:
  ┌────────────────────────┐       ┌────────────────────────┐
  │  Account SAV2001       │       │                        │
  │  + snapshot            │       │  (empty -- all GC'd)   │
  │  Account SAV2002       │       │                        │
  │  + snapshot            │       │                        │
  │  Account SAV2003       │       │                        │
  │  + snapshot            │       │                        │
  └────────────────────────┘       └────────────────────────┘
  Memory: ~6 objects                Memory: 0 objects

  detach(SAV2002) only:
  ┌────────────────────────┐
  │  Account SAV2001       │
  │  + snapshot            │
  │  (SAV2002 removed)     │
  │  Account SAV2003       │
  │  + snapshot            │
  └────────────────────────┘
  Memory: ~4 objects
DIAGRAM
  Batch loop memory profile:

  Heap ▲
       │   ┌─┐   ┌─┐   ┌─┐   ┌─┐
       │  ─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─
       │  flush│clear │     │
       └──────────────────────────► Rows processed
            500  1000  1500  2000

Key Takeaway

The methods evict() and detach() both remove a single entity from the persistence context -- evict() is Hibernate-native and detach() is JPA standard -- while clear() removes every managed entity at once. None of them touch the database row; they only change what the in-memory context is tracking and dirty-checking. When you detach or evict an entity, Hibernate drops its reference and snapshot, so it stops watching for changes and the object becomes eligible for garbage collection.

The practical reason this matters is memory. The persistence context holds a strong reference and a field snapshot for every managed entity, and in a long loop that loads thousands of rows the context grows without bound until the transaction ends. This is the classic cause of OutOfMemoryError in batch jobs. The fix is the flush+clear pattern: process in chunks, call flush() to push SQL to the database, then clear() to detach everything so the heap is reclaimed before the next chunk.

At National Bank of India, Rajesh's month-end interest posting job originally loaded every savings account in one transaction and crashed on heap. He rewrote it to process five hundred accounts at a time, crediting interest with BigDecimal, and after each chunk called flush then clear so memory stayed flat. He reserves single-entity detach() for the case where one heavy report object is no longer needed but the rest of the context should stay managed.

Remember one sentence: evict/detach removes one entity, clear removes all, none touch the database, and always flush before clearing or changes are silently lost.

---

Share
Use ← → arrow keys to navigate