skip to content

Enabling L2 & Providers

Wiring a second-level cache through the JCache bridge and choosing what gets cached. Interviewers ask which entities are worth caching — read-mostly reference data — and how ENABLE_SELECTIVE differs from caching everything.

part ofHibernateoverview, primer and where to startread it →
on this pageshow

questions

5

Hibernate's second-level cache does nothing unless you configure it. Walk through everything required to make one entity class actually cached: the configuration properties, the extra library, and the annotations on the class.

level: juniorimportance: must knowfreq 55%

answer

  1. use_second_level_cache=true → master switch
  2. RegionFactory = SPI, JCache = usual bridge
  3. @Cacheable (JPA) + @Cache (Hibernate usage/region)
  4. L2 is by-id: find/proxy/many-to-one only
  5. missing_cache_strategy default = fail

basics

~10 s

Three things. Set hibernate.cache.use_second_level_cache=true. Plug in a RegionFactory backed by a real provider (usually the JCache bridge plus EhCache, Infinispan or Caffeine). Mark the entity with jakarta.persistence.@Cacheable and Hibernate's @Cache.

solid answer

~50 s

The second-level cache is a **SessionFactory-wide** store of entity state, keyed by entity id, that outlives any single session. Turning it on takes three independent steps: 1. **Enable it**: `hibernate.cache.use_second_level_cache=true`. 2. **Supply an implementation.** Hibernate ships no cache of its own; it delegates to a `RegionFactory`. The common route is the `hibernate-jcache` module (`hibernate.cache.region.factory_class=jcache`) plus a JSR-107 provider on the classpath — EhCache 3, Infinispan, or Caffeine's JCache module — with a provider config file referenced by `hibernate.javax.cache.uri`. 3. **Opt the entity in**: `jakarta.persistence.@Cacheable` marks it eligible under the default `ENABLE_SELECTIVE` shared-cache mode, and Hibernate's `@Cache(usage = ...)` chooses the concurrency strategy and, optionally, the region name. Skip any step and you get a silent no-op or a startup failure: no provider means no caching (or a missing-cache exception), and no annotation means the flag is on while nothing is ever cached. Verify with `hibernate.generate_statistics` and the second-level-cache hit/miss counters rather than by eyeballing logs.

code

properties · 5 lines
properties
hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=jcache
hibernate.javax.cache.uri=classpath:ehcache.xml
hibernate.javax.cache.missing_cache_strategy=fail
hibernate.generate_statistics=true

go deeper

for a junior

Recall the three steps — property, provider library, annotations — and that L2 is shared across sessions while L1 is per session.

for a middle

Add what is actually stored (dehydrated state by id, not object graphs), which access paths hit it, and that collections and queries need their own opt-in.

for a senior

Talk about verifying with statistics per region, the missing-cache strategy, region naming/prefixes, and why a cold benchmark shows no gain.

for a principal

Frame it as a correctness-and-capacity decision: which providers fit the deployment topology, heap budget per region, and whether shared mutable cached state is acceptable at all.

## Two different caches Every `Session`/`EntityManager` owns a **first-level cache** — the persistence context — which lives only as long as that session and guarantees one object instance per id within it. It is always on and cannot be disabled. The **second-level cache (L2)** sits behind it at `SessionFactory` scope: it is shared by every session in the JVM (and, with a clustered provider, potentially across nodes). It stores **dehydrated entity state** — an array of column-ish values plus the version — keyed by entity id, not managed object graphs. On a cache hit Hibernate builds a fresh entity instance for the current session from that state, so cached instances are never shared between sessions. Crucially, L2 is a **by-id cache**. `em.find(Product.class, 7L)`, lazy proxy initialisation, and many-to-one navigation can be served from it. An arbitrary JPQL query cannot: a query goes to the database unless the separate query cache is also enabled. ## Step 1 — the master switch `hibernate.cache.use_second_level_cache=true`. Historically this defaulted differently across bootstrap modes; treat it as required and set it explicitly. On its own it caches nothing. ## Step 2 — a provider behind a RegionFactory Hibernate defines an SPI, `org.hibernate.cache.spi.RegionFactory`, and asks it for **regions** — named key/value stores. Implementations are separate artifacts: - `hibernate-jcache` implements the SPI over **JSR-107 (JCache)**, so any JCache provider works: EhCache 3, Infinispan, Caffeine (via `caffeine-jcache`). - Infinispan also ships a **native** Hibernate region factory with clustering features beyond the JCache API. With JCache you set `hibernate.cache.region.factory_class=jcache`, put exactly one provider on the classpath (or name it with `hibernate.javax.cache.provider`), and point `hibernate.javax.cache.uri` at the provider's own config file. Hibernate then calls `cacheManager.getCache(regionName)` for each region. If that cache is not declared, modern Hibernate **fails at startup** rather than silently creating one — `hibernate.javax.cache.missing_cache_strategy` (default `fail`) also accepts `create-warn` and `create`. That default exists precisely because an auto-created, unbounded region is a memory leak waiting to happen. ## Step 3 — opting entities in Under the default shared-cache mode (`ENABLE_SELECTIVE`), an entity participates only if annotated. Two annotations are in play and they are not the same thing: - `jakarta.persistence.@Cacheable` — the portable JPA marker meaning “this entity may be cached”. - `org.hibernate.annotations.@Cache` — Hibernate's own, carrying `usage` (the concurrency strategy: read-only, read-write, nonstrict-read-write, transactional), an optional `region` name, and `includeLazy` control. Hibernate treats `@Cache` as implying cacheability, but writing both is the clear, portable habit. Without `@Cache`, the strategy falls back to `hibernate.cache.default_cache_concurrency_strategy`, which is easy to get wrong — pick it deliberately per entity. Also note what `@Cacheable` does **not** cover: `@OneToMany`/`@ManyToMany` collections are separate regions requiring their own `@Cache` on the field, and query results need the query cache. A very common bug report — “L2 is on but I still see SELECTs” — is really “my access path was a query or an uncached collection, not `find()` by id”. ## Verifying it works Set `hibernate.generate_statistics=true` and read `SessionFactory.getStatistics()`: `getSecondLevelCacheHitCount()`, miss count, and put count, ideally per region. A region with puts and no hits is pure overhead. Do not judge by SQL logs alone — the first load of every entity is always a miss, so a cold benchmark shows no benefit at all. ## Naming By default a region is named after the fully-qualified entity name (`com.acme.shop.Product`); collection regions append the field (`com.acme.shop.Product.tags`). `hibernate.cache.region_prefix` prefixes them all, which matters when several applications share one cache manager.

  • You enabled every property and annotated the entity, yet a list query still hits the database on every call. Why?
    The second-level cache is keyed by entity identifier, so it only serves id-based access paths: `find()`, proxy initialisation, and many-to-one navigation. A JPQL or Criteria query is always executed against the database unless the separate query cache is enabled and the query is marked cacheable. Even then the query cache stores identifiers and relies on the entity region to hydrate them.
  • What is the difference between jakarta.persistence.@Cacheable and Hibernate's @Cache annotation?
    `@Cacheable` is the portable JPA marker that says the entity is eligible under `ENABLE_SELECTIVE`; it carries no policy. `@Cache` is Hibernate-specific and carries the concurrency strategy (`READ_ONLY`, `READ_WRITE`, `NONSTRICT_READ_WRITE`, `TRANSACTIONAL`) and the region name. Hibernate accepts `@Cache` alone as implying cacheability, but annotating both keeps intent explicit and the strategy chosen deliberately rather than inherited from a global default.

The first-level cache is the notepad on one clerk's desk, thrown away when their shift ends; the second-level cache is the filing cabinet in the middle of the office that every clerk shares.

saying these in an interview costs you the question

  • Confusing it with the first-level cache and claiming L2 gives repeatable reads inside one session
  • Thinking hibernate.cache.use_second_level_cache=true alone caches anything without a provider or annotations
  • Believing L2 caches query results — it caches entity state by id
  • Assuming collections and queries are covered by the entity's @Cacheable
  • Expecting Hibernate to auto-create missing regions; modern JCache setups fail startup by design

context

open as a page

The JPA property jakarta.persistence.sharedCache.mode accepts ENABLE_SELECTIVE, DISABLE_SELECTIVE, ALL and NONE. Explain what each value means for which entities get cached, and which one you would run in production.

level: middleimportance: must knowfreq 42%

basics

~10 s

ENABLE_SELECTIVE (the practical default) caches only entities marked @Cacheable. DISABLE_SELECTIVE caches everything except @Cacheable(false). ALL caches every entity regardless of annotations, NONE caches none. Production should use ENABLE_SELECTIVE — opt in deliberately.

open as a page

Hibernate implements no cache itself — it delegates to a provider through a RegionFactory. Explain how that plug-in point works, and how you would choose between EhCache, Infinispan and Caffeine behind the JCache (JSR-107) bridge.

level: seniorimportance: should knowfreq 33%

basics

~20 s

RegionFactory is Hibernate's SPI for named cache regions; hibernate-jcache adapts it to any JSR-107 provider. Caffeine is a fast in-JVM cache, EhCache adds sizing/disk tiers, Infinispan adds real clustering — pick by deployment topology, not benchmarks.

open as a page

How do you control the maximum size and expiry of one individual Hibernate second-level cache region, and how do you work out which region name corresponds to a given entity or collection?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Regions default to the fully-qualified entity name; collection regions append the field name. Override with @Cache(region="..."), prefix all with hibernate.cache.region_prefix. Size and TTL are set per region in the provider's config file, not by Hibernate.

open as a page

You are deciding which parts of a domain model should get a shared second-level cache and how much heap to budget for it. How do you make that call, and what would make you leave the second-level cache switched off entirely?

level: principalimportance: should knowfreq 28%

basics

~20 s

Cache small, hot, rarely-written, id-accessed data whose staleness the domain tolerates. Skip write-heavy, huge, or externally-written tables. Leave L2 off entirely when the database is not the bottleneck, or when a correctness-critical read cannot tolerate any staleness window.

open as a page