hibernate/hibernate-orm · error · StaleObjectStateException

No rows were returned from JDBC query for versioned entity

Error message

No rows were returned from JDBC query for versioned entity

What it means

While applying a pessimistic lock via its locking SELECT, SqlAstBasedLockingStrategy received zero rows (org.hibernate.sql.results.spi.NoRowException): the row identified by id no longer exists - typically deleted concurrently between load and lock. For entities with an optimistic lock style other than NONE (@Version), Hibernate translates this into StaleObjectStateException with this message so callers treat it as an optimistic-lock failure; for unversioned entities the raw NoRowException propagates instead.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/SqlAstBasedLockingStrategy.java:206

					);
				} );
			}
		}
		catch (LockTimeoutException lockTimeout) {
			throw new PessimisticEntityLockException(
					object,
					String.format( Locale.ROOT, "Lock timeout exceeded attempting to lock row(s) for %s", object ),
					lockTimeout
			);
		}
		catch (NoRowException noRow) {
			if ( !entityToLock.optimisticLockStyle().isNone() ) {
				final String entityName = entityToLock.getEntityName();
				final var statistics = session.getFactory().getStatistics();
				if ( statistics.isStatisticsEnabled() ) {
					statistics.optimisticFailure( entityName );
				}
				throw new StaleObjectStateException( entityName, id,
						"No rows were returned from JDBC query for versioned entity" );
			}
			else {
				throw noRow;
			}
		}
	}

	private static void handleRestriction(
			Object value,
			SelectableMapping jdbcValueMapping,
			QuerySpec rootQuerySpec,
			LoaderSqlAstCreationState sqlAstCreationState,
			TableGroup rootTableGroup,
			JdbcParameterBindings jdbcParameterBindings) {
		final var jdbcParameter = new SqlTypedMappingJdbcParameter( jdbcValueMapping );
		rootQuerySpec.applyPredicate(
				new ComparisonPredicate(

View on GitHub (pinned to fad1729dce)

Solutions

  1. Catch StaleObjectStateException / OptimisticLockException and re-load the entity (em.refresh won't work - use find again) or treat it as not-found
  2. Re-fetch a fresh managed instance before locking instead of locking a long-held detached reference
  3. If deletion is legitimate in your domain, branch on EntityNotFoundException-style handling instead of retrying
  4. For soft deletes, verify row-level filters do not hide rows from the locking select

Example fix

// before
session.lock(order, LockMode.PESSIMISTIC_WRITE); // row may be gone -> StaleObjectStateException

// after
try {
    session.lock(order, LockMode.PESSIMISTIC_WRITE);
} catch (StaleObjectStateException e) {
    Order fresh = session.find(Order.class, order.getId());
    if (fresh == null) {
        // row deleted concurrently: treat as not-found
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the row still exists before requesting the lock
Object id = session.getIdentifier(entity);
boolean exists = session.createQuery("select 1 from Order o where o.id = :id", Integer.class)
        .setParameter("id", id).getResultList().isEmpty() == false;

Try / catch

try {
    session.lock(order, LockMode.PESSIMISTIC_WRITE);
} catch (StaleObjectStateException e) {
    // row vanished between load and lock: reload or treat as not-found
    Order fresh = session.find(Order.class, order.getId());
    if (fresh == null) { /* deleted concurrently */ }
}

Prevention

When it happens

Trigger: session.lock(entity, LockMode.PESSIMISTIC_WRITE) or em.find(id, PESSIMISTIC_WRITE) where another transaction deleted (hard delete, or @SQLDelete) the row after it was loaded; locking a detached/stale reference whose row is gone; follow-on locking racing a concurrent remove().

Common situations: Two users editing the same record with one deleting it; cleanup jobs purging rows that in-flight requests still reference; queue/claim patterns where another worker deleted the item; soft-delete filters hiding rows that the locking select expects.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/52cbd46a3293434b. Report an issue: GitHub.