{"record":{"id":"b09966bf9e4a0519","repo":"hibernate/hibernate-orm","slug":"entity-name-has-optimisticlocktype-optimis","errorCode":null,"errorMessage":"Entity '${name}' has 'OptimisticLockType.${optimisticLockStyle}' but is not annotated '@DynamicUpdate'","messagePattern":"Entity '(.+?)' has 'OptimisticLockType\\.(.+?)' but is not annotated '@DynamicUpdate'","errorType":"exception","errorClass":"MappingException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/persister/entity/BaseEntityPersister.java","lineNumber":415,"sourceCode":"\n\t\tmutable = persistentClass.isMutable();\n\t\tisAbstract = isAbstract( persistentClass );\n\n\t\tselectBeforeUpdate = persistentClass.hasSelectBeforeUpdate();\n\n\t\tdynamicUpdate = persistentClass.useDynamicUpdate() || hasMultipleFetchGroups( bytecodeEnhancementMetadata );\n\t\tdynamicInsert = persistentClass.useDynamicInsert();\n\n\t\tpolymorphic = persistentClass.isPolymorphic();\n\t\tinherited = persistentClass.isInherited();\n\t\tsuperclass = inherited ? persistentClass.getSuperclass().getEntityName() : null;\n\t\thasSubclasses = persistentClass.hasSubclasses();\n\n\t\toptimisticLockStyle = persistentClass.getOptimisticLockStyle();\n\t\t//TODO: move these checks into the Binders\n\t\tif ( optimisticLockStyle.isAllOrDirty() ) {\n\t\t\tif ( !dynamicUpdate ) {\n\t\t\t\tthrow new MappingException( \"Entity '\" + name\n\t\t\t\t\t\t\t\t\t\t\t+ \"' has 'OptimisticLockType.\" + optimisticLockStyle\n\t\t\t\t\t\t\t\t\t\t\t+ \"' but is not annotated '@DynamicUpdate'\" );\n\t\t\t}\n\t\t\tif ( versionPropertyIndex != NO_VERSION_INDX ) {\n\t\t\t\tthrow new MappingException( \"Entity '\" + name\n\t\t\t\t\t\t\t\t\t\t\t+ \"' has 'OptimisticLockType.\" + optimisticLockStyle\n\t\t\t\t\t\t\t\t\t\t\t+ \"' but declares a '@Version' field\" );\n\t\t\t}\n\t\t}\n\n\t\thasCollections = foundCollection;\n\t\thasOwnedCollections = foundOwnedCollection;\n\t\tmutablePropertiesIndexes = mutableIndexes;\n\n\t\tsubclassEntityNames = collectSubclassEntityNames( persistentClass );\n\n//\t\tHashMap<Class<?>, String> entityNameByInheritanceClassMapLocal = new HashMap<>();\n//\t\tif ( persistentClass.hasPojoRepresentation() ) {","sourceCodeStart":397,"sourceCodeEnd":433,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/persister/entity/BaseEntityPersister.java#L397-L433","documentation":"At SessionFactory boot, BaseEntityPersister validates that entities using OptimisticLockType.ALL or DIRTY run with dynamic SQL updates, because column-comparing optimistic locking needs Hibernate to build an UPDATE restricted to the (all|dirty) columns. dynamicUpdate is only true when @DynamicUpdate (or dynamic-update='true') is set, or bytecode enhancement configured multiple fetch groups (hasMultipleFetchGroups(bytecodeEnhancementMetadata)). If neither holds, persister construction throws a MappingException.","triggerScenarios":"An @Entity class annotated @org.hibernate.annotations.OptimisticLock(type = OptimisticLockType.ALL or DIRTY) without @DynamicUpdate and without enhancement producing multiple fetch groups; equivalent XML with optimistic-lock='all|dirty' but no dynamic-update='true'.","commonSituations":"Adding @OptimisticLock to existing entities that never had dynamic update; porting legacy hbm.xml mappings; disabling bytecode enhancement in a project where it previously satisfied this check; code-generation templates that emit only one of the two annotations.","solutions":["Add @DynamicUpdate to the entity (or dynamic-update='true' in XML)","Or switch to version-based locking: remove @OptimisticLock (default VERSION) and add a @Version column","If relying on enhancement, keep it configured with multiple fetch groups so dynamicUpdate computes true"],"exampleFix":"// before\n@Entity\n@OptimisticLock(type = OptimisticLockType.DIRTY)\npublic class Document { ... }\n\n// after\n@Entity\n@DynamicUpdate\n@OptimisticLock(type = OptimisticLockType.DIRTY)\npublic class Document { ... }","handlingStrategy":"validation","validationCode":"static void checkOptimisticLock(Class<?> entity) {\n    OptimisticLock lock = entity.getAnnotation(OptimisticLock.class);\n    if ( lock != null && (lock.type() == OptimisticLockType.ALL\n                       || lock.type() == OptimisticLockType.DIRTY)\n            && !entity.isAnnotationPresent(DynamicUpdate.class) ) {\n        throw new IllegalStateException(entity.getName()\n            + \" requires @DynamicUpdate for OptimisticLockType.\" + lock.type());\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    sessionFactory = metadata.getSessionFactoryBuilder().build();\n}\ncatch ( org.hibernate.MappingException e ) {\n    // configuration error: fail deployment with e.getMessage()\n    throw new IllegalStateException(\"SessionFactory boot failed: \" + e.getMessage(), e);\n}","preventionTips":["Treat @OptimisticLock(ALL/DIRTY) and @DynamicUpdate as an inseparable pair in code review","Put the pair in a shared annotation bundle or template so they are never added separately","Run a boot test per persistence unit in CI so mapping errors surface before deployment"],"tags":["hibernate","orm","optimistic-locking","mapping","annotations","bytecode-enhancement"],"backgroundTag":"optimistic-lock-misconfiguration","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}