{"record":{"id":"3697563e9b47283f","repo":"hibernate/hibernate-orm","slug":"spanner-doesn-t-support-for-update-with-skip-locke","errorCode":null,"errorMessage":"Spanner doesn't support for-update with skip locked timeout","messagePattern":"Spanner doesn't support for-update with skip locked timeout","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/SpannerPostgreSQLDialect.java","lineNumber":511,"sourceCode":"\t@Override\n\tpublic String getReadLockString(String aliases, int timeout) {\n\t\treturn getWriteLockString( timeout );\n\t}\n\n\t@Override\n\tpublic String getForUpdateNowaitString() {\n\t\tthrow new UnsupportedOperationException(\n\t\t\t\t\"Spanner doesn't support for-update with no-wait timeout\" );\n\t}\n\n\t@Override\n\tpublic String getForUpdateNowaitString(String aliases) {\n\t\treturn getForUpdateNowaitString();\n\t}\n\n\t@Override\n\tpublic String getForUpdateSkipLockedString() {\n\t\tthrow new UnsupportedOperationException(\n\t\t\t\t\"Spanner doesn't support for-update with skip locked timeout\" );\n\t}\n\n\t@Override\n\tpublic String getForUpdateSkipLockedString(String aliases) {\n\t\treturn getForUpdateSkipLockedString();\n\t}\n\n\t@Override\n\tpublic LockingClauseStrategy getLockingClauseStrategy(\n\t\t\tQuerySpec querySpec, LockOptions lockOptions) {\n\t\tif ( lockOptions == null ) {\n\t\t\treturn NON_CLAUSE_STRATEGY;\n\t\t}\n\t\tvalidateSpannerLockTimeout( lockOptions.getTimeOut() );\n\t\treturn super.getLockingClauseStrategy( querySpec, lockOptions );\n\t}\n","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/SpannerPostgreSQLDialect.java#L493-L529","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the SKIP_LOCKED timeout hint/option on the Spanner PG profile and redesign claim logic as a conditional UPDATE with a lease timestamp.","Use optimistic @Version locking with retry for contended entities.","Catch UnsupportedOperationException at the claim site and degrade to plain FOR UPDATE.","Isolate queue/claim queries in dialect-aware repository variants so the PG-modeled one is never executed against Spanner."],"exampleFix":"// before (PostgreSQL-style, throws on Spanner PG)\n@QueryHints(@QueryHint(name = \"jakarta.persistence.lock.timeout\", value = \"-2\"))\n@Lock(LockModeType.PESSIMISTIC_WRITE)\nList<Task> findOpenTasks(Pageable p);\n\n// after — lease-based claim\n@Modifying @Query(\"update Task t set t.leaseUntil = :until where t.status = 'OPEN' and (t.leaseUntil is null or t.leaseUntil < :now)\")\nint claimTasks(Instant now, Instant until);","handlingStrategy":"validation","validationCode":"if (dialect instanceof SpannerPostgreSQLDialect && lockOptions.getTimeOut() == Timeouts.SKIP_LOCKED_MILLI) {\n  throw new IllegalArgumentException(\"SKIP LOCKED unsupported on Spanner PG; use lease-based claiming\");\n}","typeGuard":null,"tryCatchPattern":"try { return query.getResultList(); }\ncatch (UnsupportedOperationException e) {\n  if (e.getMessage().contains(\"skip locked\")) { /* run lease UPDATE claim instead */ }\n  throw e;\n}","preventionTips":["Port queue logic off SKIP LOCKED before moving to Spanner PG.","Audit Spring @QueryHints for -2 values.","Use optimistic locking for contention."],"tags":["hibernate","cloud-spanner","spanner-postgresql","locking","skip-locked","unsupportedoperationexception"],"backgroundTag":"skip-locked-not-supported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}