{"record":{"id":"14f7394e40388f49","repo":"hibernate/hibernate-orm","slug":"entity-has-no-version-and-may-not-be-locked-a","errorCode":null,"errorMessage":"Entity '{}' has no version and may not be locked at level {}","messagePattern":"Entity '(.+?)' has no version and may not be locked at level (.+?)","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java","lineNumber":41,"sourceCode":"public class OptimisticForceIncrementLockingStrategy implements LockingStrategy {\n\tprivate final EntityPersister lockable;\n\tprivate final LockMode lockMode;\n\n\t/**\n\t * Construct locking strategy.\n\t *\n\t * @param lockable The metadata for the entity to be locked.\n\t * @param lockMode Indicates the type of lock to be acquired.\n\t */\n\tpublic OptimisticForceIncrementLockingStrategy(EntityPersister lockable, LockMode lockMode) {\n\t\tthis.lockable = lockable;\n\t\tthis.lockMode = lockMode;\n\t\tif ( lockMode.lessThan( LockMode.OPTIMISTIC_FORCE_INCREMENT ) ) {\n\t\t\tthrow new HibernateException( \"Entity '\" + lockable.getEntityName()\n\t\t\t\t\t\t+ \"' may not be locked at level \" + lockMode );\n\t\t}\n\t\tif ( !lockable.isVersioned() ) {\n\t\t\tthrow new HibernateException( \"Entity '\" + lockable.getEntityName()\n\t\t\t\t\t\t+ \"' has no version and may not be locked at level \" + lockMode);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void lock(Object id, Object version, Object object, int timeout, EventSource session) {\n//\t\tfinal EntityEntry entry = session.getPersistenceContextInternal().getEntry( object );\n\t\t// Register the EntityIncrementVersionProcess action to run just prior to transaction commit.\n\t\tsession.getActionQueue().registerCallback( new EntityIncrementVersionProcess( object ) );\n\t}\n\n\tprotected LockMode getLockMode() {\n\t\treturn lockMode;\n\t}\n}\n","sourceCodeStart":23,"sourceCodeEnd":57,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java#L23-L57","documentation":"OptimisticForceIncrementLockingStrategy works by incrementing the entity's version column at commit; with no version column there is nothing to increment. Its constructor therefore throws HibernateException 'Entity '<name>' has no version and may not be locked at level <mode>' when lockable.isVersioned() is false, after first validating the lock mode. The exception appears the first time the strategy is constructed for that entity.","triggerScenarios":"session.lock(entity, LockMode.OPTIMISTIC_FORCE_INCREMENT) or jakarta lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) on an entity without @Version; direct construction of OptimisticForceIncrementLockingStrategy with a persister whose isVersioned() is false. The constructor's second check fires immediately.","commonSituations":"Adding optimistic force-increment locking to a legacy entity model that never had version columns; new entities copied from templates without the version field; schema where the version column exists in DB but is not mapped (missing @Version annotation after refactor).","solutions":["Add @Version private long version; (plus the version column in the schema) to the entity","If a version column cannot be added, use pessimistic locking for that entity instead","Verify with session.getMetamodel().entityPersister(clazz).isVersioned() before issuing OPTIMISTIC_FORCE_INCREMENT locks","Check the exception message: it names the entity and requested level, making the offending mapping easy to locate"],"exampleFix":"// before\n@Entity\npublic class Document {\n    @Id private Long id;\n    private String body;\n}\n\n// after\n@Entity\npublic class Document {\n    @Id private Long id;\n    @Version private long version;\n    private String body;\n}","handlingStrategy":"validation","validationCode":"EntityPersister persister = session.getEntityPersister(Document.class.getName(), document);\nif (!persister.isVersioned()) {\n    throw new IllegalArgumentException(Document.class.getName() + \" needs a @Version column for OPTIMISTIC_FORCE_INCREMENT\");\n}\nsession.lock(document, LockMode.OPTIMISTIC_FORCE_INCREMENT);","typeGuard":null,"tryCatchPattern":"try {\n    session.lock(document, LockMode.OPTIMISTIC_FORCE_INCREMENT);\n}\ncatch (HibernateException e) {\n    if (e.getMessage().contains(\"has no version\")) {\n        throw new IllegalStateException(\"Add @Version to \" + document.getClass().getName(), e);\n    }\n    throw e;\n}","preventionTips":["Every entity that will ever be force-increment locked must have a @Version field and column","Keep entity templates with @Version included so new entities start versioned","Add a metadata test listing entities locked optimistically and asserting each isVersioned()"],"tags":["hibernate","locking","optimistic-lock","version","entity-mapping"],"backgroundTag":"missing-version-column","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}