hibernate/hibernate-orm · error · OptimisticEntityLockException
Newer version [" + latestVersion + "] of entity [" + infoStr
Error message
Newer version [" + latestVersion + "] of entity [" + infoString( entry.getEntityName(), entry.getId() ) + "] found in database
What it means
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.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java:40
private final Object object;
/**
* Constructs an EntityVerifyVersionProcess
*
* @param object The entity instance
*/
public EntityVerifyVersionProcess(@Nonnull Object object) {
this.object = object;
}
@Override
public void doBeforeTransactionCompletion(@Nonnull SharedSessionContractImplementor session) {
final var entry = session.getPersistenceContext().getEntry( object );
// Don't check the version for an entity that is not in the PersistenceContext
if ( entry != null ) {
final Object latestVersion = entry.getPersister().getCurrentVersion( entry.getId(), session );
if ( !entry.getVersion().equals( latestVersion ) ) {
throw new OptimisticEntityLockException(
object,
"Newer version ["
+ latestVersion
+ "] of entity ["
+ infoString( entry.getEntityName(), entry.getId() )
+ "] found in database"
);
}
}
}
}
View on GitHub (pinned to fad1729dce)
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)
Example fix
// before - single attempt, fails at commit with OptimisticEntityLockException
em.getTransaction().begin();
Account a = em.find(Account.class, id);
a.withdraw(amount);
em.getTransaction().commit();
// after - optimistic retry loop with fresh transaction per attempt
for (int i = 0; i < 5; i++) {
try {
em.getTransaction().begin();
Account a = em.find(Account.class, id);
a.withdraw(amount);
em.getTransaction().commit();
break;
} catch (OptimisticEntityLockException e) {
if (em.getTransaction().isActive()) em.getTransaction().rollback();
}
} Defensive patterns
Strategy: retry
Try / catch
// optimistic retry loop - each attempt gets a fresh transaction
for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
tx = em.getTransaction(); tx.begin();
Account a = em.find(Account.class, id);
a.withdraw(amount);
tx.commit();
return;
} catch (OptimisticEntityLockException | StaleObjectStateException e) {
if (tx.isActive()) tx.rollback();
em.clear(); // start the next attempt with fresh state
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- %s for entity %s#%s
- No rows were returned from JDBC query for versioned entity
- <causeMessage> for entity [<entityName> with id '<id>']
- Entity '{name}' has 'OptimisticLockType.{optimisticLockStyle
- Cannot update previous revision for entity %s and id %s (%s
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/156ac9eff7a41407.
Report an issue: GitHub.