hibernate/hibernate-orm · error · NoRowException
SQL query returned no results
Error message
SQL query returned no results
What it means
Thrown by SingleResultConsumer when a query executed with single-result semantics returns zero rows (rowProcessingState.next() was false). In this codebase it surfaces from `SelectionQuery.getResultCount()` (SelectionQueryImpl:1035), the native-query count plan (NativeQueryImpl:1082), and the pessimistic-locking re-select (SqlAstBasedLockingStrategy:176) - for a versioned entity that path converts it into StaleObjectStateException, for an unversioned one it propagates as this NoRowException. It is not the standard `getSingleResult()` failure - that throws jakarta.persistence.NoResultException.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/sql/results/spi/SingleResultConsumer.java:48
}
@Override
public T consume(
JdbcValues jdbcValues,
SharedSessionContractImplementor session,
JdbcValuesSourceProcessingOptions processingOptions,
JdbcValuesSourceProcessingState jdbcValuesSourceProcessingState,
RowProcessingStateStandardImpl rowProcessingState,
RowReader<T> rowReader) {
final var persistenceContext = session.getPersistenceContextInternal();
RuntimeException ex = null;
persistenceContext.beforeLoad();
persistenceContext.getLoadContexts().register( jdbcValuesSourceProcessingState );
try {
rowReader.startLoading( rowProcessingState );
final boolean hadResult = rowProcessingState.next();
if ( !hadResult ) {
throw new NoRowException( "SQL query returned no results" );
}
final T result = rowReader.readRow( rowProcessingState );
rowProcessingState.finishRowProcessing( true );
jdbcValuesSourceProcessingState.registerSubselects();
rowReader.finishUp( rowProcessingState );
jdbcValuesSourceProcessingState.finishUp();
return result;
}
catch (RuntimeException e) {
ex = e;
}
finally {
try {
jdbcValues.finishUp( session );
persistenceContext.afterLoad();
persistenceContext.getLoadContexts().deregister( jdbcValuesSourceProcessingState );
persistenceContext.initializeNonLazyCollections();
}View on GitHub (pinned to fad1729dce)
Solutions
- Re-check existence before locking: a fresh `em.find(...)` (which returns null when gone) before `em.lock(...)`
- Catch NoRowException (and StaleObjectStateException for versioned entities) around the lock and treat it as a concurrent modification: reload or show a 'record no longer exists' outcome
- For soft-deleted/filtered rows, verify the entity passes the current @Where/@Filters criteria before attempting to lock it
- Prefer optimistic locking when concurrent deletes are common, since it fails gracefully at flush
Example fix
// before
em.lock(employee, LockModeType.PESSIMISTIC_WRITE); // NoRowException if row deleted
// after
Employee fresh = em.find(Employee.class, employee.getId());
if (fresh == null) { throw new ObjectNotFoundException(employee.getId(), Employee.class.getName()); }
em.lock(fresh, LockModeType.PESSIMISTIC_WRITE); Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the row still exists before pessimistic locking Employee fresh = em.find(Employee.class, id); if (fresh == null) return handleMissing(id); // row gone - do not lock
Try / catch
try {
em.lock(managedEntity, LockModeType.PESSIMISTIC_WRITE);
} catch (org.hibernate.sql.results.spi.NoRowException e) {
// unversioned entity: row vanished before SELECT ... FOR UPDATE ran
throw new ConcurrentDeletionException(id);
} catch (jakarta.persistence.OptimisticLockException | org.hibernate.StaleObjectStateException e) {
// versioned entity: same race surfaced as stale object
throw new ConcurrentDeletionException(id);
} Prevention
- Re-find the entity (fresh, in the same transaction) immediately before em.lock
- Treat NoRowException from locking as a concurrent delete: reload, notify, or fail gracefully
- For soft-deleted models check @Where criteria match before locking
- Prefer optimistic locking in workflows where concurrent deletes are frequent
When it happens
Trigger: `session.lock(entity, LockMode.PESSIMISTIC_WRITE)` (or `em.lock`/`em.refresh(PESSIMISTIC)`) where the row was deleted by a concurrent transaction before the `select ... for update` ran; pessimistic locking an entity whose row is excluded by @Where/@OnLoad filters or row-level security; a count query variant that returns no rows (e.g. grouped native count).
Common situations: Two users editing the same record, one deleting it while the other locks it; locking stale detached entities whose rows are gone; @Where-based soft-delete filters hiding the row; concurrent admin deletes racing background jobs that lock first.
Related errors
- No rows were returned from JDBC query for versioned entity
- Newer version [" + latestVersion + "] of entity [" + infoStr
- %s for entity %s#%s
- Could not obtain pessimistic lock
- Row was already updated or deleted by another transaction
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/147e76fa00256d05.
Report an issue: GitHub.