An entity is mapped with Hibernate's READ_ONLY second-level cache concurrency strategy, and some code path modifies a managed instance of it and commits. What happens, and why is READ_ONLY the cheapest strategy?
answer
- read-only = insert once, never update
- update attempt → unsupported-operation throw at flush
- delete just evicts the entry
- no locks, no versions, no invalidation chatter
- pairs with @Immutable reference data
basics
~20 sHibernate refuses the cache update and throws (an unsupported-operation error) when flushing the change, failing the transaction. READ_ONLY is cheapest because immutable data needs no locking, no version reconciliation and no invalidation — entries are stored once and served forever.
solid answer
~50 sREAD_ONLY declares the entity immutable *from the cache's point of view*. When Hibernate flushes the modification and tries to push the new state into the read-only region, the access strategy rejects the operation and throws an unsupported-operation exception, so the transaction fails rather than leaving a cache entry that contradicts the database. Deletes are fine — the entry is simply dropped. It is the cheapest strategy because there is nothing to coordinate: no soft locks, no lock timeouts, no eviction-on-write, no version comparison to decide whether a put is allowed, and no invalidation traffic in a cluster. The entry is loaded once and served on every subsequent hit. The practical consequence is that READ_ONLY is a *design assertion*: pick it only for data that truly never changes after insert — reference tables, currencies, country codes, immutable historical records. If such data changes at deploy time only, evicting the region on deploy is an acceptable pattern.
code
java · 8 lines@Entity
@Immutable
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Currency {
@Id private String code;
private String displayName;
}go deeper
Know that READ_ONLY means the entity is never updated, that trying to update it fails loudly, and that it is the fastest strategy.
Explain why it is fastest — no soft locks, no version reconciliation, no invalidation — and name the kinds of tables that qualify.
Treat the exception as a modelling signal: decide whether the entity is genuinely immutable, and describe evicting regions on release for reference-data changes.
Position immutable reference data as an architectural choice — version-by-insert instead of edit-in-place — which unlocks the cheapest caching and removes invalidation traffic across the cluster.
## What READ_ONLY actually means Hibernate's second-level cache is a shared store outside the database, so every strategy is a rule for reconciling that store with committed database state. READ_ONLY takes the simplest possible position: this entity's rows are never updated, so there is nothing to reconcile. You declare it with `@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)` on the entity. Because the assumption is baked into the strategy, Hibernate enforces it. The read-only access strategy implements the "update" and "after update" callbacks by throwing `UnsupportedOperationException` (the classic message is along the lines of *can't write to a readonly object*). So a dirty managed instance flushed inside a transaction will fail: Hibernate detects the change during dirty checking, issues (or prepares to issue) the SQL UPDATE, then attempts to reflect that in the cache region and blows up. The transaction does not complete successfully. This is deliberate — silently keeping the old cached value would be a data-correctness bug that surfaces later and elsewhere, which is far worse than an immediate failure. Removal is treated differently: deleting an entity from a read-only region is handled by simply evicting the entry, since dropping a cached value can never make a reader see something newer than reality — it only forces a database round-trip. ## Why it is the fastest option Every other strategy pays for mutability: - READ_WRITE must install a soft lock before a write, release or replace it after transaction completion, track lock expiry, and compare versions or timestamps before allowing a put. That is multiple cache operations per write and cache misses for readers during the write. - NONSTRICT_READ_WRITE must evict on write, twice, and readers may reload from the database more often than you would like. - TRANSACTIONAL drags a two-phase commit into every write path. READ_ONLY has none of that machinery on the hot path. A lookup is a straight region get; a load populates the region once. Some providers can also avoid defensive copying of entry state, because nothing is going to mutate it. In a cluster there is no invalidation chatter for these entities, which matters more than people expect: invalidation messages, not cache reads, are usually what makes a distributed cache expensive. ## When to choose it Good candidates: currency and country codes, ISO enumerations, tax jurisdictions that change only with a release, product definitions in a system that versions rather than edits them, immutable event or audit rows. Roughly: if a change to the row would normally arrive as a deployment or a data migration rather than as user traffic, READ_ONLY fits. Bad candidates: anything a user can edit, even rarely. "Rarely edited" is the case for NONSTRICT_READ_WRITE, not READ_ONLY — the difference is that NONSTRICT tolerates the edit, while READ_ONLY forbids it. ## Related but distinct mechanisms Do not confuse the READ_ONLY cache strategy with two neighbours: - The `@Immutable` mapping annotation tells Hibernate the entity or collection never changes at the ORM level, so it can skip dirty checking entirely and will not emit UPDATE statements. It pairs naturally with a READ_ONLY cache region, but it is a separate declaration. - Marking objects read-only in a session (a read-only query or session setting) is a persistence-context optimisation that skips snapshot retention and dirty checking for those instances. Again, a different layer. A reasonable production pattern is to combine them: mark reference entities `@Immutable`, cache them READ_ONLY, and when reference data does change with a release, evict those regions explicitly at startup or via an admin action rather than trying to make the cache track edits. ## What to say if asked how to fix a failure If you hit the exception in a real system, the fix is a modelling decision, not a try/catch: either the entity really is mutable — move it to NONSTRICT_READ_WRITE or READ_WRITE — or the mutation is a bug and the code path should not be touching immutable reference data at all. Downgrading the strategy "to make the error go away" without deciding which of those is true is the wrong answer.
- Your reference data does change, but only once per release. Do you have to abandon READ_ONLY?Not necessarily. A common pattern is to keep READ_ONLY and treat reference data as replaced rather than edited: apply the change as a migration or admin operation and evict the affected cache regions afterwards (or restart the nodes). If instead the data is edited through normal application traffic — even rarely — READ_ONLY is the wrong declaration and NONSTRICT_READ_WRITE is the honest choice.
- How is the READ_ONLY cache strategy different from mapping the entity as @Immutable?They live at different layers. `@Immutable` tells the ORM the entity never changes, so Hibernate skips dirty checking and never emits an UPDATE for it. The READ_ONLY cache strategy tells the second-level cache that entries never need reconciling and rejects cache updates. They are complementary and are usually applied together to reference data, but either can be used without the other.
saying these in an interview costs you the question
- Claiming Hibernate silently ignores the update and keeps serving the stale cached value
- Using READ_ONLY for 'rarely updated' data — that is NONSTRICT_READ_WRITE's case
- Thinking READ_ONLY prevents the SQL UPDATE from being generated (that is @Immutable's job)
- Catching the exception and continuing instead of fixing the mapping or the code path
- Assuming deletes also throw — deletes just evict the entry