{"record":{"id":"50690f3f52369350","repo":"hibernate/hibernate-orm","slug":"entity-has-no-version-and-may-not-be-locked-v","errorCode":null,"errorMessage":"Entity '{}' has no version and may not be locked via 'update' statement","messagePattern":"Entity '(.+?)' has no version and may not be locked via 'update' statement","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractPessimisticUpdateLockingStrategy.java","lineNumber":46,"sourceCode":"\tprivate final LockMode lockMode;\n\tprivate final String sql;\n\n\t/**\n\t * Construct a locking strategy based on SQL UPDATE statements.\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.  Note that\n\t * read-locks are not valid for this strategy.\n\t */\n\tpublic AbstractPessimisticUpdateLockingStrategy(EntityPersister lockable, LockMode lockMode) {\n\t\tthis.lockable = lockable;\n\t\tthis.lockMode = lockMode;\n\t\tif ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) {\n\t\t\tthrow new HibernateException( \"Lock mode \" + lockMode\n\t\t\t\t\t\t+ \" not valid for locking via 'update' statement\" );\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 via 'update' statement\" );\n\t\t}\n\t\tthis.sql = generateLockString();\n\t}\n\n\t@Override\n\tpublic void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) {\n\t\ttry {\n\t\t\tdoLock( id, version, session );\n\t\t}\n\t\tcatch (JDBCException e) {\n\t\t\tthrow new PessimisticEntityLockException( object, \"Could not obtain pessimistic lock\", e );\n\t\t}\n\t}\n\n\tvoid doLock(Object id, Object version, SharedSessionContractImplementor session) {\n\t\ttry {\n\t\t\tfinal var factory = session.getFactory();","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractPessimisticUpdateLockingStrategy.java#L28-L64","documentation":"Update-based pessimistic locking works by executing UPDATE ... where id=? and version=?, so a version column is functionally required for the strategy to be safe. AbstractPessimisticUpdateLockingStrategy's constructor therefore throws HibernateException 'Entity '<name>' has no version and may not be locked via 'update' statement' when lockable.isVersioned() is false. The check runs at strategy construction, i.e. the first time that entity is locked this way (or eagerly when the dialect builds it).","triggerScenarios":"Calling session.lock(entity, LockMode.PESSIMISTIC_WRITE) (or a buildLockRequest with a pessimistic mode) on an entity class that has no @Version field, while the active dialect locks that mode via an UPDATE statement. Also triggered directly by new UpdateLockingStrategy(persister, mode) in custom dialect code.","commonSituations":"Adding pessimistic locking to a legacy entity model that never carried version columns; enabling a locking-oriented feature (e.g. a lock-then-read batch job) on databases where Hibernate emulates locks via UPDATE; entities intentionally modeled without optimistic-lock metadata.","solutions":["Add an @Version column (e.g. private long version; with a version column in the schema) to the entity being locked","If a version column is impossible, avoid update-based locking: use a select-based pessimistic lock (select ... for update) by relying on dialects/modes that support it, or optimistic locking","Pre-verify with session.getMetamodel().entityPersister(clazz).isVersioned() before issuing pessimistic locks","For custom dialects, route unversioned entities to a strategy that does not require a version"],"exampleFix":"// before\n@Entity\npublic class Person {\n    @Id private Long id;\n    private String name;\n}\n\n// after\n@Entity\npublic class Person {\n    @Id private Long id;\n    @Version private long version;\n    private String name;\n}","handlingStrategy":"validation","validationCode":"// Verify versionability before pessimistic locking\nEntityPersister persister = session.getEntityPersister(Person.class.getName(), person);\nif (!persister.isVersioned()) {\n    throw new IllegalArgumentException(\"Cannot use update-based pessimistic lock on unversioned \" + Person.class.getName());\n}\nsession.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(person);","typeGuard":null,"tryCatchPattern":"try {\n    session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(person);\n}\ncatch (HibernateException e) {\n    if (e.getMessage().contains(\"has no version\")) {\n        throw new IllegalStateException(\"Add @Version to \" + person.getClass().getName() + \" or use a non-update lock strategy\", e);\n    }\n    throw e;\n}","preventionTips":["Add @Version columns to every entity that participates in pessimistic locking","Keep an architecture test (e.g. ClassGraph/ArchUnit scan) asserting locked entities carry @Version","Document per entity which lock modes are legal"],"tags":["hibernate","locking","pessimistic-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"}