{"record":{"id":"f8409d664702bc71","repo":"hibernate/hibernate-orm","slug":"connection-lock-timeout-does-not-accept-skip-locke-f8409d","errorCode":null,"errorMessage":"Connection lock-timeout does not accept skip-locked","messagePattern":"Connection lock-timeout does not accept skip-locked","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/CockroachLockingSupport.java","lineNumber":107,"sourceCode":"\t\t\t\t\t\tdefault -> Timeout.milliseconds( millis );\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tconnection,\n\t\t\t\tfactory\n\t\t);\n\t}\n\n\t@Override\n\tpublic void setLockTimeout(\n\t\t\tTimeout timeout,\n\t\t\tConnection connection,\n\t\t\tSessionFactoryImplementor factory) {\n\t\tHelper.setLockTimeout(\n\t\t\t\ttimeout,\n\t\t\t\t(t) -> {\n\t\t\t\t\tfinal int milliseconds = timeout.milliseconds();\n\t\t\t\t\tif ( milliseconds == SKIP_LOCKED_MILLI ) {\n\t\t\t\t\t\tthrow new HibernateException( \"Connection lock-timeout does not accept skip-locked\" );\n\t\t\t\t\t}\n\t\t\t\t\tif ( milliseconds == NO_WAIT_MILLI ) {\n\t\t\t\t\t\tthrow new HibernateException( \"Connection lock-timeout does not accept no-wait\" );\n\t\t\t\t\t}\n\t\t\t\t\treturn milliseconds == WAIT_FOREVER_MILLI ? 0 : milliseconds;\n\t\t\t\t},\n\t\t\t\t\"set lock_timeout = %s\",\n\t\t\t\tconnection,\n\t\t\t\tfactory\n\t\t);\n\t}\n\n}\n","sourceCodeStart":89,"sourceCodeEnd":121,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/CockroachLockingSupport.java#L89-L121","documentation":"Hibernate 7's CockroachDB support applies pessimistic-lock timeouts at the JDBC connection level: before the locking statement it executes 'set lock_timeout = N' (via Helper.setLockTimeout). The connection setting can only represent a real duration (or 0 = wait forever), so when the requested timeout is the magic value SKIP_LOCKED (-2 ms, Timeouts.SKIP_LOCKED_MILLI) it throws this HibernateException instead of silently applying wrong semantics. CockroachLockingSupport reports Level.SUPPORTED (not EXTENDED), meaning neither skip-locked nor no-wait is expressible through this path.","triggerScenarios":"session.buildLockRequest(LockOptions.UPGRADE_SKIPLOCKED).lock(entity) or LockOptions.setTimeOut(-2); a query with setLockMode(LockModeType.PESSIMISTIC_WRITE) plus hint 'jakarta.persistence.lock.timeout' (or legacy 'javax.persistence.lock.timeout') set to -2; any Timeouts.SKIP_LOCKED Timeout that reaches CockroachLockingSupport.setLockTimeout when LockTimeoutType.CONNECTION was selected (JdbcSelectWithActions registers LockTimeoutHandler).","commonSituations":"Porting PostgreSQL pessimistic-locking code to CockroachDB; a global <property name=\"jakarta.persistence.lock.timeout\" value=\"-2\"/> in persistence.xml applied to every lock; code that works on SQL Server (Level.EXTENDED) reused on CockroachDB; upgrading from Hibernate 6 where these timeout values were handled by different machinery.","solutions":["Use a real positive timeout (e.g. lockOptions.setTimeOut(1000) or Timeouts.ONE_SECOND) or WAIT_FOREVER (-1) on CockroachDB","If you need skip-locked semantics, request them at the locking-clause level (FOR UPDATE SKIP LOCKED) rather than through the connection lock_timeout, or use a native query","Guard up front: dialect.getLockingSupport().getConnectionLockTimeoutStrategy().getSupportedLevel() - skip-locked is accepted by no level, no-wait only by Level.EXTENDED","Remove or scope down global lock timeout hints set to -2/0 in persistence.xml or Spring Data JPA hints"],"exampleFix":"// before\nMap<String, Object> hints = Map.of(\"jakarta.persistence.lock.timeout\", -2); // SKIP_LOCKED\nOrder o = em.find(Order.class, id, LockModeType.PESSIMISTIC_WRITE, hints);\n\n// after: a real wait; skip-locked is not expressible via CRDB connection lock_timeout\nMap<String, Object> hints = Map.of(\"jakarta.persistence.lock.timeout\", 1000);\nOrder o = em.find(Order.class, id, LockModeType.PESSIMISTIC_WRITE, hints);","handlingStrategy":"validation","validationCode":"ConnectionLockTimeoutStrategy s = sessionFactory.getJdbcServices().getDialect()\n        .getLockingSupport().getConnectionLockTimeoutStrategy();\nint millis = lockOptions.getTimeOut();\nif (millis == Timeouts.SKIP_LOCKED_MILLI) {\n    lockOptions.setTimeOut(50); // skip-locked is never expressible via connection lock_timeout\n}","typeGuard":"static boolean acceptsConnectionTimeout(ConnectionLockTimeoutStrategy s, int millis) {\n    if (s.getSupportedLevel() == ConnectionLockTimeoutStrategy.Level.NONE) return false;\n    if (millis == Timeouts.SKIP_LOCKED_MILLI) return false;\n    return millis != Timeouts.NO_WAIT_MILLI\n            || s.getSupportedLevel() == ConnectionLockTimeoutStrategy.Level.EXTENDED;\n}","tryCatchPattern":"try {\n    session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE).timeout(1000)).lock(entity);\n} catch (HibernateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"lock-timeout does not accept\")) {\n        // requested magic timeout not supported by this dialect: degrade to a real timeout\n    } else { throw e; }\n}","preventionTips":["Never set 'jakarta.persistence.lock.timeout' (or LockOptions) to -2 (skip-locked) when the dialect applies timeouts on the connection","Keep lock timeout configuration per-dialect, not global in persistence.xml","Exercise pessimistic locking paths in integration tests on every supported database","Check getConnectionLockTimeoutStrategy().getSupportedLevel() at startup and log it"],"tags":["cockroachdb","pessimistic-locking","lock-timeout","skip-locked","hibernate"],"backgroundTag":"pessimistic-lock-timeout-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}