{"record":{"id":"f3f3609789e064a0","repo":"hibernate/hibernate-orm","slug":"entity-has-no-version-and-may-not-be-locked-a-f3f360","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/PessimisticForceIncrementLockingStrategy.java","lineNumber":41,"sourceCode":"\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 PessimisticForceIncrementLockingStrategy(EntityPersister lockable, LockMode lockMode) {\n\t\tthis.lockable = lockable;\n\t\tthis.lockMode = lockMode;\n\t\t// ForceIncrement can be used for PESSIMISTIC_READ, PESSIMISTIC_WRITE or PESSIMISTIC_FORCE_INCREMENT\n\t\tif ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) {\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, SharedSessionContractImplementor session) {\n\t\tfinal var entry = session.getPersistenceContextInternal().getEntry( object );\n\t\tOptimisticLockHelper.forceVersionIncrement( object, entry, session );\n\t}\n\n\t/**\n\t * Retrieve the specific lock mode defined.\n\t *\n\t * @return The specific lock mode.\n\t */\n\tprotected LockMode getLockMode() {\n\t\treturn lockMode;\n\t}","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java#L23-L59","documentation":"Like all version-increment strategies, PessimisticForceIncrementLockingStrategy requires the entity to be versioned because its lock action is 'bump the version now'. After checking the lock mode, the constructor throws HibernateException 'Entity '<name>' has no version and may not be locked at level <mode>' when lockable.isVersioned() is false. The message identifies both the entity and the lock level requested, so the offending mapping is easy to find.","triggerScenarios":"session.lock(entity, LockMode.PESSIMISTIC_FORCE_INCREMENT) or EntityManager.lock(entity, LockModeType.PESSIMISTIC_FORCE_INCREMENT) on an entity without @Version; direct new PessimisticForceIncrementLockingStrategy(persister, mode) where the persister reports no version. Fires on first lock (strategy construction).","commonSituations":"Applying a uniform 'lock before update' policy across all entities, some of which predate the policy and lack version fields; read-only/reporting entities drawn into transactional flows; version column dropped in the schema but mapping not updated.","solutions":["Add @Version (and the DB column) to the entity being force-increment locked","Use plain PESSIMISTIC_WRITE (select/update-based lock without forced increment) if adding a version is not possible","Add an isVersioned() guard to shared locking utilities and fail with a clear domain-specific message"],"exampleFix":"// before\n@Entity\npublic class Counter {\n    @Id private Long id;\n    private long value;\n}\n\n// after\n@Entity\npublic class Counter {\n    @Id private Long id;\n    @Version private long version;\n    private long value;\n}","handlingStrategy":"validation","validationCode":"EntityPersister persister = session.getEntityPersister(Counter.class.getName(), counter);\nif (!persister.isVersioned()) {\n    throw new IllegalArgumentException(Counter.class.getName() + \" needs @Version for forced version increment\");\n}\nsession.lock(counter, LockMode.PESSIMISTIC_FORCE_INCREMENT);","typeGuard":null,"tryCatchPattern":"try {\n    session.lock(counter, LockMode.PESSIMISTIC_FORCE_INCREMENT);\n}\ncatch (HibernateException e) {\n    if (e.getMessage().contains(\"has no version\")) {\n        throw new IllegalStateException(\"Add @Version to \" + counter.getClass().getName() + \" - force-increment needs a version to bump\", e);\n    }\n    throw e;\n}","preventionTips":["Force-increment locking literally increments @Version - no version, no lock","Include @Version in the entity template used by code generators","When applying a blanket locking policy, exempt or upgrade unversioned entities explicitly"],"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"}