Full Stack • Java • System Design • Cloud • AI Engineering

Dirty Checking in Hibernate

Complete guide to Hibernate Dirty Checking mechanism with lifecycle, internal workflow, examples, performance considerations, and interview questions.

What is Dirty Checking?

Dirty Checking is one of Hibernate's most powerful features.

It automatically detects changes made to a managed entity and synchronizes those changes with the database during transaction commit.

Instead of writing:

employeeRepository.update(employee);

Hibernate automatically executes the UPDATE statement.

This significantly reduces boilerplate code.


Real Life Example

Imagine you open a Google Doc.

You make some changes.

You don't click "Save" every second.

Google automatically detects modifications and saves them.

Hibernate Dirty Checking works exactly the same way.

Google Docs
     |
Detect Changes
     |
Auto Save

Hibernate
     |
Detect Changes
     |
Auto Update DB

Why Dirty Checking?

Without Dirty Checking:

Employee employee = repository.findById(1L).get();

employee.setSalary(100000);

repository.save(employee);

Developer must remember to save every change.

With Dirty Checking:

Employee employee = repository.findById(1L).get();

employee.setSalary(100000);

// No save()

Hibernate updates database automatically.


Prerequisites

Dirty Checking works only when:

✅ Entity is Managed

✅ Inside Persistence Context

✅ Transaction is Active

❌ Detached Entity

❌ Closed Session

❌ No Transaction


Entity States Refresher

stateDiagram-v2

[*] --> Transient

Transient --> Managed : persist()
Managed --> Detached : session close
Detached --> Managed : merge()

Managed --> Removed : remove()
Removed --> [*]

Dirty Checking only works for:

Managed State

Sample Entity

@Entity
@Table(name = "employees")
public class Employee {

    @Id
    private Long id;

    private String name;

    private Double salary;

    private String department;

    // getters setters
}

Basic Example

Service Layer

@Transactional
public void updateSalary() {

    Employee employee =
            employeeRepository.findById(1L).get();

    employee.setSalary(120000.0);

}

Notice:

employeeRepository.save(employee);

is missing.

Still Hibernate executes:

UPDATE employees
SET salary = 120000
WHERE id = 1;

How Dirty Checking Works Internally

When entity is loaded:

Employee employee =
        repository.findById(1L).get();

Hibernate creates:

Managed Entity

and

Snapshot Copy

Example:

Database

ID=1
Name=Venu
Salary=80000

Hibernate stores:

Managed Object

ID=1
Name=Venu
Salary=80000

Snapshot:

Snapshot

ID=1
Name=Venu
Salary=80000

Entity Modified

employee.setSalary(120000);

Now:

Managed Object

Salary=120000

Snapshot remains:

Snapshot

Salary=80000

During Commit

Hibernate compares:

Managed Object
       VS
Snapshot

If difference found:

UPDATE employees
SET salary = 120000
WHERE id = 1;

This comparison process is called:

Dirty Checking

Internal Workflow Diagram

flowchart TD

A[Load Entity]

B[Create Snapshot]

C[Modify Entity]

D[Transaction Commit]

E[Compare Snapshot]

F[Dirty?]

G[Generate UPDATE]

H[Execute SQL]

I[No SQL]

A --> B
B --> C
C --> D
D --> E
E --> F

F -->|Yes| G
G --> H

F -->|No| I

Example Flow

@Transactional
public void updateEmployee() {

    Employee employee =
            repository.findById(1L).get();

    employee.setDepartment("Architecture");

}

Generated SQL:

select * from employees where id=1;

update employees
set department='Architecture'
where id=1;

No Changes Scenario

@Transactional
public void checkEmployee() {

    Employee employee =
            repository.findById(1L).get();

}

Generated SQL:

select * from employees where id=1;

No update executed.

Because nothing changed.


Dirty Checking with Multiple Fields

@Transactional
public void updateEmployee() {

    Employee employee =
            repository.findById(1L).get();

    employee.setName("Venugopal");
    employee.setSalary(150000.0);
    employee.setDepartment("Platform");
}

Generated SQL:

UPDATE employees
SET
    name=?,
    salary=?,
    department=?
WHERE id=?;

Dirty Checking and Flush

Hibernate performs Dirty Checking during:

flush()

or

transaction commit

Example:

employee.setSalary(150000);

entityManager.flush();

Immediately executes:

UPDATE employees
SET salary=150000
WHERE id=1;

Flush Lifecycle

flowchart LR

A[Entity Loaded]

B[Entity Modified]

C[Flush]

D[Dirty Check]

E[Generate SQL]

F[Commit]

A --> B
B --> C
C --> D
D --> E
E --> F

Dirty Checking with EntityManager

@Transactional
public void updateEmployee() {

    Employee employee =
            entityManager.find(Employee.class, 1L);

    employee.setSalary(100000);

}

No explicit update required.


Dirty Checking Does NOT Work

Detached Entity

Employee employee =
        repository.findById(1L).get();

entityManager.detach(employee);

employee.setSalary(200000);

No update.

Because entity is detached.


Diagram

flowchart LR

A[Managed Entity]

B[detach]

C[Detached Entity]

D[Modify]

E[Commit]

F[No Update]

A --> B
B --> C
C --> D
D --> E
E --> F

Reattach Using Merge

Employee employee =
        repository.findById(1L).get();

entityManager.detach(employee);

employee.setSalary(200000);

entityManager.merge(employee);

Now update happens.


Dirty Checking Performance Impact

Suppose Session contains:

10000 Entities

During flush:

Hibernate compares
all snapshots
with current objects

Can become expensive.


Optimization Techniques

Read Only Transaction

@Transactional(readOnly = true)
public Employee getEmployee() {
    return repository.findById(1L).get();
}

Hibernate skips dirty checking.

Performance improves.


Clear Persistence Context

entityManager.clear();

Removes managed entities.

Useful for large batch jobs.


Batch Processing

for(Employee employee : employees){

    employee.setSalary(1000);

    if(count % 50 == 0){
        entityManager.flush();
        entityManager.clear();
    }
}

Prevents memory issues.


Dirty Checking vs save()

Dirty Checking

employee.setSalary(100000);

Automatic.


Explicit Save

repository.save(employee);

Manual.


Comparison

Feature Dirty Checking save()
Automatic Yes No
Less Code Yes No
Managed Entity Required Not Required
Uses Persistence Context Yes No
Performance Better Depends

Interview Questions

Q1: What is Dirty Checking?

Hibernate mechanism that automatically detects entity changes and updates database during flush/commit.


Q2: When does Dirty Checking occur?

During:

flush()
transaction commit

Q3: Does Dirty Checking work on detached entities?

No.

Only managed entities participate.


Q4: How does Hibernate know entity changed?

Using:

Snapshot Comparison

Current state is compared against original snapshot.


Q5: How can we disable Dirty Checking?

@Transactional(readOnly = true)

or

entityManager.detach(entity);

Best Practices

✅ Keep transactions short

✅ Use readOnly transactions for queries

✅ Flush periodically in batch jobs

✅ Avoid huge persistence contexts

✅ Understand entity states

❌ Don't keep thousands of entities managed

❌ Don't rely on Dirty Checking outside transaction


Summary

Dirty Checking is Hibernate's automatic change detection mechanism.

Workflow:

Load Entity
      ↓
Create Snapshot
      ↓
Modify Entity
      ↓
Flush / Commit
      ↓
Compare Snapshot
      ↓
Generate UPDATE SQL

Key Rule:

Dirty Checking Works Only For Managed Entities
Inside Active Transaction

This is one of the biggest reasons Hibernate reduces boilerplate code and makes database operations simpler.