Definition
- 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.
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
}
}
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.
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
}
}
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.
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
}
}
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 |
// 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
}
}
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.
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
}
}
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.
// 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
}
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.
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
}
}
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.
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();
}
}
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
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
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.
---