hibernate/hibernate-orm · error · HibernateException

Connection lock-timeout does not accept skip-locked

Error message

Connection lock-timeout does not accept skip-locked

What it means

On MySQL, Hibernate applies pessimistic-lock timeouts by executing 'SET @@SESSION.innodb_lock_wait_timeout = N' (MySQLLockingSupport.ConnectionLockTimeoutStrategyImpl). That server variable is measured in whole seconds with a minimum of 1, so the magic value SKIP_LOCKED (-2 ms) cannot be mapped and MySQLLockingSupport throws this HibernateException rather than silently waiting. The strategy reports Level.SUPPORTED (not EXTENDED): skip-locked is never expressible through the connection setting, even though MySQL 8 supports SKIP LOCKED as a locking clause.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/MySQLLockingSupport.java:136

						// unit: seconds, allowed values: [1, 1073741824]
						final int seconds = resultSet.getInt( 1 );
						return seconds == foreverValue ? Timeouts.WAIT_FOREVER : Timeout.seconds( seconds );
					},
					connection,
					factory
			);
		}

		@Override
		public void setLockTimeout(Timeout timeout, Connection connection, SessionFactoryImplementor factory) {
			Helper.setLockTimeout(
					timeout,
					(t) -> {
						// see https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html#sysvar_innodb_lock_wait_timeout
						// unit: seconds, allowed values: [1, 1073741824]
						final int milliseconds = timeout.milliseconds();
						if ( milliseconds == SKIP_LOCKED_MILLI ) {
							throw new HibernateException( "Connection lock-timeout does not accept skip-locked" );
						}
						if ( milliseconds == NO_WAIT_MILLI ) {
							throw new HibernateException( "Connection lock-timeout does not accept no-wait" );
						}
						if ( milliseconds == WAIT_FOREVER_MILLI ) {
							return foreverValue;
						}
						return (int) Math.ceil( (double) milliseconds / 1000);
					},
					"SET @@SESSION.innodb_lock_wait_timeout = %s",
					connection,
					factory
			);
		}
	}
}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Use a real positive timeout in ms (it will be rounded up to seconds) or WAIT_FOREVER (-1) on MySQL
  2. Express skip-locked through the locking clause instead: rely on LockMode UPGRADE_SKIPLOCKED with a dialect/locking-clause strategy that emits 'FOR UPDATE SKIP LOCKED', or a native query
  3. Check getConnectionLockTimeoutStrategy().getSupportedLevel() before relying on magic timeout values - only EXTENDED (SQL Server/Sybase) supports no-wait, none support skip-locked
  4. Scope 'jakarta.persistence.lock.timeout' hints so they are not applied globally with value -2

Example fix

// before
Map<String, Object> hints = Map.of("jakarta.persistence.lock.timeout", -2); // SKIP_LOCKED -> throws
List<Order> orders = em.createQuery(...).setLockMode(LockModeType.PESSIMISTIC_WRITE)
        .setHints(hints).getResultList();

// after: short real wait (rounded up to 1s by innodb_lock_wait_timeout)
Map<String, Object> hints = Map.of("jakarta.persistence.lock.timeout", 1000);
List<Order> orders = em.createQuery(...).setLockMode(LockModeType.PESSIMISTIC_WRITE)
        .setHints(hints).getResultList();
Defensive patterns

Strategy: validation

Validate before calling

int millis = lockOptions.getTimeOut();
if (millis == Timeouts.SKIP_LOCKED_MILLI
        || (millis == Timeouts.NO_WAIT_MILLI
            && strategy.getSupportedLevel() != ConnectionLockTimeoutStrategy.Level.EXTENDED)) {
    lockOptions.setTimeOut(1000); // innodb_lock_wait_timeout works in whole seconds >= 1
}

Type guard

static boolean acceptsConnectionTimeout(ConnectionLockTimeoutStrategy s, int millis) {
    if (s.getSupportedLevel() == ConnectionLockTimeoutStrategy.Level.NONE) return false;
    if (millis == Timeouts.SKIP_LOCKED_MILLI) return false;
    return millis != Timeouts.NO_WAIT_MILLI
            || s.getSupportedLevel() == ConnectionLockTimeoutStrategy.Level.EXTENDED;
}

Try / catch

try {
    query.setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList();
} catch (HibernateException e) {
    if (e.getMessage() != null && e.getMessage().contains("lock-timeout does not accept")) {
        // magic timeout rejected by innodb_lock_wait_timeout path: adjust and retry
    } else { throw e; }
}

Prevention

When it happens

Trigger: session.buildLockRequest(LockOptions.UPGRADE_SKIPLOCKED).lock(entity) / lockOptions.setTimeOut(-2); em.find(id, PESSIMISTIC_WRITE, hints) or query.setLockMode(PESSIMISTIC_WRITE) with 'jakarta.persistence.lock.timeout' = -2; Timeouts.SKIP_LOCKED reaching setLockTimeout when the dialect uses connection-level timeouts. Also applies to MariaDB/TiDB variants reusing this strategy.

Common situations: Queue/poller code written for PostgreSQL (SELECT ... FOR UPDATE SKIP LOCKED) ported to MySQL with the same LockOptions; global lock timeout hint -2 in persistence.xml or Spring Data JPA repository hints; switching databases without adjusting lock timeout magic values; Hibernate 6 -> 7 upgrades where the timeout plumbing changed.

Understand the failure class

Related errors


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