hibernate/hibernate-orm · error · MappingException
Entity '{name}' has 'OptimisticLockType.{optimisticLockStyle
Error message
Entity '{name}' has 'OptimisticLockType.{optimisticLockStyle}' but declares a '@Version' field What it means
Same boot-time validation block as the @DynamicUpdate check: OptimisticLockType.ALL/DIRTY lock by comparing columns in the UPDATE's WHERE clause instead of using a version number, so combining them with an explicit @Version property is contradictory and BaseEntityPersister rejects it whenever versionPropertyIndex != NO_VERSION_INDX. The dynamic-update check runs first, so the typical failing combination is @DynamicUpdate + @OptimisticLock(ALL|DIRTY) + @Version. Thrown as MappingException while the persister is constructed.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/persister/entity/BaseEntityPersister.java:420
dynamicUpdate = persistentClass.useDynamicUpdate() || hasMultipleFetchGroups( bytecodeEnhancementMetadata );
dynamicInsert = persistentClass.useDynamicInsert();
polymorphic = persistentClass.isPolymorphic();
inherited = persistentClass.isInherited();
superclass = inherited ? persistentClass.getSuperclass().getEntityName() : null;
hasSubclasses = persistentClass.hasSubclasses();
optimisticLockStyle = persistentClass.getOptimisticLockStyle();
//TODO: move these checks into the Binders
if ( optimisticLockStyle.isAllOrDirty() ) {
if ( !dynamicUpdate ) {
throw new MappingException( "Entity '" + name
+ "' has 'OptimisticLockType." + optimisticLockStyle
+ "' but is not annotated '@DynamicUpdate'" );
}
if ( versionPropertyIndex != NO_VERSION_INDX ) {
throw new MappingException( "Entity '" + name
+ "' has 'OptimisticLockType." + optimisticLockStyle
+ "' but declares a '@Version' field" );
}
}
hasCollections = foundCollection;
hasOwnedCollections = foundOwnedCollection;
mutablePropertiesIndexes = mutableIndexes;
subclassEntityNames = collectSubclassEntityNames( persistentClass );
// HashMap<Class<?>, String> entityNameByInheritanceClassMapLocal = new HashMap<>();
// if ( persistentClass.hasPojoRepresentation() ) {
// entityNameByInheritanceClassMapLocal.put( persistentClass.getMappedClass(), persistentClass.getEntityName() );
// for ( Subclass subclass : persistentClass.getSubclasses() ) {
// entityNameByInheritanceClassMapLocal.put( subclass.getMappedClass(), subclass.getEntityName() );
// }
// }View on GitHub (pinned to fad1729dce)
Solutions
- Prefer removing @OptimisticLock and keeping standard @Version-based locking (OptimisticLockType.VERSION, the default)
- Or remove the @Version property and rely fully on all/dirty column comparison with @DynamicUpdate
- Audit every entity where the lock style was changed and delete the now-contradictory @Version field
Example fix
// before
@Entity @DynamicUpdate
@OptimisticLock(type = OptimisticLockType.ALL)
public class Order {
@Version private int version; // contradicts ALL/DIRTY locking
}
// after
@Entity @DynamicUpdate
public class Order {
@Version private int version; // version locking (default style)
} Defensive patterns
Strategy: validation
Validate before calling
static void checkVersionConflict(Class<?> entity) {
OptimisticLock lock = entity.getAnnotation(OptimisticLock.class);
boolean versioned = java.util.stream.Stream.of(entity.getDeclaredFields())
.anyMatch(f -> f.isAnnotationPresent(Version.class));
if ( lock != null && (lock.type() == OptimisticLockType.ALL
|| lock.type() == OptimisticLockType.DIRTY) && versioned ) {
throw new IllegalStateException(entity.getName()
+ " mixes OptimisticLockType." + lock.type() + " with @Version");
}
} Try / catch
try {
sessionFactory = metadata.getSessionFactoryBuilder().build();
}
catch ( org.hibernate.MappingException e ) {
throw new IllegalStateException("SessionFactory boot failed: " + e.getMessage(), e);
} Prevention
- Pick one optimistic-locking strategy per entity and document it in the code review checklist
- When switching from @Version to ALL/DIRTY, delete the @Version field in the same commit
- Scan the whole persistence unit for @OptimisticLock during upgrades
When it happens
Trigger: Entity with @DynamicUpdate + @OptimisticLock(type = ALL or DIRTY) + a @Version field; XML mapping with <version> plus optimistic-lock='all|dirty' and dynamic-update='true'.
Common situations: Leaving @Version in place when switching a legacy entity to all/dirty locking; copy-pasting annotation sets between entities; inheritance hierarchies where the @Version property sits on the root but a subclass sets the lock style.
Related errors
- Entity '${name}' has 'OptimisticLockType.${optimisticLockSty
- Association '{propertyName}' marked as '@NaturalId' is also
- Could not parse discriminator value '{discriminatorValue}' a
- optimistic-lock=all|dirty not supported for joined-subclass
- Query result contains conflicting version of entity already
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/96c881f3c57dcef4.
Report an issue: GitHub.