{"record":{"id":"f56ff8e5dbb21ebd","repo":"hibernate/hibernate-orm","slug":"spanner-does-not-support-lock-timeout","errorCode":null,"errorMessage":"Spanner does not support lock timeout.","messagePattern":"Spanner does not support lock timeout\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java","lineNumber":1294,"sourceCode":"\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}\n\n\t@Override\n\tpublic boolean supportsOffsetInSubquery() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic char openQuote() {\n\t\treturn '`';\n\t}","sourceCodeStart":1276,"sourceCodeEnd":1312,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java#L1276-L1312","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the positive lock timeout property/hint — leave it unset (or 0/no-timeout semantics) so FOR UPDATE renders without modifiers.","Bound waits at the transaction level instead: set a Spanner transaction timeout (e.g. spring.transaction.default-timeout or the driver's transaction options).","Catch UnsupportedOperationException around locking calls as a safety net and retry without the timeout.","Prefer optimistic @Version locking when wait bounding was the only goal."],"exampleFix":"# before\nspring.jpa.properties.jakarta.persistence.lock.timeout=5000\n\n# after (remove; Spanner FOR UPDATE supports no timeout modifier)\n# spring.jpa.properties.jakarta.persistence.lock.timeout=  <- unset","handlingStrategy":"validation","validationCode":"int t = lockOptions.getTimeOut();\nif (Timeouts.isRealTimeout(t) && (dialect instanceof SpannerDialect || dialect instanceof SpannerPostgreSQLDialect)) {\n  lockOptions.setTimeOut(LockOptions.NO_TIMEOUT);\n}","typeGuard":null,"tryCatchPattern":"try { return session.find(Order.class, id, lockOptions); }\ncatch (UnsupportedOperationException e) {\n  if (e.getMessage().contains(\"lock timeout\")) { /* retry with no timeout; bound via transaction timeout */ }\n  throw e;\n}","preventionTips":["Do not set positive lock.timeout values on Spanner.","Bound waits with transaction/driver timeouts instead.","Keep JPA hint files per-datasource."],"tags":["hibernate","cloud-spanner","locking","lock-timeout","pessimistic-lock","unsupportedoperationexception"],"backgroundTag":"lock-timeout-not-supported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}