hibernate/hibernate-orm · error · StaleObjectStateException
Row was already updated or deleted by another transaction
Error message
Row was already updated or deleted by another transaction
What it means
Error "Row was already updated or deleted by another transaction" thrown in hibernate/hibernate-orm.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java:453
// We must clone embedded composite identifiers, or we will get
// back the same instance that we pass in. Apply the special MERGE
// fetch profile and perform the resolution (Session#get).
final Object result =
session.getLoadQueryInfluencers()
.fromInternalFetchProfile( CascadingFetchProfile.MERGE,
() -> session.find( entityName, clonedIdentifier ) );
if ( result == null ) {
EVENT_LISTENER_LOGGER.detachedInstanceNotFoundInDatabase();
// we got here because we assumed that an instance
// with an assigned id and no version was detached
// when it was really transient (or deleted)
final Boolean knownTransient = persister.isTransient( entity, session );
if ( knownTransient == Boolean.FALSE ) {
// we know for sure it's detached (generated id
// or a version property), and so the instance
// must have been deleted by another transaction
throw new StaleObjectStateException( entityName, id );
}
else {
// we know for sure it's transient, or we just
// don't have information (assigned id and no
// version property) so keep assuming transient
entityIsTransient( event, clonedIdentifier, copyCache );
}
}
else {
// before cascade!
copyCache.put( entity, result, true );
final Object target = targetEntity( event, entity, persister, id, result );
// cascade first, so that all unsaved objects get their
// copy created before we actually copy
cascadeOnMerge( session, persister, entity, copyCache );
final var interceptor = session.getInterceptor();
final String[] propertyNames = persister.getPropertyNames();View on GitHub (pinned to fad1729dce)
Solutions
- Use optimistic locking (@Version) and catch OptimisticLockException to retry with fresh data.
- Reload the entity before merging when concurrent updates are expected.
When it happens
Trigger: Thrown when a flush detects that the database row was concurrently modified or removed by another transaction (stale optimistic state).
Common situations: Typical situations: concurrent transactions updating the same row; long-running transactions with optimistic locking; reusing stale entities across transactions.
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/1c6e4c2939ea14a8.
Report an issue: GitHub.