hibernate/hibernate-orm · error · StaleObjectStateException
Query result contains conflicting version of entity already
Error message
Query result contains conflicting version of entity already held in persistence context
What it means
When a query result provides a row for an entity already managed in the persistence context (first-level cache), EntityInitializerImpl checks the @Version column against the version held in the entity's EntityEntry. If they are not equal, it records an optimistic failure in statistics and throws StaleObjectStateException with 'Query result contains conflicting version of entity already held in persistence context'. Hibernate refuses to hand back an instance whose state contradicts what the session already believes, because two versions of the same entity would coexist in one unit of work.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java:1479
* Check the version of the object in the {@code RowProcessingState} against
* the object version in the session cache, throwing an exception
* if the version numbers are different
*/
private void checkVersion(
EntityInitializerData data,
EntityEntry entry,
RowProcessingState rowProcessingState) {
final Object version = entry.getVersion();
if ( version != null ) {
// null version means the object is in the process of being loaded somewhere else in the ResultSet
final Object currentVersion = versionAssembler.assemble( rowProcessingState );
if ( !data.concreteDescriptor.getVersionType().isEqual( version, currentVersion ) ) {
final String entityName = data.concreteDescriptor.getEntityName();
final var statistics = rowProcessingState.getSession().getFactory().getStatistics();
if ( statistics.isStatisticsEnabled() ) {
statistics.optimisticFailure( entityName );
}
throw new StaleObjectStateException( entityName, entry.getId(),
"Query result contains conflicting version of entity already held in persistence context" );
}
}
}
/**
* Used by Hibernate Reactive
*/
protected Object resolveEntityInstance2(EntityInitializerData data) {
if ( data.entityHolder.getEntityInitializer() == this ) {
assert data.entityHolder.getEntity() == null;
return resolveEntityInstance( data );
}
else {
// the entity is already being loaded elsewhere
return data.entityHolder.getEntity();
}View on GitHub (pinned to fad1729dce)
Solutions
- Refresh the instance from the database (em.refresh(entity)) before re-querying, so the session's snapshot matches the row.
- Keep sessions short (one unit of work per request); evict or clear the session when an external update may have occurred.
- Acquire an appropriate lock when loading (LockModeType.OPTIMISTIC / PESSIMISTIC_WRITE) so concurrent updates cannot slip in between load and re-query.
- For bulk external updates that bypass the session, evict the affected entities (or clear caches) immediately after the update.
Example fix
// before: stale managed instance conflicts with the fresh row
Order o = em.find(Order.class, 1L); // version 1
otherService.updateOrderToVersion2(); // commits version 2
em.createQuery("select o from Order o", Order.class).getResultList(); // StaleObjectStateException
// after: refresh or evict before re-querying
Order o = em.find(Order.class, 1L);
otherService.updateOrderToVersion2();
em.refresh(o); // re-read version 2
em.createQuery("select o from Order o", Order.class).getResultList(); Defensive patterns
Strategy: retry
Validate before calling
// Before re-querying in a long session, compare the managed version to the DB
Object managed = em.find(Order.class, id);
Object dbVersion = em.createQuery(
"select o.version from Order o where o.id = :id", Object.class)
.setParameter("id", id).getSingleResult();
if (managed != null && !Objects.equals(((Versioned) managed).getVersion(), dbVersion)) {
em.refresh(managed); // align the persistence context before the conflicting query
} Try / catch
for (int attempt = 0; attempt < 2; attempt++) {
try {
return em.createQuery("select o from Order o", Order.class).getResultList();
} catch (StaleObjectStateException e) {
em.clear(); // drop stale persistence-context state, then retry once
}
}
throw new IllegalStateException("order query keeps conflicting with persistence context"); Prevention
- Keep sessions short - one unit of work per request; never cache EntityManager across requests.
- Refresh (em.refresh) instances you keep holding after external updates.
- After bulk updates, evict/clear affected entities from still-open sessions.
- Load rows you intend to modify with an explicit lock (OPTIMISTIC/PESSIMISTIC) so versions cannot diverge silently.
When it happens
Trigger: Session loads entity E at version 1; another transaction (or a direct JDBC/bulk update) commits version 2; the same session then runs any query returning E's row - the version mismatch is detected during result processing.
Common situations: Long-lived sessions (desktop apps, stateful web flows) spanning external updates; bulk JPQL/native updates committed by other instances of the application; background jobs mutating rows while a user's session stays open; missing evict/detach of entities after data changes outside the session.
Related errors
- Entity '{name}' has 'OptimisticLockType.{optimisticLockStyle
- Newer version [" + latestVersion + "] of entity [" + infoStr
- %s for entity %s#%s
- <causeMessage> for entity [<entityName> with id '<id>']
- Entity '${persister.getEntityName()}' has no version and may
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/8c6d63791f5f91b6.
Report an issue: GitHub.