Hibernate Second‑Level Cache: When and How to Use It Effectively
Learn how to enable Hibernate’s second‑level cache, configure entity caching, verify hit/miss ratios, and understand the trade‑offs. A practical guide with code snippets, configuration examples, and a checklist for production readiness.
21 Aug 2026, 07:36 UTC

Why a Second‑Level Cache Matters
When a Hibernate application reads the same data repeatedly – think a product catalog or user profile – each read normally issues a round‑trip to the database. The second‑level cache (SLC) stores those entity and collection instances across SessionFactory instances, letting subsequent reads hit memory instead of the database. The result is a dramatic drop in latency and load on the DB, but it comes with extra memory usage and consistency considerations.
Enabling the Cache in Your Project
To get started you need two things:
- A cache provider on the classpath – Ehcache, Infinispan, Hazelcast, or the JCache (JSR‑107) API.
- Hibernate configuration telling it to use the provider and to enable the SLC.
Below is a minimal Maven setup for Ehcache 3.x with Hibernate 6.2:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.2.5.Final</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache-impl</artifactId>
<version>3.10.8</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>6.2.5.Final</version>
</dependency>
In application.properties (or hibernate.cfg.xml) enable the cache and point to the provider:
hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory
hibernate.javax.cache.provider=org.ehcache.jsr107.EhcacheCachingProvider
hibernate.javax.cache.uri=ehcache.xml
hibernate.generate_statistics=true
The ehcache.xml file defines cache regions – logical groupings that let you tune size and eviction policies per entity.
Marking Entities for Caching
Hibernate can cache any mapped entity or collection. Add the annotations on the entity class:
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Product {
@Id
private Long id;
private String name;
// getters/setters
}
For collections, annotate the @OneToMany or @ManyToMany field similarly, or configure via XML. If you need read‑write semantics (updates invalidate cache entries), use READ_WRITE or NONSTRICT_READ_WRITE strategies, but remember that frequent writes reduce the cache hit ratio.
Verifying Cache Activity
Hibernate’s statistics API gives you a quick sanity check. In a test or a servlet you can do:
SessionFactory sf = sessionFactory;
Statistics stats = sf.getStatistics();
stats.setStatisticsEnabled(true);
// Run a query that should hit the cache
try (Session s = sf.openSession()) {
s.get(Product.class, 1L); // first read – cache miss
}
try (Session s = sf.openSession()) {
s.get(Product.class, 1L); // second read – should be a hit
}
System.out.println("Cache hits: " + stats.getSecondLevelCacheHitCount());
System.out.println("Cache misses: " + stats.getSecondLevelCacheMissCount());
If the second read reports a hit, the cache is working. You can also inspect stats.getSecondLevelCachePutCount() to see how many entries were populated.
Trade‑Offs and Limitations
- Memory Footprint: Each cached entity consumes heap space. In a read‑heavy but write‑intensive domain, the cache may grow quickly and trigger GC pauses. Use
maxEntriesLocalHeapin Ehcache or equivalent settings to cap size. - Stale Data: If another application instance updates the database via raw JDBC or a different ORM, the cache will not be aware of the change unless the provider supports automatic invalidation. In such cases you must manually evict or update the cache.
- Clustered Environments: A distributed cache provider (Infinispan, Hazelcast) is required so that all nodes see the same data. A local cache will cause each node to have its own copy, leading to divergence.
- Transaction Isolation: Under
READ_COMMITTEDor higher isolation levels, a transaction might read stale data from the cache if a write has not yet been committed. ChooseREAD_ONLYstrategy for truly immutable data. - Write‑Heavy Workloads: Every update forces a cache eviction or update. The overhead can outweigh the benefit of caching. Profile before enabling on such entities.
Practical Checklist Before Production Roll‑Out
- Enable
hibernate.generate_statisticsin a staging environment and run representative queries. Aim for a hit ratio > 80% for cached entities. - Measure memory usage and GC pause times. Adjust cache region sizes if you see frequent evictions or OOM errors.
- Verify that all application instances use the same cache provider and that the provider is configured for clustering if needed.
- Implement cache eviction hooks for any non‑Hibernate data modifications (e.g.,
entityManager.flush()callbacks or custom DAO methods). - Document the cache strategy per entity so future developers understand when to enable or disable caching.
Conclusion
The second‑level cache is a powerful tool for read‑heavy Hibernate applications, but it is not a silver bullet. By enabling it per entity, tuning region sizes, and verifying statistics, you can gain significant performance improvements. Keep an eye on memory, consistency, and cluster configuration – the most common pitfalls that turn a cache from a performance win into a maintenance headache.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.