{"record":{"id":"156ac9eff7a41407","repo":"hibernate/hibernate-orm","slug":"newer-version-latestversion-of-entity","errorCode":null,"errorMessage":"Newer version [\" + latestVersion + \"] of entity [\" + infoString( entry.getEntityName(), entry.getId() ) + \"] found in database","messagePattern":"Newer version \\[\" \\+ latestVersion \\+ \"\\] of entity \\[\" \\+ infoString\\( entry\\.getEntityName\\(\\), entry\\.getId\\(\\) \\) \\+ \"\\] found in database","errorType":"exception","errorClass":"OptimisticEntityLockException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java","lineNumber":40,"sourceCode":"\tprivate final Object object;\n\n\t/**\n\t * Constructs an EntityVerifyVersionProcess\n\t *\n\t * @param object The entity instance\n\t */\n\tpublic EntityVerifyVersionProcess(@Nonnull Object object) {\n\t\tthis.object = object;\n\t}\n\n\t@Override\n\tpublic void doBeforeTransactionCompletion(@Nonnull SharedSessionContractImplementor session) {\n\t\tfinal var entry = session.getPersistenceContext().getEntry( object );\n\t\t// Don't check the version for an entity that is not in the PersistenceContext\n\t\tif ( entry != null ) {\n\t\t\tfinal Object latestVersion = entry.getPersister().getCurrentVersion( entry.getId(), session );\n\t\t\tif ( !entry.getVersion().equals( latestVersion ) ) {\n\t\t\t\tthrow new OptimisticEntityLockException(\n\t\t\t\t\t\tobject,\n\t\t\t\t\t\t\"Newer version [\"\n\t\t\t\t\t\t\t\t+ latestVersion\n\t\t\t\t\t\t\t\t+ \"] of entity [\"\n\t\t\t\t\t\t\t\t+ infoString( entry.getEntityName(), entry.getId() )\n\t\t\t\t\t\t\t\t+ \"] found in database\"\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n","sourceCodeStart":22,"sourceCodeEnd":52,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java#L22-L52","documentation":"EntityVerifyVersionProcess (EntityVerifyVersionProcess.java:40) runs at before-transaction-completion for entities with an optimistic @Version that were updated during the transaction. It re-reads the current version from the database and throws OptimisticEntityLockException (\"Newer version [...] of entity [...] found in database\") when the stored version no longer equals the one your flush used. This closes the gap between flush and commit: another transaction modified the row after your UPDATE was flushed but before your commit, i.e. a classic lost-update detected late.","triggerScenarios":"Versioned entity updated and flushed, then a concurrent transaction commits an update to the same row before your transaction commits; long-running transactions (extended @Transactional methods, OSIV request handling, batch jobs) where the post-flush window is large; two threads both bumping the same hot row.","commonSituations":"Conversation-style flows (page loads entity, user thinks, page saves) without stale-object state checks; background schedulers updating the same counters/rows as web requests; optimistic @Version columns that some code path bypasses (native SQL updates) so the version changed outside ORM control.","solutions":["Retry the whole business operation in a fresh transaction: catch OptimisticEntityLockException, reload the entity, reapply changes, commit - a standard optimistic retry loop","Shrink the transaction: move the read-modify-write as late and as short as possible so the flush-to-commit gap is minimal","For hot rows where retry storms are likely, switch that access to a pessimistic lock (LockModeType.PESSIMISTIC_WRITE / buildLockRequest) held for the transaction","Make sure every writer goes through Hibernate so the version column is bumped consistently (no native UPDATEs bypassing the version)"],"exampleFix":"// before - single attempt, fails at commit with OptimisticEntityLockException\nem.getTransaction().begin();\nAccount a = em.find(Account.class, id);\na.withdraw(amount);\nem.getTransaction().commit();\n\n// after - optimistic retry loop with fresh transaction per attempt\nfor (int i = 0; i < 5; i++) {\n    try {\n        em.getTransaction().begin();\n        Account a = em.find(Account.class, id);\n        a.withdraw(amount);\n        em.getTransaction().commit();\n        break;\n    } catch (OptimisticEntityLockException e) {\n        if (em.getTransaction().isActive()) em.getTransaction().rollback();\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// optimistic retry loop - each attempt gets a fresh transaction\nfor (int attempt = 0; attempt < MAX_RETRIES; attempt++) {\n    try {\n        tx = em.getTransaction(); tx.begin();\n        Account a = em.find(Account.class, id);\n        a.withdraw(amount);\n        tx.commit();\n        return;\n    } catch (OptimisticEntityLockException | StaleObjectStateException e) {\n        if (tx.isActive()) tx.rollback();\n        em.clear(); // start the next attempt with fresh state\n    }\n}","preventionTips":["Keep read-modify-write transactions short; never hold one across user think time","Ensure every writer bumps the @Version column (no native updates bypassing the ORM)","For hot rows, switch to pessimistic locking instead of relying on retries"],"tags":["optimistic-locking","concurrency","versioning","transaction","hibernate"],"backgroundTag":"optimistic-lock-conflict","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}