hibernate/hibernate-orm · error · UnsupportedOperationException

Spanner does not support lock timeout.

Error message

Spanner does not support lock timeout.

What it means

SpannerPostgreSQLDialect.validateSpannerLockTimeout() throws UnsupportedOperationException('Spanner does not support lock timeout.') when Timeouts.isRealTimeout(millis) is true — a positive lock-wait value. The validator runs from getLockingClauseStrategy(querySpec, lockOptions) before any lock clause is rendered, so any concrete wait duration on a pessimistic lock fails immediately. Spanner's FOR UPDATE simply has no WAIT n syntax in either dialect mode.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/SpannerPostgreSQLDialect.java:532

	@Override
	public String getForUpdateSkipLockedString(String aliases) {
		return getForUpdateSkipLockedString();
	}

	@Override
	public LockingClauseStrategy getLockingClauseStrategy(
			QuerySpec querySpec, LockOptions lockOptions) {
		if ( lockOptions == null ) {
			return NON_CLAUSE_STRATEGY;
		}
		validateSpannerLockTimeout( lockOptions.getTimeOut() );
		return super.getLockingClauseStrategy( querySpec, lockOptions );
	}

	private static void validateSpannerLockTimeout(int millis) {
		if ( Timeouts.isRealTimeout( millis ) ) {
			throw new UnsupportedOperationException( "Spanner does not support lock timeout." );
		}
		if ( millis == Timeouts.SKIP_LOCKED_MILLI ) {
			throw new UnsupportedOperationException( "Spanner does not support skip locked." );
		}
		if ( millis == Timeouts.NO_WAIT_MILLI ) {
			throw new UnsupportedOperationException( "Spanner does not support no wait." );
		}
	}

	@Override
	public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
		super.contributeTypes( typeContributions, serviceRegistry );

		final var configurationService = serviceRegistry.requireService( ConfigurationService.class );

		this.useIntegerForPrimaryKey = configurationService.getSetting(
				USE_INTEGER_FOR_PRIMARY_KEY,
				StandardConverters.BOOLEAN,

View on GitHub (pinned to fad1729dce)

Solutions

  1. Delete the positive lock-timeout setting for the Spanner PG datasource (no lock.timeout hint / LockOptions default).
  2. Bound waits with transaction timeouts instead — configure the transaction manager / Spanner session timeout.
  3. Catch UnsupportedOperationException around locking calls as a guard rail in shared code.
  4. Switch the affected entity to optimistic locking if the timeout was only protecting against contention.

Example fix

# before
spring.jpa.properties.jakarta.persistence.lock.timeout=2000

# after — unset for Spanner; bound waits at transaction level
spring.transaction.default-timeout=5s
Defensive patterns

Strategy: validation

Validate before calling

if (Timeouts.isRealTimeout(lockOptions.getTimeOut())
    && (dialect instanceof SpannerDialect || dialect instanceof SpannerPostgreSQLDialect)) {
  lockOptions.setTimeOut(LockOptions.NO_TIMEOUT);
}

Try / catch

try { return query.getResultList(); }
catch (UnsupportedOperationException e) {
  if (e.getMessage().contains("lock timeout")) { /* retry without timeout */ }
  throw e;
}

Prevention

When it happens

Trigger: Calling a locked query/finder on the Spanner PostgreSQL interface with LockOptions/QueryHints carrying a positive timeout, e.g. `lo.setTimeOut(2000)` or jakarta.persistence.lock.timeout=2000 with PESSIMISTIC_WRITE; getLockingClauseStrategy validates lockOptions.getTimeOut() and throws.

Common situations: Applications migrated from real PostgreSQL that kept lock.timeout in persistence.xml; ops teams adding wait bounds to prevent pile-ups, discovering Spanner rejects them; AOP locking templates that apply one timeout everywhere.

Understand the failure class

Related errors


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