hibernate/hibernate-orm · error · IllegalStateException

Locking not supported for ProcedureCall

Error message

Locking not supported for ProcedureCall

What it means

StoredProcedureQuery inherits setLockMode(LockModeType) from the JPA Query contract, and the JPA specification requires IllegalStateException when locking is requested on a stored-procedure query. ProcedureCallImpl implements exactly that (see the source comment on getLockMode): pessimistic locking is a Hibernate/JPA translation feature with no meaning for a database call.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java:322

	@Nonnull
	public ProcedureCallImplementor<R> setMaxResults(int maxResult) {
		checkNotClosed();
		super.setMaxResults( maxResult );
		return this;
	}

	@Override
	@Nonnull
	public ProcedureCallImplementor<R> setFirstResult(int startPosition) {
		checkNotClosed();
		super.setFirstResult( startPosition );
		return this;
	}

	@Override
	@Nonnull
	public ProcedureCallImplementor<R> setLockMode(@Nonnull LockModeType lockMode) {
		throw new IllegalStateException( "Locking not supported for ProcedureCall" );
	}

	@Override
	@Nonnull
	public ProcedureCallImplementor<R> addQueryHint(@Nonnull String hint) {
		checkNotClosed();
		super.addQueryHint( hint );
		return this;
	}

	@Override @Deprecated
	@SuppressWarnings("removal")
	@Nonnull
	public Query<R> setEntityGraph(@Nonnull EntityGraph<? super R> graph, @Nonnull GraphSemantic semantic) {
		throw new UnsupportedOperationException( "Entity graph not supported for ProcedureCall" );
	}

	@Override @Deprecated

View on GitHub (pinned to fad1729dce)

Solutions

  1. Guard with instanceof: skip setLockMode when the query is a ProcedureCall/StoredProcedureQuery.
  2. If row locking is genuinely needed, do SELECT ... FOR UPDATE inside the procedure/function itself.
  3. Split the generic API so locking applies only to selection queries.

Example fix

// before
jakarta.persistence.Query q = em.createStoredProcedureQuery("reserve_stock");
q.setLockMode(LockModeType.PESSIMISTIC_WRITE); // throws IllegalStateException

// after
if (q instanceof StoredProcedureQuery) {
    // locking handled inside the procedure (SELECT ... FOR UPDATE)
} else {
    q.setLockMode(LockModeType.PESSIMISTIC_WRITE);
}
Defensive patterns

Strategy: type-guard

Type guard

static boolean supportsLocking(jakarta.persistence.Query q) {
    return !( q instanceof org.hibernate.procedure.ProcedureCall )
            && !( q instanceof StoredProcedureQuery );
}

Try / catch

try {
    q.setLockMode( lockMode );
} catch (IllegalStateException e) {
    // spec-required failure for procedure calls; locking belongs inside the procedure
}

Prevention

When it happens

Trigger: Calling setLockMode(LockModeType.PESSIMISTIC_WRITE) (or OPTIMISTIC) on a StoredProcedureQuery/ProcedureCall — usually from generic DAO code that applies a lock mode to every query in a write transaction.

Common situations: Reusable repository layers with a findByQuery(Query q, LockModeType lock) method; @Lock annotations on repository methods that end up invoking procedures; copy-pasted query-building helpers.

Related errors


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