hibernate/hibernate-orm · error · HibernateException

Entity '{}' may not be locked at level {}

Error message

Entity '{}' may not be locked at level {}

What it means

PessimisticForceIncrementLockingStrategy immediately forces a version increment while also acting as a pessimistic lock; it accepts PESSIMISTIC_READ, PESSIMISTIC_WRITE and PESSIMISTIC_FORCE_INCREMENT. The constructor throws HibernateException 'Entity '<name>' may not be locked at level <mode>' when lockMode.lessThan(LockMode.PESSIMISTIC_READ), i.e. for OPTIMISTIC or weaker modes routed here. The comment in source spells out the three valid modes.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java:37

 * @author Scott Marlow
 * @since 3.5
 */
public class PessimisticForceIncrementLockingStrategy implements LockingStrategy {
	private final EntityPersister lockable;
	private final LockMode lockMode;

	/**
	 * Construct locking strategy.
	 *
	 * @param lockable The metadata for the entity to be locked.
	 * @param lockMode Indicates the type of lock to be acquired.
	 */
	public PessimisticForceIncrementLockingStrategy(EntityPersister lockable, LockMode lockMode) {
		this.lockable = lockable;
		this.lockMode = lockMode;
		// ForceIncrement can be used for PESSIMISTIC_READ, PESSIMISTIC_WRITE or PESSIMISTIC_FORCE_INCREMENT
		if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) {
			throw new HibernateException( "Entity '" + lockable.getEntityName()
						+ "' may not be locked at level " + lockMode );
		}
		if ( !lockable.isVersioned() ) {
			throw new HibernateException( "Entity '" + lockable.getEntityName()
						+ "' has no version and may not be locked at level " + lockMode);
		}
	}

	@Override
	public void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) {
		final var entry = session.getPersistenceContextInternal().getEntry( object );
		OptimisticLockHelper.forceVersionIncrement( object, entry, session );
	}

	/**
	 * Retrieve the specific lock mode defined.
	 *
	 * @return The specific lock mode.

View on GitHub (pinned to fad1729dce)

Solutions

  1. Request PESSIMISTIC_READ, PESSIMISTIC_WRITE or PESSIMISTIC_FORCE_INCREMENT with this strategy
  2. Fix custom dialect mode-to-strategy mapping so weaker modes get optimistic or select-based strategies instead
  3. Use LockModeType JPA constants to make the intended strength explicit at call sites

Example fix

// before
session.lock(person, LockMode.OPTIMISTIC); // routed to pessimistic force-increment strategy

// after
session.lock(person, LockMode.PESSIMISTIC_FORCE_INCREMENT);
Defensive patterns

Strategy: validation

Validate before calling

// Force the mode into the accepted range for force-increment locking
static LockMode atLeastPessimisticRead(LockMode m) {
    return m.lessThan(LockMode.PESSIMISTIC_READ) ? LockMode.PESSIMISTIC_WRITE : m;
}
session.lock(person, atLeastPessimisticRead(requestedMode));

Try / catch

try {
    session.lock(person, LockMode.PESSIMISTIC_FORCE_INCREMENT);
}
catch (HibernateException e) {
    if (e.getMessage().contains("may not be locked at level")) {
        throw new IllegalArgumentException("Use PESSIMISTIC_READ/PESSIMISTIC_WRITE/PESSIMISTIC_FORCE_INCREMENT", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: A dialect's getLockingStrategy (or explicit code) hands this strategy a mode below PESSIMISTIC_READ - e.g. session.lock(entity, LockMode.OPTIMISTIC) resolving to PessimisticForceIncrementLockingStrategy, or direct construction with LockMode.UPGRADE. Thrown eagerly in the constructor with entity name and offending mode.

Common situations: Custom dialect getLockingStrategy implementations that map all modes to one strategy class; switching lock constants during a Hibernate major upgrade where LockMode ordering changed; test code constructing strategies with arbitrary modes.

Related errors


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