hibernate/hibernate-orm · error · UnsupportedOperationException

Spanner doesn't support for-update with skip locked timeout

Error message

Spanner doesn't support for-update with skip locked timeout

What it means

SpannerPostgreSQLDialect.getForUpdateSkipLockedString() throws UnsupportedOperationException('Spanner doesn't support for-update with skip locked timeout'). The Spanner PostgreSQL interface accepts PostgreSQL syntax broadly, but SKIP LOCKED semantics (skipping rows held by other transactions) do not exist in Spanner's transaction model, so the dialect blocks the request at SQL rendering. The alias overload delegates to this method and throws identically.

Source

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

	@Override
	public String getReadLockString(String aliases, int timeout) {
		return getWriteLockString( timeout );
	}

	@Override
	public String getForUpdateNowaitString() {
		throw new UnsupportedOperationException(
				"Spanner doesn't support for-update with no-wait timeout" );
	}

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

	@Override
	public String getForUpdateSkipLockedString() {
		throw new UnsupportedOperationException(
				"Spanner doesn't support for-update with skip locked timeout" );
	}

	@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 );
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove the SKIP_LOCKED timeout hint/option on the Spanner PG profile and redesign claim logic as a conditional UPDATE with a lease timestamp.
  2. Use optimistic @Version locking with retry for contended entities.
  3. Catch UnsupportedOperationException at the claim site and degrade to plain FOR UPDATE.
  4. Isolate queue/claim queries in dialect-aware repository variants so the PG-modeled one is never executed against Spanner.

Example fix

// before (PostgreSQL-style, throws on Spanner PG)
@QueryHints(@QueryHint(name = "jakarta.persistence.lock.timeout", value = "-2"))
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Task> findOpenTasks(Pageable p);

// after — lease-based claim
@Modifying @Query("update Task t set t.leaseUntil = :until where t.status = 'OPEN' and (t.leaseUntil is null or t.leaseUntil < :now)")
int claimTasks(Instant now, Instant until);
Defensive patterns

Strategy: validation

Validate before calling

if (dialect instanceof SpannerPostgreSQLDialect && lockOptions.getTimeOut() == Timeouts.SKIP_LOCKED_MILLI) {
  throw new IllegalArgumentException("SKIP LOCKED unsupported on Spanner PG; use lease-based claiming");
}

Try / catch

try { return query.getResultList(); }
catch (UnsupportedOperationException e) {
  if (e.getMessage().contains("skip locked")) { /* run lease UPDATE claim instead */ }
  throw e;
}

Prevention

When it happens

Trigger: On the Spanner PG interface: Query.setLockOptions()/setLockMode() with timeout -2 (LockOptions.SKIP_LOCKED / Timeouts.SKIP_LOCKED_MILLI), or Spring hints jakarta.persistence.lock.timeout=-2 with pessimistic locks; also getForUpdateSkipLockedString(aliases) delegations.

Common situations: Lifting a PostgreSQL job-queue implementation (SELECT ... FOR UPDATE SKIP LOCKED) onto Spanner PG with minimal code changes; generic locking aspects applied to all datasources; tests that exercise all LockOptions constants across profiles.

Understand the failure class

Related errors


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