hibernate/hibernate-orm · error · UnsupportedLockAttemptException
Lock mode {} not supported for read-only entity
Error message
Lock mode {} not supported for read-only entity What it means
EntityEntryImpl.setLockMode refuses lock modes that are greater than LockMode.READ (OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT, WRITE, PESSIMISTIC_*) when the entity's persister is immutable (@Immutable entity, or otherwise mapped non-mutable). Immutable entities are never versioned or lock-upgraded, so requesting a real lock on them raises UnsupportedLockAttemptException instead of silently ignoring the lock.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryImpl.java:171
setDeletedState( deletedState );
this.version = version;
setCompressedValue( LOCK_MODE, lockMode );
setCompressedValue( EXISTS_IN_DATABASE, existsInDatabase );
this.rowId = null; // this is equivalent to the old behavior...
// don't store PersistenceContext for immutable entity, see HHH-10251
this.persistenceContext = mutable ? persistenceContext : null;
}
@Override
public LockMode getLockMode() {
return getCompressedValue( LOCK_MODE );
}
@Override
public void setLockMode(LockMode lockMode) {
if ( lockMode.greaterThan( LockMode.READ )
&& persister!=null && !persister.isMutable() ) {
throw new UnsupportedLockAttemptException( "Lock mode " + lockMode
+ " not supported for read-only entity" );
}
setCompressedValue( LOCK_MODE, lockMode );
}
@Override
public Status getStatus() {
return getCompressedValue( STATUS );
}
private Status getPreviousStatus() {
return getCompressedValue( PREVIOUS_STATUS );
}
@Override
public void setStatus(Status status) {
if ( status == READ_ONLY ) {
//memory optimizationView on GitHub (pinned to fad1729dce)
Solutions
- Remove the lock request (annotation, find/lock argument, or query hint) for the immutable entity - LockMode.READ/NONE are the only accepted modes.
- If the data really needs locking, map the entity mutable (drop @Immutable / set mutable) - accepting the update semantics that brings.
- Catch org.hibernate.UnsupportedLockAttemptException to degrade gracefully when generic locking code hits immutable entities.
- Audit generic lock aspects/interceptors so they skip entities whose persister reports isMutable() == false.
Example fix
// before - @Immutable entity with pessimistic lock
@Immutable @Entity
public class ExchangeRate { ... }
em.find(ExchangeRate.class, id, LockModeType.PESSIMISTIC_WRITE); // throws
// after - no lock upgrade on immutable data
em.find(ExchangeRate.class, id);
// (or map the entity mutable if locking is genuinely required) Defensive patterns
Strategy: validation
Validate before calling
// Guard: skip lock requests on immutable entities
boolean mutable = session.getFactory().getRuntimeMetamodels()
.getMappingMetamodel().getEntityDescriptor(entityClass).isMutable();
if (!mutable && lockMode.greaterThan(org.hibernate.LockMode.READ)) {
log.debug("skipping lock {} for immutable entity {}", lockMode, entityClass);
} else {
session.buildLockRequest(new LockOptions(lockMode)).lock(entity);
} Type guard
static boolean supportsLockUpgrade(org.hibernate.Session session, Class<?> entityClass) {
return session.getFactory().getRuntimeMetamodels()
.getMappingMetamodel().getEntityDescriptor(entityClass).isMutable();
} Try / catch
try {
session.buildLockRequest(new LockOptions(lockMode)).lock(entity);
} catch (org.hibernate.UnsupportedLockAttemptException e) {
// entity is @Immutable and lockMode > READ: degrade to no lock
log.debug("lock not applicable to immutable entity", e);
} Prevention
- Do not annotate entities @Lock or pass LockModeType to find() for @Immutable entities.
- Exclude immutable entities from generic locking aspects/interceptors.
- Catch UnsupportedLockAttemptException in shared data-access templates so locking is best-effort.
When it happens
Trigger: Calling em.lock / session.lock, em.find(id, LockModeType.PESSIMISTIC_WRITE), refresh(entity, lockMode), setting jakarta.lock.scope/lock-mode hints, or a @Lock annotation on a repository method for an entity mapped @Immutable (or a read-only-mapped persister where isMutable() is false) with any lock mode above READ.
Common situations: Adding pessimistic locking to a query that touches an audit/lookup table mapped @Immutable; @Version fields removed while lock annotations remain; generic locking aspects applied to all repositories including immutable ones; Spring Data @Lock on repositories of immutable projections.
Related errors
- Connection lock-timeout does not accept skip-locked
- Connection lock-timeout does not accept no-wait
- Spanner does not support no wait.
- Spanner does not support skip locked.
- Spanner does not support lock timeout.
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/00b5743a23bdf857.
Report an issue: GitHub.