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
- Add an @Version column (e.g. private long version; with a version column in the schema) to the entity being locked
- 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
- Pre-verify with session.getMetamodel().entityPersister(clazz).isVersioned() before issuing pessimistic locks
- 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
- Add @Version columns to every entity that participates in pessimistic locking
- Keep an architecture test (e.g. ClassGraph/ArchUnit scan) asserting locked entities carry @Version
- Document per entity which lock modes are legal
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
- Entity '{}' has no version and may not be locked at level {}
- Entity '{}' has no version and may not be locked at level {}
- Entity '{}' has no version and may not be locked at level {}
- Spanner does not support no wait.
- Spanner does not support skip locked.
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/50690f3f52369350.
Report an issue: GitHub.