hibernate/hibernate-orm · error · HibernateException
Entity '${persister.getEntityName()}' has no version and may
Error message
Entity '${persister.getEntityName()}' has no version and may not be locked at level ${lockMode} What it means
Thrown from DefaultPostLoadEventListener.onPostLoad when the entity's current LockMode requires a version (lockMode.requiresVersion(), e.g. OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT, PESSIMISTIC_FORCE_INCREMENT) but the persister reports isVersioned() == false. Optimistic locking works by verifying or incrementing a @Version column at flush; with no version attribute there is nothing to verify or increment, so Hibernate refuses the lock. The message names the entity and the offending lock mode.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java:56
if ( lockMode.requiresVersion() ) {
final var persister = entry.getPersister();
if ( persister.isVersioned() ) {
switch ( lockMode ) {
case PESSIMISTIC_FORCE_INCREMENT:
OptimisticLockHelper.forceVersionIncrement( entity, entry, session );
break;
case OPTIMISTIC_FORCE_INCREMENT:
session.getActionQueue()
.registerCallback( new EntityIncrementVersionProcess( entity ) );
break;
case OPTIMISTIC:
session.getActionQueue()
.registerCallback( new EntityVerifyVersionProcess( entity ) );
break;
}
}
else {
throw new HibernateException( "Entity '" + persister.getEntityName()
+ "' has no version and may not be locked at level " + lockMode);
}
}
}
}
View on GitHub (pinned to fad1729dce)
Solutions
- Add a @Version attribute to the entity (e.g. @Version private Long version;) and add the corresponding version column to the table (ALTER TABLE ... ADD COLUMN version BIGINT)
- If the schema cannot take a version column, switch to a pessimistic mode: LockModeType.PESSIMISTIC_READ or LockModeType.WRITE-style SELECT ... FOR UPDATE locking
- Remove the lock() call if no row-level concurrency control is actually needed, or implement manual version checking in an UPDATE ... WHERE clause
Example fix
// before
em.lock(order, LockModeType.OPTIMISTIC_FORCE_INCREMENT); // Order has no @Version -> HibernateException at post-load
// after
@Entity
public class Order {
@Id Long id;
@Version Long version; // add version attribute + column
}
em.lock(order, LockModeType.OPTIMISTIC_FORCE_INCREMENT); Defensive patterns
Strategy: validation
Validate before calling
EntityPersister p = session.getFactory().getMappingMetamodel()
.getEntityDescriptor(Order.class);
if (!p.isVersioned()) {
// entity has no @Version: use PESSIMISTIC_READ/WRITE instead of OPTIMISTIC*
session.lock(order, LockMode.PESSIMISTIC_WRITE);
}
else {
session.lock(order, LockMode.OPTIMISTIC_FORCE_INCREMENT);
} Type guard
boolean lockableOptimistically(SessionFactory f, Class<?> entityType) {
return f.getMappingMetamodel().getEntityDescriptor(entityType).isVersioned();
} Try / catch
try {
em.lock(order, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
} catch (HibernateException e) {
if (e.getMessage() != null && e.getMessage().contains("has no version")) {
// entity class lacks @Version — add a version or fall back to pessimistic locking
em.lock(order, LockModeType.PESSIMISTIC_WRITE);
} else { throw e; }
} Prevention
- Give every entity that will be locked a @Version attribute from day one
- Assert in architecture tests (ArchUnit) that entities used with @Lock(OPTIMISTIC*) have a version field
- Prefer pessimistic locking for non-versioned legacy tables
When it happens
Trigger: Calling session.lock(entity, LockMode.OPTIMISTIC/OPTIMISTIC_FORCE_INCREMENT), JPA EntityManager.lock(entity, LockModeType.OPTIMISTIC/OPTIMISTIC_FORCE_INCREMENT), or Spring Data JPA @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) on a query for an entity class that has no @Version field; also find(entityName, id, LockMode) with a version-requiring mode. The post-load listener fires as the entity loads, so the exception surfaces on the load/lock call itself.
Common situations: Adding optimistic locking annotations to a repository/service for a legacy entity that never got a version column; switching from PESSIMISTIC to OPTIMISTIC lock modes during a concurrency refactor without adding @Version; upgrading code that used LockMode.READ semantics (deprecated no-op in Hibernate 6+) and mapping them to OPTIMISTIC on non-versioned entities.
Related errors
- Duplicate named query '%s'
- Duplicate named stored procedure '{}'
- Duplicate SQL result set mapping '{}'
- Duplicate table mapping '{}'
- Property '${property}' is annotated '@OptimisticLock(exclude
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/9f45095b9d343e75.
Report an issue: GitHub.