hibernate/hibernate-orm · error · PessimisticEntityLockException

Could not obtain pessimistic lock

Error message

Could not obtain pessimistic lock

What it means

AbstractPessimisticUpdateLockingStrategy.lock() executes the locking UPDATE through JDBC; any SQLException surfaced as a JDBCException is wrapped in PessimisticEntityLockException with message 'Could not obtain pessimistic lock', carrying the original exception as cause. It signals the database refused or failed the lock acquisition - lock wait timeout, deadlock victim, permission or connection failure - rather than a mapping problem. The original SQLState/error code stays available via getCause().

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractPessimisticUpdateLockingStrategy.java:58

		this.lockMode = lockMode;
		if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) {
			throw new HibernateException( "Lock mode " + lockMode
						+ " not valid for locking via 'update' statement" );
		}
		if ( !lockable.isVersioned() ) {
			throw new HibernateException( "Entity '" + lockable.getEntityName()
						+ "' has no version and may not be locked via 'update' statement" );
		}
		this.sql = generateLockString();
	}

	@Override
	public void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) {
		try {
			doLock( id, version, session );
		}
		catch (JDBCException e) {
			throw new PessimisticEntityLockException( object, "Could not obtain pessimistic lock", e );
		}
	}

	void doLock(Object id, Object version, SharedSessionContractImplementor session) {
		try {
			final var factory = session.getFactory();
			final var jdbcCoordinator = session.getJdbcCoordinator();
			final var preparedStatement = jdbcCoordinator.getStatementPreparer().prepareStatement( sql );
			try {
				final var versionType = lockable.getVersionType();
				final var identifierType = lockable.getIdentifierType();

				versionType.nullSafeSet( preparedStatement, version, 1, session );
				int offset = 2;

				identifierType.nullSafeSet( preparedStatement, id, offset, session );
				offset += identifierType.getColumnSpan( factory.getRuntimeMetamodels() );

View on GitHub (pinned to fad1729dce)

Solutions

  1. Inspect getCause() (and its SQLState/error code) to classify: retry deadlocks and timeouts with backoff, fix permissions or connection issues instead of retrying
  2. Shorten the transaction that holds conflicting locks, and always lock rows in a consistent order to avoid deadlocks
  3. Set a lock timeout hint (e.g. LockOptions.setTimeout(Timeout.seconds(n)) or jakarta.persistence.lock.timeout) so waiting fails fast instead of the driver default
  4. If contention is structural, switch to optimistic locking for that use case

Example fix

// before
session.lock(person, LockMode.PESSIMISTIC_WRITE);

// after
try {
    session.lock(person, LockMode.PESSIMISTIC_WRITE);
}
catch (PessimisticEntityLockException e) {
    if (isDeadlockOrTimeout(e.getCause())) {
        // back off and retry with jitter, then reload the entity
    }
    else {
        throw e;
    }
}
Defensive patterns

Strategy: retry

Try / catch

try {
    session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)
            .setTimeout(Timeout.seconds(5))).lock(person);
}
catch (PessimisticEntityLockException e) { // org.hibernate.dialect.lock
    Throwable cause = e.getCause();
    if (cause instanceof PessimisticLockException
            || (cause instanceof JDBCException jdbc && isDeadlockOrTimeout(jdbc.getSQLException()))) {
        backoffAndRetry(); // exponential backoff + jitter, reload entity after retry
    }
    else {
        throw e;
    }
}

Prevention

When it happens

Trigger: session.lock(entity, LockMode.PESSIMISTIC_WRITE) or buildLockRequest(...).lock() while another transaction holds a conflicting row lock past innodb_lock_wait_timeout / lock_timeout; being chosen deadlock victim by the database; the locking UPDATE violating permissions or the connection dying mid-statement. All surface as JDBCException inside doLock and are rethrown as PessimisticEntityLockException.

Common situations: Long-running transactions that lock popular rows (order processing, scheduler jobs); lock ordering inconsistencies across services causing deadlocks; tight retry loops that immediately re-acquire locks; connection pool exhaustion causing failures during the lock statement.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/fe13d9ca853971bf. Report an issue: GitHub.