hibernate/hibernate-orm · error · TransactionException

Transaction timeout expired

Error message

Transaction timeout expired

What it means

JdbcCoordinatorImpl.determineRemainingTransactionTimeOutPeriod() computes the milliseconds left before the configured transaction timeout instant; if the deadline has already passed it throws TransactionException('Transaction timeout expired'). The remaining-time value is used to apply statement query timeouts when preparing mutation and query statements (MutationStatementPreparerImpl and StatementPreparerImpl), so the exception typically surfaces during flush or statement preparation inside an over-deadline transaction.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java:323

	@Override
	public void setTransactionTimeOut(int seconds) {
		transactionTimeOutInstant = System.currentTimeMillis() + ( seconds * 1000L );
	}

	@Override
	public void flushBeforeTransactionCompletion() {
		getJdbcSessionOwner().flushBeforeTransactionCompletion();
	}

	@Override
	public int determineRemainingTransactionTimeOutPeriod() {
		if ( transactionTimeOutInstant < 0 ) {
			return -1;
		}
		final long millisecondsRemaining = transactionTimeOutInstant - System.currentTimeMillis();
		if ( millisecondsRemaining <= 0L ) {
			throw new TransactionException( "Transaction timeout expired" );
		}
		return Math.max( (int) (millisecondsRemaining / 1000), 1 );
	}

	@Override
	public void afterStatementExecution() {
		final var connectionReleaseMode = getLogicalConnection().resolvedConnectionReleaseMode();
		if ( TRACE_ENABLED ) {
			JDBC_LOGGER.statementExecutionComplete( connectionReleaseMode, hashCode() );
		}
		if ( connectionReleaseMode == AFTER_STATEMENT ) {
			if ( !releasesEnabled ) {
				JDBC_LOGGER.skippingAggressiveRelease( "manually disabled" );
			}
			else if ( hasRegisteredResources() ) {
				JDBC_LOGGER.skippingAggressiveRelease( "registered resources" );
			}
			else {

View on GitHub (pinned to fad1729dce)

Solutions

  1. Raise the transaction timeout for the long-running unit of work (e.g. @Transactional(timeout = 300))
  2. Optimize or split the work: chunk batches into multiple transactions, fix slow statements and lock contention
  3. Keep entity counts per transaction small so flush does not blow the deadline

Example fix

// before
@Transactional(timeout = 5)
public void importAll(List<Row> rows) { rows.forEach(repo::save); }

// after
@Transactional(timeout = 300)
public void importAll(List<Row> rows) { /* also consider chunking */ }
Defensive patterns

Strategy: retry

Validate before calling

// check remaining time in long loops and commit chunks early
long deadline = System.currentTimeMillis() + timeoutMillis;
for (Row r : rows) {
    if (System.currentTimeMillis() > deadline - margin) { commitChunkAndStartNewTx(); }
    save(r);
}

Try / catch

catch (TransactionException e) {
    if (e.getMessage().contains("Transaction timeout expired")) {
        // rollback and retry the unit of work with a fresh transaction
    }
}

Prevention

When it happens

Trigger: A transaction timeout was set (Spring @Transactional(timeout=...), JTA transaction timeout, or transaction manager timeout propagated to setTransactionTimeOutInstant) and elapsed before Hibernate finished preparing/executing statements — long flushes, slow SQL, lock waits, or oversized batch jobs.

Common situations: Batch imports inside one @Transactional; pessimistic-lock waits; a statement-level query timeout being derived from the remaining transaction time; heavy first-level-cache flushes at commit.

Understand the failure class

Related errors


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