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 optimization

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove the lock request (annotation, find/lock argument, or query hint) for the immutable entity - LockMode.READ/NONE are the only accepted modes.
  2. If the data really needs locking, map the entity mutable (drop @Immutable / set mutable) - accepting the update semantics that brings.
  3. Catch org.hibernate.UnsupportedLockAttemptException to degrade gracefully when generic locking code hits immutable entities.
  4. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/00b5743a23bdf857. Report an issue: GitHub.