hibernate/hibernate-orm · error · HibernateException

Entity '{}' has no version and may not be locked at level {}

Error message

Entity '{}' has no version and may not be locked at level {}

What it means

Like all version-increment strategies, PessimisticForceIncrementLockingStrategy requires the entity to be versioned because its lock action is 'bump the version now'. After checking the lock mode, the constructor throws HibernateException 'Entity '<name>' has no version and may not be locked at level <mode>' when lockable.isVersioned() is false. The message identifies both the entity and the lock level requested, so the offending mapping is easy to find.

Source

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

	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.
	 */
	protected LockMode getLockMode() {
		return lockMode;
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Add @Version (and the DB column) to the entity being force-increment locked
  2. Use plain PESSIMISTIC_WRITE (select/update-based lock without forced increment) if adding a version is not possible
  3. Add an isVersioned() guard to shared locking utilities and fail with a clear domain-specific message

Example fix

// before
@Entity
public class Counter {
    @Id private Long id;
    private long value;
}

// after
@Entity
public class Counter {
    @Id private Long id;
    @Version private long version;
    private long value;
}
Defensive patterns

Strategy: validation

Validate before calling

EntityPersister persister = session.getEntityPersister(Counter.class.getName(), counter);
if (!persister.isVersioned()) {
    throw new IllegalArgumentException(Counter.class.getName() + " needs @Version for forced version increment");
}
session.lock(counter, LockMode.PESSIMISTIC_FORCE_INCREMENT);

Try / catch

try {
    session.lock(counter, LockMode.PESSIMISTIC_FORCE_INCREMENT);
}
catch (HibernateException e) {
    if (e.getMessage().contains("has no version")) {
        throw new IllegalStateException("Add @Version to " + counter.getClass().getName() + " - force-increment needs a version to bump", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: session.lock(entity, LockMode.PESSIMISTIC_FORCE_INCREMENT) or EntityManager.lock(entity, LockModeType.PESSIMISTIC_FORCE_INCREMENT) on an entity without @Version; direct new PessimisticForceIncrementLockingStrategy(persister, mode) where the persister reports no version. Fires on first lock (strategy construction).

Common situations: Applying a uniform 'lock before update' policy across all entities, some of which predate the policy and lack version fields; read-only/reporting entities drawn into transactional flows; version column dropped in the schema but mapping not updated.

Related errors


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