{"record":{"id":"bf83ccac968bc74c","repo":"hibernate/hibernate-orm","slug":"spanner-does-not-support-skip-locked","errorCode":null,"errorMessage":"Spanner does not support skip locked.","messagePattern":"Spanner does not support skip locked\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java","lineNumber":1284,"sourceCode":"\n\t@Override\n\tpublic String getReadLockString(Timeout timeout) {\n\t\treturn getReadLockString( timeout.milliseconds() );\n\t}\n\n\t@Override\n\tpublic String getForUpdateNowaitString() {\n\t\tthrow new UnsupportedOperationException( \"Spanner does not support no wait.\" );\n\t}\n\n\t@Override\n\tpublic String getForUpdateNowaitString(String aliases) {\n\t\tthrow new UnsupportedOperationException( \"Spanner does not support no wait.\" );\n\t}\n\n\t@Override\n\tpublic String getForUpdateSkipLockedString() {\n\t\tthrow new UnsupportedOperationException( \"Spanner does not support skip locked.\" );\n\t}\n\n\t@Override\n\tpublic String getForUpdateSkipLockedString(String aliases) {\n\t\tthrow new UnsupportedOperationException( \"Spanner does not support skip locked.\" );\n\t}\n\n\tprivate static void validateSpannerLockTimeout(int millis) {\n\t\tif ( Timeouts.isRealTimeout( millis ) ) {\n\t\t\tthrow new UnsupportedOperationException( \"Spanner does not support lock timeout.\" );\n\t\t}\n\t\tif ( millis == Timeouts.SKIP_LOCKED_MILLI ) {\n\t\t\tthrow new UnsupportedOperationException( \"Spanner does not support skip locked.\" );\n\t\t}\n\t\tif ( millis == Timeouts.NO_WAIT_MILLI ) {\n\t\t\tthrow new UnsupportedOperationException( \"Spanner does not support no wait.\" );\n\t\t}\n\t}","sourceCodeStart":1266,"sourceCodeEnd":1302,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java#L1266-L1302","documentation":"SpannerDialect.getForUpdateSkipLockedString() throws UnsupportedOperationException because Cloud Spanner has no SKIP LOCKED locking mode — readers in a conflicting transaction get aborted rather than skipping rows. Hibernate requests this string when a pessimistic lock with the SKIP_LOCKED timeout is requested, and the dialect rejects it since it cannot be rendered. The alias variant behaves identically.","triggerScenarios":"session.find()/lock() or Query.setLockOptions() with LockOptions.setTimeOut(LockOptions.SKIP_LOCKED) (i.e. Timeouts.SKIP_LOCKED_MILLI = -2), or explicit SKIP LOCKED emulation, on a connection using SpannerDialect.","commonSituations":"Queue/worklist processing ('select next N unlocked rows') written against PostgreSQL SKIP LOCKED and pointed at Spanner; scheduler or job-claiming code that uses -2 timeout constants; Spring @Lock(PESSIMISTIC_WRITE) with LockModeType properties requesting skip-locked via jakarta.persistence.lock.timeout=-2.","solutions":["Replace SKIP LOCKED job claiming with a Spanner-appropriate pattern: UPDATE ... WHERE processed=false with a timestamp lease, or an interleaved claim table.","Use optimistic locking (@Version) and retry on StaleObjectStateException/OptimisticLockException instead of skipping rows.","Remove the -2 lock timeout setting (jakarta.persistence.lock.timeout=-2 in Spring properties) for Spanner profiles.","Catch UnsupportedOperationException at the claim boundary and fall back to plain FOR UPDATE within a short transaction."],"exampleFix":"// before (Spring Data JPA)\n@Lock(LockModeType.PESSIMISTIC_WRITE)\n@QueryHints(@QueryHint(name = \"jakarta.persistence.lock.timeout\", value = \"-2\")) // SKIP LOCKED\nList<Task> findNextTasks(...);\n\n// after — lease-claim update instead of skip locked\n@Modifying @Query(\"update Task t set t.leaseUntil = :until where t.id in :ids and t.leaseUntil < :now\")\nint leaseTasks(...);","handlingStrategy":"validation","validationCode":"boolean skipLocked = lockOptions.getTimeOut() == LockOptions.SKIP_LOCKED;\nif (skipLocked && (dialect instanceof SpannerDialect || dialect instanceof SpannerPostgreSQLDialect)) {\n  throw new IllegalArgumentException(\"SKIP LOCKED unsupported on Spanner; use a lease-based claim query\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  return query.list();\n} catch (UnsupportedOperationException e) {\n  if (e.getMessage().contains(\"skip locked\")) { /* fall back to lease-claim UPDATE */ }\n  throw e;\n}","preventionTips":["Do not port PostgreSQL SKIP LOCKED queues to Spanner; use conditional UPDATE leases.","Never set lock timeout -2 in Spanner JPA profiles.","Use @Version optimistic locking for contended entities."],"tags":["hibernate","cloud-spanner","locking","pessimistic-lock","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"}