{"record":{"id":"1429d2eb85302d66","repo":"hibernate/hibernate-orm","slug":"lock-timeout-exceeded-attempting-to-lock-row-s-fo","errorCode":null,"errorMessage":"Lock timeout exceeded attempting to lock row(s) for %s","messagePattern":"Lock timeout exceeded attempting to lock row\\(s\\) for (.+?)","errorType":"exception","errorClass":"PessimisticEntityLockException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/SqlAstBasedLockingStrategy.java","lineNumber":193,"sourceCode":"\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\tSingleResultConsumer.instance()\n\t\t\t\t\t);\n\n\t\t\tif ( lockOptions.getLockScope() == PessimisticLockScope.EXTENDED ) {\n\t\t\t\tSqmMutationStrategyHelper.visitCollectionTables( entityToLock, (attribute) -> {\n\t\t\t\t\tfinal var collectionToLock = (PersistentCollection<?>) attribute.getValue( object );\n\t\t\t\t\tLockingHelper.lockCollectionTable(\n\t\t\t\t\t\t\tattribute,\n\t\t\t\t\t\t\tlockMode,\n\t\t\t\t\t\t\tlockOptions.getTimeout(),\n\t\t\t\t\t\t\tcollectionToLock,\n\t\t\t\t\t\t\tlockingExecutionContext\n\t\t\t\t\t);\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\t\tcatch (LockTimeoutException lockTimeout) {\n\t\t\tthrow new PessimisticEntityLockException(\n\t\t\t\t\tobject,\n\t\t\t\t\tString.format( Locale.ROOT, \"Lock timeout exceeded attempting to lock row(s) for %s\", object ),\n\t\t\t\t\tlockTimeout\n\t\t\t);\n\t\t}\n\t\tcatch (NoRowException noRow) {\n\t\t\tif ( !entityToLock.optimisticLockStyle().isNone() ) {\n\t\t\t\tfinal String entityName = entityToLock.getEntityName();\n\t\t\t\tfinal var statistics = session.getFactory().getStatistics();\n\t\t\t\tif ( statistics.isStatisticsEnabled() ) {\n\t\t\t\t\tstatistics.optimisticFailure( entityName );\n\t\t\t\t}\n\t\t\t\tthrow new StaleObjectStateException( entityName, id,\n\t\t\t\t\t\t\"No rows were returned from JDBC query for versioned entity\" );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthrow noRow;\n\t\t\t}","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/SqlAstBasedLockingStrategy.java#L175-L211","documentation":"SqlAstBasedLockingStrategy locks an entity by issuing a locking SELECT built from the SQL AST (used when the lock is applied via a separate/follow-up select, e.g. session.lock(entity, LockMode.PESSIMISTIC_WRITE)). When the database reports that the lock wait was exceeded (org.hibernate.exception.LockTimeoutException from SQLState 55P03 on PostgreSQL, 1205 on MySQL, 1222 on SQL Server, ORA-30006 on Oracle), the strategy rethrows it as PessimisticEntityLockException with this message naming the locked object. It means the row(s) were still held by another transaction when the configured wait elapsed - a genuine runtime contention signal, not a configuration defect.","triggerScenarios":"session.lock(entity, LockMode.PESSIMISTIC_WRITE) / session.buildLockRequest(...).lock(...) on a detached or loaded entity; em.find(id, LockModeType.PESSIMISTIC_WRITE) with follow-on locking; a lock timeout (jakarta.persistence.lock.timeout in ms) smaller than the time another transaction holds the row; two concurrent transactions locking the same rows in a batch.","commonSituations":"Long-running transactions (user think-time inside @Transactional, external API calls while holding locks) blocking readers that lock; batch jobs contending with OLTP traffic; timeouts configured too aggressively; missing indexes causing larger lock footprints; deadlocks resolved by lock-wait expiry.","solutions":["Catch PessimisticLockException / PessimisticEntityLockException and retry the unit of work with backoff, or surface 'busy' to the caller","Shorten the transaction holding the lock: commit before I/O, split large transactions, lock as late as possible","Increase the lock timeout (hint 'jakarta.persistence.lock.timeout' in ms) if the contention is transient and acceptable","If failing fast is preferred, use a very short timeout deliberately and standardize the retry path"],"exampleFix":"// before\nem.find(Order.class, id, LockModeType.PESSIMISTIC_WRITE);\n\n// after\nOrder order = null;\nfor (int attempt = 0; attempt < 3 && order == null; attempt++) {\n    try {\n        order = em.find(Order.class, id, LockModeType.PESSIMISTIC_WRITE);\n    } catch (PessimisticLockException e) {\n        sleepBackoff(attempt); // e.g. 50ms * 2^attempt\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {\n    try {\n        return em.find(Order.class, id, LockModeType.PESSIMISTIC_WRITE);\n    } catch (PessimisticLockException e) { // wraps PessimisticEntityLockException\n        if (attempt == MAX_RETRIES - 1) throw e;\n        sleep(Duration.ofMillis(50L << attempt)); // exponential backoff\n    }\n}","preventionTips":["Keep transactions short: commit before external calls, lock rows as late as possible","Lock rows in a consistent order across transactions to reduce contention","Index the columns used to locate locked rows to keep lock footprints small","Size 'jakarta.persistence.lock.timeout' above your p99 transaction duration where contention is expected"],"tags":["pessimistic-locking","lock-timeout","concurrency","optimistic-retry","hibernate"],"backgroundTag":"pessimistic-lock-timeout-exceeded","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}