{"record":{"id":"e44d9559edf44bcd","repo":"hibernate/hibernate-orm","slug":"spanner-does-not-support-no-wait","errorCode":null,"errorMessage":"Spanner does not support no wait.","messagePattern":"Spanner does not support no wait\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java","lineNumber":1274,"sourceCode":"\t@Override\n\tpublic String getWriteLockString(Timeout timeout) {\n\t\treturn getWriteLockString( timeout.milliseconds() );\n\t}\n\n\t@Override\n\tpublic String getReadLockString(int timeout) {\n\t\tvalidateSpannerLockTimeout( timeout );\n\t\treturn getForUpdateString();\n\t}\n\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) {","sourceCodeStart":1256,"sourceCodeEnd":1292,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java#L1256-L1292","documentation":"SpannerDialect.getForUpdateNowaitString() throws UnsupportedOperationException because Cloud Spanner's locking (via its FOR UPDATE support) has no NOWAIT mode — lock waits are governed by the transaction, not a SQL suffix. Hibernate calls this method when a pessimistic lock with a no-wait timeout is requested (LockOptions.NO_WAIT), so the request can never be satisfied on Spanner. Companion overloads for aliases and skip-locked throw likewise.","triggerScenarios":"Calling session.lock()/find(id, lockOptions) or setting Query.setLockOptions() with LockOptions.NO_WAIT (timeout = 0 meaning nowait, i.e. Timeouts.NO_WAIT_MILLI) or PESSIMISTIC_WRITE combined with `setTimeOut(LockOptions.NO_WAIT)` while connected through SpannerDialect; also `select ... for update nowait` emulation attempts.","commonSituations":"Reusing pessimistic-locking code written for PostgreSQL/Oracle (where NOWAIT avoids long waits) against a Spanner deployment; default timeout constants copied into LockOptions; frameworks (e.g. ShedLock-style routines) that request NOWAIT to detect contention.","solutions":["Remove the NOWAIT option: use plain LockOptions.NONE timeout (0 milliseconds semantics per Timeouts) so getReadLockString()/getForUpdateString() paths are used, which Spanner supports.","Catch UnsupportedOperationException around the locking call and retry with an ordinary FOR UPDATE, or accept wait semantics.","If fail-fast behavior is required, implement it at the application level (e.g. transaction timeout or a lock table) rather than the SQL suffix.","Verify the dialect in use — SpannerPostgreSQLDialect has the same restriction, so switching PG-dialect code to Spanner PG does not help."],"exampleFix":"// before\nLockOptions lo = new LockOptions(LockMode.PESSIMISTIC_WRITE);\nlo.setTimeOut(LockOptions.NO_WAIT); // -> getForUpdateNowaitString() throws\nsession.find(Order.class, id, lo);\n\n// after\nLockOptions lo = new LockOptions(LockMode.PESSIMISTIC_WRITE);\n// leave timeout unset/none — plain FOR UPDATE is supported\nsession.find(Order.class, id, lo);","handlingStrategy":"validation","validationCode":"boolean spanner = dialect instanceof SpannerDialect || dialect instanceof SpannerPostgreSQLDialect;\nif (spanner && (lockOptions.getTimeOut() == LockOptions.NO_WAIT || Timeouts.isNoWaitTimeout(lockOptions.getTimeOut()))) {\n  lockOptions.setTimeOut(LockOptions.NO_TIMEOUT); // degrade to plain FOR UPDATE\n}","typeGuard":null,"tryCatchPattern":"try {\n  return session.find(Order.class, id, lockOptions);\n} catch (UnsupportedOperationException e) {\n  if (e.getMessage().contains(\"no wait\")) {\n    LockOptions plain = new LockOptions(lockOptions.getLockMode());\n    return session.find(Order.class, id, plain);\n  }\n  throw e;\n}","preventionTips":["Never set NO_WAIT lock timeouts on Spanner datasources.","Keep lock configuration in per-dialect profiles.","Prefer optimistic locking unless strict read consistency is required."],"tags":["hibernate","cloud-spanner","locking","pessimistic-lock","for-update","unsupportedoperationexception"],"backgroundTag":"nowait-not-supported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}