hibernate/hibernate-orm · error · IllegalStateException

Transaction not successfully started

Error message

Transaction not successfully started

What it means

TransactionImpl.commit() requires the transaction to be active (or at least marked rollback-only) so commit has something to drive. If the transaction was never begun — or already completed and not marked for rollback — the inactive state means there is nothing to commit, and Hibernate throws IllegalStateException('Transaction not successfully started') instead of performing a silent no-op.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java:88

			if ( jpaCompliance ) {
				throw new IllegalStateException( "Transaction already active (in JPA compliant mode)" );
			}
			else if ( !transactionCoordinator.getTransactionCoordinatorBuilder().isJta() ) {
				throw new IllegalStateException( "Resource-local transaction already active" );
			}
		}
		else {
			CORE_LOGGER.beginningTransaction();
			transactionDriverControl.begin();
		}
	}

	@Override
	public void commit() {
		// allow MARKED_ROLLBACK to propagate through to transactionDriverControl
		if ( !isActive() ) {
			// we have a transaction that is inactive and has not been marked for rollback only
			throw new IllegalStateException( "Transaction not successfully started" );
		}
		else {
			CORE_LOGGER.committingTransaction();
			try {
				internalGetTransactionDriverControl().commit();
			}
			catch (RuntimeException e) {
				throw session.getExceptionConverter().convertCommitException( e );
			}
		}
	}

	@Nonnull
	public TransactionDriver internalGetTransactionDriverControl() {
		// NOTE here to help be a more descriptive NullPointerException
		if ( transactionDriverControl == null ) {
			throw new IllegalStateException( "Transaction was not properly begun/started" );
		}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Guard the commit: if (tx.isActive()) { tx.commit(); } — or only commit in the path where you began it
  2. Structure try/finally so begin() happens before the try, and commit/rollback are mutually exclusive in finally
  3. On business exceptions call rollback() (or markRollbackOnly) instead of letting a commit be reached

Example fix

// before
Transaction tx = em.getTransaction();
try {
    doWork();
} finally {
    tx.commit(); // never begun / already done -> IllegalStateException
}

// after
Transaction tx = em.getTransaction();
tx.begin();
try {
    doWork();
    tx.commit();
} catch (RuntimeException e) {
    if (tx.isActive()) {
        tx.rollback();
    }
    throw e;
}
Defensive patterns

Strategy: validation

Validate before calling

if (tx.isActive()) {
    tx.commit();
} else {
    log.warn("Skipping commit: transaction was never begun or already completed");
}

Prevention

When it happens

Trigger: tx.commit() without any tx.begin() (e.g. a Transaction reference obtained via em.getTransaction() but never begun); commit() after an earlier commit()/rollback() already completed the transaction; commit attempted when the driver control was never started.

Common situations: finally blocks that commit unconditionally even when begin() failed or was skipped; retry logic that commits twice; code paths where an exception happens before begin() but the catch/finally still commits; tests that forget begin().

Related errors


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