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

  1. Re-check existence before locking: a fresh `em.find(...)` (which returns null when gone) before `em.lock(...)`
  2. 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
  3. For soft-deleted/filtered rows, verify the entity passes the current @Where/@Filters criteria before attempting to lock it
  4. 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

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


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