Guide
Using Hibernate Second‑Level Cache with Ehcache to Reduce DB Load in Read‑Heavy Applications
Practical steps to enable Hibernate’s second‑level cache with Ehcache, configure TTL, and verify hit ratios while respecting consistency boundaries.
Published by Tasadduq Burney
19 Mar 2026, 02:51 UTC
3 min129.4K views0

Problem: Unnecessary database round‑trips for frequently read data
Read‑heavy services often issue the same SELECT statements repeatedly, increasing latency and DB load. When the underlying data changes infrequently, a shared in‑memory cache can serve many requests without hitting the database.
Smallest suitable design
- Add the Ehcache region factory dependency (e.g.,
org.hibernate:hibernate-ehcache). - Enable Hibernate’s second‑level cache in
persistence.xmlorapplication.properties:hibernate.cache.use_second_level_cache=true org.hibernate.cache.ehcache.EhcacheRegionFactory - Annotate the entity that represents the read‑only or infrequently updated data:
@Entity @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) // or READ_WRITE if updates occur via Hibernate public class Product { ... } - Provide an
ehcache.xml(or programmatic configuration) that defines a cache region for the entity, e.g.,<ehcache> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" /> <cache name="com.example.Product" maxElementsInMemory="5000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" /> </ehcache>
Trust and data boundaries
- The second‑level cache lives in the same JVM (or, with Terracotta, across a cluster). It is trusted only for data that originates from the same persistence unit.
- Any update that bypasses Hibernate—native SQL, JDBC calls, or external systems—does not automatically invalidate the cache. You must either evict the affected region explicitly or configure a synchronized clustering mechanism.
- For data that is updated frequently via Hibernate, use
READ_WRITE strategy; for truly read‑only data,READ_ONLY avoids the overhead of version checks.
Operational checks
- Enable Hibernate statistics to monitor cache effectiveness:
hibernate.generate_statistics=true - Log cache region access (e.g., via
org.hibernate.cacheat DEBUG level) and compute hit ratio:hits / (hits + misses). Aim for a ratio that meets your SLA (commonly >80% for read‑heavy workloads). - Verify eviction matches TTL: after inserting a known value, wait longer than
timeToLiveSecondsand confirm that a subsequent read results in a cache miss and a DB hit. - Test invalidation after a bulk update performed through Hibernate: update an entity, then read it again and ensure the new value appears immediately (for
READ_WRITE) or after the TTL expires (forREAD_ONLY with manual eviction). - On application restart, confirm that pre‑loaded data is either cleared or repopulated according to
ehcache.xmlsettings; no stale data should survive across redeploys unless you explicitly enable persistent storage.
Failure modes and conditions that would change the design
- High write‑to‑read ratio: If updates occur as frequently as reads, the cache spends more time invalidating or updating entries than serving hits, increasing overhead. In this case disable the second‑level cache or switch to a transactional cache (e.g., Hazelcast with strong consistency).
- Distributed transactions without proper replication: When multiple JVMs update the same entity and the cache is not clustered (or uses asynchronous replication), stale reads can appear. Either enable a clustered cache with synchronous replication or disable the second‑level cache for those entities.
- Strong consistency requirement: If the business cannot tolerate any stale read (e.g., financial balances), the second‑level cache must be avoided or used only with
READ_WRITE and a very short TTL, coupled with explicit eviction on every update. - External data sources: When data is modified outside the persistence unit (batch jobs, other services, direct DB updates), the cache becomes a source of inconsistency unless you implement a cache‑eviction hook or use a cache‑coherency mechanism like JMS invalidation.
Practical verification checklist
- Start the application with statistics enabled.
- Warm‑up the cache by exercising the typical read path (e.g., via a load test).
- Check the logs for Hibernate statistics output; confirm that the region’s hit ratio meets the target.
- Perform a known update through Hibernate and verify that the subsequent read reflects the change according to the configured concurrency strategy.
- Restart the node and ensure that the cache behaves as defined in
ehcache.xml(cleared or re‑populated).
By following this architecture note, you can safely introduce Hibernate’s second‑level cache with Ehcache to reduce database load while keeping visibility into consistency guarantees and operational health.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.