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

  1. Prefer removing @OptimisticLock and keeping standard @Version-based locking (OptimisticLockType.VERSION, the default)
  2. Or remove the @Version property and rely fully on all/dirty column comparison with @DynamicUpdate
  3. 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

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


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