hibernate/hibernate-orm · error · HibernateException

Entity '{}' has no version and may not be locked via 'update

Error message

Entity '{}' has no version and may not be locked via 'update' statement

What it means

Update-based pessimistic locking works by executing UPDATE ... where id=? and version=?, so a version column is functionally required for the strategy to be safe. AbstractPessimisticUpdateLockingStrategy's constructor therefore throws HibernateException 'Entity '<name>' has no version and may not be locked via 'update' statement' when lockable.isVersioned() is false. The check runs at strategy construction, i.e. the first time that entity is locked this way (or eagerly when the dialect builds it).

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractPessimisticUpdateLockingStrategy.java:46

	private final LockMode lockMode;
	private final String sql;

	/**
	 * Construct a locking strategy based on SQL UPDATE statements.
	 *
	 * @param lockable The metadata for the entity to be locked.
	 * @param lockMode Indicates the type of lock to be acquired.  Note that
	 * read-locks are not valid for this strategy.
	 */
	public AbstractPessimisticUpdateLockingStrategy(EntityPersister lockable, LockMode lockMode) {
		this.lockable = lockable;
		this.lockMode = lockMode;
		if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) {
			throw new HibernateException( "Lock mode " + lockMode
						+ " not valid for locking via 'update' statement" );
		}
		if ( !lockable.isVersioned() ) {
			throw new HibernateException( "Entity '" + lockable.getEntityName()
						+ "' has no version and may not be locked via 'update' statement" );
		}
		this.sql = generateLockString();
	}

	@Override
	public void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) {
		try {
			doLock( id, version, session );
		}
		catch (JDBCException e) {
			throw new PessimisticEntityLockException( object, "Could not obtain pessimistic lock", e );
		}
	}

	void doLock(Object id, Object version, SharedSessionContractImplementor session) {
		try {
			final var factory = session.getFactory();

View on GitHub (pinned to fad1729dce)

Solutions

  1. Add an @Version column (e.g. private long version; with a version column in the schema) to the entity being locked
  2. If a version column is impossible, avoid update-based locking: use a select-based pessimistic lock (select ... for update) by relying on dialects/modes that support it, or optimistic locking
  3. Pre-verify with session.getMetamodel().entityPersister(clazz).isVersioned() before issuing pessimistic locks
  4. For custom dialects, route unversioned entities to a strategy that does not require a version

Example fix

// before
@Entity
public class Person {
    @Id private Long id;
    private String name;
}

// after
@Entity
public class Person {
    @Id private Long id;
    @Version private long version;
    private String name;
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify versionability before pessimistic locking
EntityPersister persister = session.getEntityPersister(Person.class.getName(), person);
if (!persister.isVersioned()) {
    throw new IllegalArgumentException("Cannot use update-based pessimistic lock on unversioned " + Person.class.getName());
}
session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(person);

Try / catch

try {
    session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(person);
}
catch (HibernateException e) {
    if (e.getMessage().contains("has no version")) {
        throw new IllegalStateException("Add @Version to " + person.getClass().getName() + " or use a non-update lock strategy", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling session.lock(entity, LockMode.PESSIMISTIC_WRITE) (or a buildLockRequest with a pessimistic mode) on an entity class that has no @Version field, while the active dialect locks that mode via an UPDATE statement. Also triggered directly by new UpdateLockingStrategy(persister, mode) in custom dialect code.

Common situations: Adding pessimistic locking to a legacy entity model that never carried version columns; enabling a locking-oriented feature (e.g. a lock-then-read batch job) on databases where Hibernate emulates locks via UPDATE; entities intentionally modeled without optimistic-lock metadata.

Related errors


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