The Hibernate first level cache is the persistence context, which is a map of entity objects that every Session or EntityManager keeps while it is open. When we load an entity by its id, Hibernate looks in this map first and runs a SELECT only when the entity is not there yet. The cache is always on and belongs to one EntityManager, so it is gone when that EntityManager closes.
We rely on the first level cache to save queries inside one unit of work, such as one web request or one transaction, but not across the application.
The following example declares a plain Movie entity and loads the same movie three times in one transaction.
@Entity
public class Movie {
@Id
@GeneratedValue
private Long id;
private String title;
private int releaseYear;
private double rating;
}
emf.runInTransaction(em -> {
Movie first = em.find(Movie.class, 1L); // select ... from Movie m1_0 where m1_0.id=?
Movie second = em.find(Movie.class, 1L); // no SQL, taken from the cache
boolean same = first == second; // same = true
em.detach(first); // removes Movie#1 from the cache
Movie third = em.find(Movie.class, 1L); // select ... (runs again)
});
Notice that only the first and the third find() run a SELECT, because detach() removes Movie#1 from the cache between them.
Next, we see how the cache works inside the persistence context and test each case with an SQL counter. After that, we cover queries, stale data, detaching and memory growth in long sessions.
1. How the First Level Cache Works
An EntityManager is the object we use to load and save entities, and Hibernate’s Session extends it. Each EntityManager owns a persistence context, which is a set of entity instances with only one instance per database identity. For every managed entity, the context holds two things.
- The entity object itself, stored under a key made of the entity class and the id, such as Movie#1.
- A copy of the values loaded from the database. At flush time, Hibernate compares the object with this copy to find changes (dirty checking) and writes an UPDATE only for changed entities.
Every lookup by id goes through the context first. A hit returns the object we already have, whereas a miss runs one SELECT and stores the result for the next lookup.

Not every operation reads the cache, but every operation that returns a managed entity writes to it.
| Operation | Reads the cache first? | Stores the result in the cache? |
|---|---|---|
| em.find(Movie.class, id) | Yes, no SQL on a hit | Yes |
| em.getReference(Movie.class, id) | Yes, returns the cached instance | Yes |
| JPQL query | No, the SQL always runs | Yes, and returns cached instances for rows it already has |
| em.persist(movie) | Not a lookup | Yes, before the INSERT runs |
| Bulk update query | No | No, cached objects keep their old values |
Hibernate calls this cache “first level” because there is an optional second-level cache behind it. The second-level cache is shared by all EntityManager instances, and section 3.4 compares the two.
2. First Level Cache Example
Our example is a small movie catalog that runs on Hibernate 7.4.11, Java 25 and an in-memory H2 database. The example saves three movies and runs each case of this section, counting the SQL that reaches the database.
2.1. The Movie Entity and an SQL Counter
The Movie entity from the introduction needs no cache setting. The @GeneratedValue annotation uses a database sequence, so the three movies get the ids 1, 2 and 3.
| id | title | releaseYear | rating |
|---|---|---|---|
| 1 | Inception | 2010 | 8.8 |
| 2 | Up | 2009 | 8.3 |
| 3 | Coco | 2017 | 8.4 |
To prove a cache hit, we count statements instead of reading logs. A StatementInspector is a Hibernate callback that receives every SQL string before Hibernate prepares it. Our SqlCounter records the string and returns it unchanged.
private static final List<String> STATEMENTS = new CopyOnWriteArrayList<>();
@Override
public String inspect(String sql) {
STATEMENTS.add(sql);
return sql;
}
public static long selects() {
return STATEMENTS.stream().filter(s -> s.startsWith("select")).count();
}
We register the inspector when we build the EntityManagerFactory with HibernatePersistenceConfiguration.
new HibernatePersistenceConfiguration("first-level-cache")
.managedClasses(Movie.class)
.jdbcUrl("jdbc:h2:mem:movies;DB_CLOSE_DELAY=-1")
.showSql(true, false, false)
.property(AvailableSettings.STATEMENT_INSPECTOR, new SqlCounter())
.createEntityManagerFactory();
2.2. Loading the Same Movie Twice
Inside one transaction, the second find() call for the same id sends no SQL. It also returns the very same Java object, so == is true.
emf.runInTransaction(em -> {
Movie first = em.find(Movie.class, 1L);
Movie second = em.find(Movie.class, 1L);
boolean same = first == second; // same = true
boolean managed = em.contains(first); // true
});
long selects = SqlCounter.selects(); // 1
Hibernate: select m1_0.id,m1_0.rating,m1_0.releaseYear,m1_0.title from Movie m1_0 where m1_0.id=?
The identity guarantee matters beyond speed. For example, a review service and a rating service that both load Movie#1 in one transaction change one object, so one change cannot overwrite the other at flush.
2.3. Each EntityManager Has Its Own Cache
The cache belongs to one EntityManager. A second EntityManager starts with an empty context, so it runs its own SELECT and builds its own object.
try (EntityManager em1 = emf.createEntityManager();
EntityManager em2 = emf.createEntityManager()) {
Movie a = em1.find(Movie.class, 1L); // select ... where m1_0.id=?
Movie b = em2.find(Movie.class, 1L); // select ... where m1_0.id=? (again)
boolean same = a == b; // same = false
boolean managed = em2.contains(a); // false
}

The same happens when one EntityManager closes and the next one starts. Two calls to emf.runInTransaction() that each load Movie#1 run two SELECT statements, because each call opens and closes its own EntityManager. In a web application, we get one cache per request or per transaction.
An EntityManager is not thread-safe, so its cache is never shared between threads. The SessionFactory (the EntityManagerFactory) is the thread-safe object we share.
2.4. persist() Adds the Entity to the Cache
A new entity enters the cache as soon as we call persist(), before the INSERT runs. A find() with its id returns the same object without any SQL.
emf.runInTransaction(em -> {
Movie soul = new Movie("Soul", 2020, 8.0);
em.persist(soul); // no SQL yet, the id comes from a preallocated sequence block
Movie found = em.find(Movie.class, soul.getId());
boolean same = found == soul; // same = true
List<String> sent = SqlCounter.statements(); // []
}); // insert into Movie (rating,releaseYear,title,id) values (?,?,?,?)
2.5. Queries Always Run SQL
A JPQL query cannot use the first level cache for lookups, because Hibernate cannot know which rows match the where clause without asking the database. So the SQL runs every time, even for a movie we already loaded.
Movie found = em.find(Movie.class, 1L); // select ... where m1_0.id=?
Movie queried = em.createQuery("from Movie where title = :title", Movie.class)
.setParameter("title", "Inception")
.getSingleResult(); // select ... where m1_0.title=?
boolean same = queried == found; // same = true
// the same query again // select ... where m1_0.title=?
long selects = SqlCounter.selects(); // 3
The other direction saves SQL. A query stores every entity it returns, so a later find() for one of them runs no SQL.
List<Movie> all = em.createQuery("from Movie order by id", Movie.class)
.getResultList(); // select ... from Movie m1_0 order by m1_0.id
Movie up = em.find(Movie.class, 2L); // no SQL
boolean inList = all.contains(up); // true
long selects = SqlCounter.selects(); // 1
2.6. Stale Data and refresh()
Here the cache can surprise us. When a row changes in the database after we loaded it, for example through a bulk update or a stored procedure, the cached object keeps the old values. A query that reads the new row still returns the cached object with the old values, because Hibernate never overwrites a managed entity with query results.
Movie inception = em.find(Movie.class, 1L); // rating = 8.8
em.createQuery("update Movie set rating = 9.0 where id = :id")
.setParameter("id", 1L)
.executeUpdate(); // update Movie m1_0 set rating=9.0 where m1_0.id=?
double cachedRating = em.find(Movie.class, 1L).getRating(); // 8.8, no SQL
double queriedRating = em.createQuery("from Movie where id = :id", Movie.class)
.setParameter("id", 1L)
.getSingleResult().getRating(); // 8.8, although the SELECT ran
em.refresh(inception); // select ... where m1_0.id=?
double freshRating = inception.getRating(); // 9.0
The method refresh() reads the row again and overwrites the object, including any change we made to it and did not flush. It works only on a managed entity; a detached one throws an IllegalArgumentException.
2.7. Removing Entities With detach(), evict() and clear()
We cannot turn the cache off, but we can take entities out of it. A removed entity becomes a detached entity, which means the object still exists in our code, but Hibernate no longer tracks it. The methods differ only in how much they remove.
| Method | API | What it removes |
|---|---|---|
| em.detach(movie) | Jakarta Persistence | One entity |
| session.evict(movie) | Hibernate Session | One entity, same as detach() |
| em.clear() | Both | Every entity in the context |
| em.contains(movie) | Both | Nothing; returns true when the entity is in the context |
A detached movie is loaded again on the next find(), as a new object.
Session session = em.unwrap(Session.class);
Movie inception = em.find(Movie.class, 1L); // select ...
Movie up = em.find(Movie.class, 2L); // select ...
int before = session.getStatistics().getEntityCount(); // 2
em.detach(inception);
boolean managed = em.contains(inception); // false
Movie again = em.find(Movie.class, 1L); // select ... (third SELECT)
boolean same = again == inception; // same = false
session.evict(up);
boolean upManaged = em.contains(up); // false
em.clear();
int after = session.getStatistics().getEntityCount(); // 0
Changes we did not flush are lost when we call clear() or detach(). Hibernate writes changes at flush time, and an entity that left the context is not part of that flush. We call flush() first when the changes must be saved.
emf.runInTransaction(em -> {
em.find(Movie.class, 2L).setTitle("Up!");
em.clear();
}); // no UPDATE, the title stays "Up"
emf.runInTransaction(em -> {
em.find(Movie.class, 2L).setTitle("Up!");
em.flush(); // update Movie set rating=?,releaseYear=?,title=? where id=?
em.clear();
}); // the title is now "Up!"
2.8. Memory Growth in Long Sessions
The cache never removes anything by itself while the EntityManager is open. A job that saves or reads thousands of entities in one transaction keeps every one of them, plus the copy of its loaded values, until the transaction ends. Saving 1000 movies in one transaction leaves 1000 entities in the context. Dirty checking at flush compares all of them, and a large job can run out of heap memory.
The fix is the pattern used for batch inserts, which flushes and clears the context every N entities. We use 50, the same value as hibernate.jdbc.batch_size in the example, so each flush sends the 50 inserts as one JDBC batch.
emf.runInTransaction(em -> {
for (int i = 1; i <= 1000; i++) {
em.persist(new Movie("Movie " + i, 2000 + i % 25, 7.0));
if (i % 50 == 0) {
em.flush(); // writes the 50 inserts
em.clear(); // removes them from the cache
}
}
});
// most entities in the context: 1000 without clear(), 50 with clear()

After clear(), every entity loaded before it is detached, so we keep only ids across the loop, not entity objects, and load them again with find() when needed.
The complete project on GitHub runs every case from section 2 and prints the SQL, and 24 JUnit tests check the results (mvn -q compile exec:java, mvn test).
3. First Level Cache FAQs
3.1. Can We Disable the First Level Cache in Hibernate?
No. Every Session and EntityManager has a persistence context, and there is no setting to turn it off. When we need to work without one, Hibernate offers StatelessSession, which has no persistence context and therefore no dirty checking and no cascades.
SessionFactory sf = emf.unwrap(SessionFactory.class);
try (StatelessSession stateless = sf.openStatelessSession()) {
Movie a = stateless.get(Movie.class, 1L); // select ... where m1_0.id=?
Movie b = stateless.get(Movie.class, 1L); // select ... where m1_0.id=? (again)
boolean same = a == b; // same = false
}
We use it for large imports and exports, where the cache only costs memory, and we write changes with explicit insert() and update() calls.
3.2. How Do We Know Whether Hibernate Used the Cache?
Hibernate statistics count the real loads. We turn them on with hibernate.generate_statistics and compare the number of find() calls with the number of entity loads.
Statistics stats = emf.unwrap(SessionFactory.class).getStatistics();
emf.runInTransaction(em -> {
for (int i = 0; i < 5; i++) {
em.find(Movie.class, 1L);
}
});
long loads = stats.getEntityLoadCount(); // 1
long statements = stats.getPrepareStatementCount(); // 1
For a single object, em.contains(movie) answers whether it is in the cache, and session.getStatistics().getEntityCount() returns how many entities the current context holds. Hibernate statistics also count query executions and collection loads.
3.3. What Replaced Session.load() in Hibernate 7?
The old Session.load(Class, id) method is gone in Hibernate 7, and session.get() is deprecated. Their Jakarta Persistence replacements read the first level cache the same way.
| Old Hibernate API | Replacement | Behavior |
|---|---|---|
| session.get(Movie.class, id) | em.find(Movie.class, id) | Returns the entity, runs a SELECT on a cache miss |
| session.load(Movie.class, id) | em.getReference(Movie.class, id) | Returns a proxy without SQL, or the cached entity if it is already loaded |
Movie found = em.find(Movie.class, 1L); // select ...
Movie ref = em.getReference(Movie.class, 1L); // no SQL
boolean same = ref == found; // same = true
3.4. What Is the Difference Between First and Second Level Cache?
The two caches work at different scopes. The first level cache avoids repeated loads inside one unit of work, while the second-level cache avoids repeated loads across EntityManager instances and must be set up with a cache provider such as Ehcache.
| First level cache | Second level cache | |
|---|---|---|
| Scope | One Session or EntityManager | The whole SessionFactory |
| Enabled | Always, cannot be disabled | Off by default, enabled per entity with @Cache or @Cacheable |
| Shared between threads | No | Yes |
| Lifetime | Until the EntityManager closes or is cleared | Until eviction or expiry in the cache provider |
| What it stores | The entity objects and their loaded values | Entity data in a “dehydrated” form (a tuple of column values), not objects |
| Same object for two lookups | Yes, == is true | No, each EntityManager builds its own object |
| Remove entries | em.detach(), em.clear() | emf.getCache().evict(), evictAll() |
A lookup by id checks the first level cache, then the second level cache if it is enabled, and only then the database.
4. Conclusion
The first level cache is the persistence context of one EntityManager. A lookup by id returns the cached object without SQL, whereas a query always runs SQL but returns the cached objects. When an object is stale, refresh() reloads it.
We remove entities with detach() or clear() (evict() on a Session) and flush before clearing. In long transactions, we clear in batches so the context does not hold thousands of entities in memory.
5. References
- Jakarta Persistence 3.2 specification: Entity Operations
- EntityManager JavaDoc (Jakarta Persistence 3.2)
- Hibernate ORM 7.4 User Guide: Working with detached data
- Hibernate ORM 7.4 User Guide: Session batching
- StatelessSession JavaDoc (Hibernate 7.4)
Happy Learning !!
Hi ,
What happened to the object stored in the first level cache if another session updated it in the Data Base?
I have the same doubt. Will the cached object gets updated?
Also can we use merge() method to update the cached object? If so why use evict() when we can use merge() ? Any replies would be truly appreciated
If there is some direct DB update, session will not know it and no cache update will happen. Merge method will overwrite in the DB again. Based on whether you want to fetch latest data from database (refresh method) or overwrite database again with data stored in entity (merge method) – choose accordingly. Read more about Merging and Refreshing Entities.
if you want to store only update fields you can provide “dynamic-update = true” in .hbm .xml file during configuration.
So, if session load object update A field, it will only update a field.
I have few doubts regarding hibernate cache.
1. Where the cached object is stored exactly? In which memory?
2. Lets say I am loading an object first time using load method. It will be cached in session.
Next time in the same session, I am trying to query same object with a hql query with some criteria. Will it be fetched from cache or database?
1) In RAM/virtual memory.
2) database.
how it will be called second time hit the databse?
Make it final.
lokesh
first
user 1 execure query thru hibernate
“select * from dept where id =40?”
then again user 2 query thru hibernate
“select * from dept where id =50?”
how does hibenate figure out if it needs to go to cache or go to databsae?
in this case obsivlosuly it needs to go to DB
i mean
user 1 execute query thru hibernate
“select * from dept where id =40?”
then again user 1 query thru hibernate
“select * from dept where id =50?”
java.lang.ClassNotFoundException: org.hibernate.cache.EhCacheProvider
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1714)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1559)
at java.lang.Class.forName0(Native Method)
Very brief and clear explanination, thanks for the artical.
To dig the issue (intoduced by Kartthik) further ,
How Transactions are managed to avoide updates on stale objects in hibernate.
HI Lokesh,
I am using Hibernate second level cache. But I have some points
1. for some entities i required cache should be created at the server startup. How I can achieve it with hibernate and ehcache.
2. In the session.load method we required Primary key for searching. But User never know the primary key they only know the Unique key like some code of the entity. then how I will get it from session.
I hope you understand my problem.
Thanks.
very instructive, thank you so much.
Now I’m going to read your explanation about second level.
I’m having many problems in an clustered production enviroment, many heavy queries.
Hi Lokesh, Can you please explain. How Session cache is working internally. I am asking about internal implementation of this cache mechanism.
Thanks
I built a git hub repository with the codes: https://github.com/mbgarcia/hibernate-first-cache.
I appreciate your efforts.
How will session cache object get update from DB if any changes happen. Will get update automatically or explicitily.?
In case of single session, your application code always update this cache object so it is always updated. Hibernate takes care of synchronizing the changes with database at time of flushing the session.
Now consider another example:
machine 1: reads the data and returns it for editing somewhere else
machine 2: also reads the data for modification
machine 1: updates the data and commits.
machine 2: tries to do an update and commit.
What will happen when the second machine tries to commit its changes? Hibernate will see that the data has changed while machine 2 was working on the data. That is, machine 2’s update is on stale data. Hibernate can’t always merge the two changes (nor is that always desired behaviour), so it rejects the second update by throwing org.hibernate.StaleObjectStateException.
The simplest solution, perhaps, is to add a version field using @Version on your data objects. Hibernate will automatically maintain the “version” of the data. Whenever an update takes place, the version will be changed automatically by Hibernate. Your job is the check that the version hasn’t changed between when you read the data and when you update the data. If they don’t match, you can do something to handle the problem (i.e. tell the user). There are some more sophisticated techniques for preventing concurrent updates, but this is the simplest.
firs level cache provided by session or SessionFactory ?
SessionFactory provides session, session maintains cache.
It’s already here: https://howtodoinjava.com/hibernate/how-hibernate-second-level-cache-works/
Thanks for your brief explanation on this.However I had a doubt on how this can be useful in real time scenario where I have 2 controllers and in each controller , it is hitting the database through opening the new session object,getting the results and immediately closing the session since it not possible to open the session until second controller results. So in those scenarios where it using the First Level Cache not hitting the database for second controller.
How to use First Level cache if we need to hit the database for same data from more than one place in application. for example in application I have 10 pages, 1st page, I hit the database and while retrieving the results from database by default it stored it in cache with in that session.
Again, I need to use same data in 5th page, how can I retrieve data from cache instead of database since it will open another session
best approach, use second level cache.
In My application, I have 10 pages and in 1st page I hit the database and retrieve the results directly from database and this time it used first level cache by default.
Again I need to use same data in 5th page, how can I use from cache instead of database.
you cannot. use second level cache.
Thank you very much. WIth your explanation I being able to resolve my issue, but, I used SessionFactory.openStatelessSession() to get a “fresh” entity.
Hi Lokesh, I m stuck in 1 issue,
i m goin to delete multiple object in dao like ..
sf.getCurrentSession.delete(object1);
sf.getCurrentSession.delete(object2);
sf.getCurrentSession.delete(object3);
And getting following exception :
org.hibernate.nonuniqueobjectexception a different object with the same identifier
Pls help in resolving the issue.
Can you please share the code is possible. That will be much better. Without code it’s very hard to guess.
Basically, what hibernate is saying is that you have two objects which have the same identifier (same primary key) but they are not the same object.
Example is good, but every where you copied same out put. If you use evict() 0r clear() it should display three select queries in output.
If object entity is in cache ,, how ot will get update if any chages is done in backend ??
That’s where versioning comes into picture. Will detail out soon about entity versioning.
Good stuff, did u detail out regarding entity versioning, curious to read on.