Master Hibernate 7 Ehcache 3 Configuration: High-Performance Caching with Jakarta Persistence

Are you struggling with database performance in your modern Java applications? As systems scale, database bottlenecks remain the primary cause of latency. While Hibernate 7 introduces massive improvements in query generation and Jakarta Persistence compatibility, the secret to true high-concurrency performance lies in the Second-Level (L2) Cache. In this guide, we will configure Ehcache 3 — the industry standard for JVM-level caching — as the L2 cache provider for Hibernate 7, using the modern JCache (JSR-107) bridge.

Why Ehcache 3 with Hibernate 7?

Ehcache 3 brings full Jakarta EE compatibility via the JCache (JSR-107) standard, LRU/LFU eviction policies, heap/off-heap/disk tiering, and robust TTL configuration. When paired with Hibernate 7’s hibernate-jcache integration module, it provides a zero-overhead caching layer that sits between your application and the database.

1. Maven Dependencies

<dependencies>
    <dependency>
        <groupId>org.hibernate.orm</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>7.4.3.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate.orm</groupId>
        <artifactId>hibernate-jcache</artifactId>
        <version>7.4.3.Final</version>
    </dependency>
    <dependency>
        <groupId>org.ehcache</groupId>
        <artifactId>ehcache</artifactId>
        <version>3.10.8</version>
        <classifier>jakarta</classifier>
    </dependency>
</dependencies>

2. Hibernate Configuration Properties

hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.jcache.internal.JCacheRegionFactory
hibernate.javax.cache.uri=classpath:ehcache.xml
hibernate.generate_statistics=true

3. ehcache.xml Configuration

<config xmlns="http://www.ehcache.org/v3">
    <cache alias="productCache">
        <expiry>
            <ttl unit="minutes">30</ttl>
        </expiry>
        <resources>
            <heap unit="entries">1000</heap>
            <offheap unit="MB">50</offheap>
        </resources>
    </cache>
    <!-- Hibernate 6/7 query-results region (the old StandardQueryCache name was Hibernate 5) -->
    <cache alias="default-query-results-region">
        <expiry><ttl unit="minutes">10</ttl></expiry>
        <resources><heap unit="entries">200</heap></resources>
    </cache>
    <cache alias="default-update-timestamps-region">
        <expiry><none/></expiry>
        <resources><heap unit="entries">5000</heap></resources>
    </cache>
</config>

4. Annotating the Entity

@Entity
@Table(name = "products")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "productCache")
public class Product {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Double price;
    // Getters and Setters
}

5. Verifying Cache Behaviour

// Session 1: load and cache
try (Session s1 = sessionFactory.openSession()) {
    Product p = s1.get(Product.class, 1L); // SELECT fires
    System.out.println("Loaded: " + p.getName());
}
// Session 2: served from L2 cache
try (Session s2 = sessionFactory.openSession()) {
    Product p = s2.get(Product.class, 1L); // No SQL — cache hit
    System.out.println("Cached: " + p.getName());
}
// Output:
// Loaded: Laptop Pro
// Cached: Laptop Pro
// (Only 1 SELECT in the SQL log — second call is a cache hit)

6. Cache Concurrency Strategy Reference

StrategyUse Case
READ_ONLYStatic reference data (countries, currencies)
READ_WRITEStandard mutable entities — safest default
NONSTRICT_READ_WRITERarely updated data; eventual consistency acceptable
TRANSACTIONALJTA environments requiring full transactional cache consistency

7. Key Pitfalls

  • Missing default-update-timestamps-region: Required for the Query Cache — omitting it causes startup errors.
  • Bulk HQL mutations bypass cache: Always call sessionFactory.getCache().evictEntityData(Product.class) after bulk updates.
  • Query Cache without Entity Cache: Enabling the Query Cache without caching the returned entities causes N+1 selects.

Frequently Asked Questions

Q1: Does the jakarta classifier matter for Ehcache 3?

Yes — critically. Without the jakarta classifier, Ehcache 3 ships with the older javax.cache JCache API. Hibernate 7 requires the jakarta.cache namespace. Using the wrong artifact causes a ClassNotFoundException or NoSuchMethodError at startup.

Q2: How do I configure off-heap memory in Ehcache 3?

Add an <offheap unit="MB">256</offheap> element inside the <resources> block of your cache configuration. Off-heap memory stores serialised data outside the Java heap, avoiding GC pressure for large caches. The entity must implement Serializable for off-heap storage.

Q3: Can I use Ehcache 3 alongside Spring Boot’s own caching?

Yes. Spring Boot’s @Cacheable annotation and Hibernate’s L2 cache are independent layers. You can configure Ehcache as the provider for both by sharing the same ehcache.xml and defining separate cache regions for application-level and Hibernate-level caches.

Q4: What TTL should I set for entity cache regions?

It depends on how frequently the entity changes. For true reference data (countries, currencies): set no expiry or 24 hours. For product catalogues updated daily: 30–60 minutes TTL. For user profiles: 5–15 minutes. Always pair TTL decisions with your cache hit rate from Hibernate statistics — if the hit rate drops off before TTL expires, your data is changing faster than expected.

Q5: How do I test that Ehcache is actually being used?

Enable hibernate.generate_statistics=true and log sessionFactory.getStatistics().getSecondLevelCacheHitCount() and getMissCount(). Also set logging.level.org.hibernate.SQL=DEBUG — if the second get() for the same entity fires a SQL statement, the cache is not working. Common causes: missing @Cacheable on the entity, wrong cache region name, or the Jakarta Ehcache classifier is missing from your POM.

Conclusion

Configuring Ehcache 3 as the Hibernate 7 L2 cache provider is a high-ROI performance investment for any read-heavy application. The setup is straightforward: add the jakarta-classified Ehcache dependency and hibernate-jcache, point Hibernate at your ehcache.xml, annotate cacheable entities with @Cacheable and @Cache, and monitor your hit rate. Use READ_WRITE as the default concurrency strategy for mutable entities, define sensible TTLs aligned with your data change frequency, and always manually evict cache regions after bulk HQL operations. With these pieces in place, your Hibernate 7 application will serve repeated reads from fast heap memory rather than the database, reducing latency and database CPU load dramatically.

Further Reading & Cross-References

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.