{"record":{"id":"59aea4ac7e46b21c","repo":"hibernate/hibernate-orm","slug":"entity-has-no-version-and-may-not-be-locked-a-59aea4","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/OptimisticLockingStrategy.java","lineNumber":40,"sourceCode":"public class OptimisticLockingStrategy 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 OptimisticLockingStrategy(EntityPersister lockable, LockMode lockMode) {\n\t\tthis.lockable = lockable;\n\t\tthis.lockMode = lockMode;\n\t\tif ( lockMode.lessThan( LockMode.OPTIMISTIC ) ) {\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\t// Register the EntityVerifyVersionProcess action to run just prior to transaction commit.\n\t\tsession.getActionQueue().registerCallback( new EntityVerifyVersionProcess( object ) );\n\t}\n\n\tprotected LockMode getLockMode() {\n\t\treturn lockMode;\n\t}\n}\n","sourceCodeStart":22,"sourceCodeEnd":55,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java#L22-L55","documentation":"OptimisticLockingStrategy ends its constructor validation by requiring a version attribute: optimistic locking is implemented as a version check at commit, so an unversioned entity cannot be protected. When lockable.isVersioned() returns false it throws HibernateException 'Entity '<name>' has no version and may not be locked at level <mode>', with the requested lock mode in the message. The strategy is created lazily, typically when the first lock request for the entity resolves the strategy.","triggerScenarios":"session.lock(entity, LockMode.OPTIMISTIC) or EntityManager.lock(entity, LockModeType.OPTIMISTIC) on an entity class lacking a @Version attribute; direct new OptimisticLockingStrategy(persister, LockMode.OPTIMISTIC) against an unversioned persister. The second constructor check fires with the entity name and lock level in the message.","commonSituations":"Entities created without optimistic locking in mind that later get locked by generic audit/locking aspects; inheritance hierarchies where the version was put on a sibling class; @Version removed accidentally during mapping refactor or moved to a non-persistent field.","solutions":["Add a @Version field and matching database column to the entity","Use pessimistic locking (PESSIMISTIC_READ/PESSIMISTIC_WRITE) for unversioned entities","Guard generic locking code with a persister.isVersioned() check and choose the strategy accordingly"],"exampleFix":"// before\n@Entity\npublic class Account {\n    @Id private Long id;\n    private BigDecimal balance;\n}\n\n// after\n@Entity\npublic class Account {\n    @Id private Long id;\n    @Version private long version;\n    private BigDecimal balance;\n}","handlingStrategy":"validation","validationCode":"boolean versioned = session.getMetamodel().entityPersister(Account.class.getName()).isVersioned();\nif (!versioned) {\n    throw new IllegalArgumentException(Account.class.getName() + \" requires @Version for optimistic locking\");\n}\nsession.lock(account, LockMode.OPTIMISTIC);","typeGuard":null,"tryCatchPattern":"try {\n    session.lock(account, LockMode.OPTIMISTIC);\n}\ncatch (HibernateException e) {\n    if (e.getMessage().contains(\"has no version\")) {\n        throw new IllegalStateException(\"Add @Version to \" + account.getClass().getName() + \" or use pessimistic locking\", e);\n    }\n    throw e;\n}","preventionTips":["Add @Version to any entity that can be locked optimistically; it is cheap insurance","In generic locking aspects, branch on persister.isVersioned() and pick pessimistic locking for unversioned types","Watch inheritance: @Version must sit on the mapped entity class itself, not a non-persistent superclass or sibling"],"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"}