hibernate/hibernate-orm · error · UnsupportedOperationException

Spanner does not support lock timeout.

Error message

Spanner does not support lock timeout.

What it means

SpannerDialect.validateSpannerLockTimeout(int millis) throws UnsupportedOperationException('Spanner does not support lock timeout.') when Timeouts.isRealTimeout(millis) is true — i.e. a positive millisecond wait was requested. Cloud Spanner's FOR UPDATE has no WAIT n / NOWAIT / SKIP LOCKED modifiers, so any concrete timeout is impossible; only the sentinel 'no timeout' value is accepted. The validator runs inside getReadLockString/getWriteLockString before SQL is built.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java:1294

	@Override
	public String getForUpdateNowaitString(String aliases) {
		throw new UnsupportedOperationException( "Spanner does not support no wait." );
	}

	@Override
	public String getForUpdateSkipLockedString() {
		throw new UnsupportedOperationException( "Spanner does not support skip locked." );
	}

	@Override
	public String getForUpdateSkipLockedString(String aliases) {
		throw new UnsupportedOperationException( "Spanner does not support skip locked." );
	}

	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 boolean supportsOffsetInSubquery() {
		return true;
	}

	@Override
	public char openQuote() {
		return '`';
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove the positive lock timeout property/hint — leave it unset (or 0/no-timeout semantics) so FOR UPDATE renders without modifiers.
  2. Bound waits at the transaction level instead: set a Spanner transaction timeout (e.g. spring.transaction.default-timeout or the driver's transaction options).
  3. Catch UnsupportedOperationException around locking calls as a safety net and retry without the timeout.
  4. Prefer optimistic @Version locking when wait bounding was the only goal.

Example fix

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

# after (remove; Spanner FOR UPDATE supports no timeout modifier)
# spring.jpa.properties.jakarta.persistence.lock.timeout=  <- unset
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

try { return session.find(Order.class, id, lockOptions); }
catch (UnsupportedOperationException e) {
  if (e.getMessage().contains("lock timeout")) { /* retry with no timeout; bound via transaction timeout */ }
  throw e;
}

Prevention

When it happens

Trigger: Setting jakarta.persistence.lock.timeout=5000 (or Spring @QueryHint lock.timeout, or LockOptions.setTimeOut(5000)) with pessimistic locking on a Spanner connection; the value reaches validateSpannerLockTimeout via getReadLockString(timeout.milliseconds()) and throws.

Common situations: Copy-pasted JPA properties from other databases where a lock wait limit is standard ops practice; Spring @Lock(PESSIMISTIC_WRITE) repositories with a timeout hint; attempts to bound lock waits to avoid long transaction aborts on Spanner.

Understand the failure class

Related errors


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