hibernate/hibernate-orm · error · BatchFailedException

Batch update failed: ${batchPosition}

Error message

Batch update failed: ${batchPosition}

What it means

After executing a JDBC batch, Expectations.checkBatched switches on each entry's row count. Statement.EXECUTE_FAILED (-3) means one batch entry failed and the driver continued; Hibernate then throws BatchFailedException naming only the failing position (batchPosition). The original SQL error is not carried by this exception - the position tells you which statement in the flush batch died.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/jdbc/Expectations.java:68

		else {
			return supplier.get();
		}
	}

	static CallableStatement toCallableStatement(PreparedStatement statement) {
		if ( statement instanceof CallableStatement callableStatement ) {
			return callableStatement;
		}
		else {
			throw new HibernateException( "Expectation.OutParameter operates exclusively on CallableStatements: "
					+ statement.getClass() );
		}
	}

	static void checkBatched(int expectedRowCount, int rowCount, int batchPosition, String sql) {
		switch (rowCount) {
			case EXECUTE_FAILED:
				throw new BatchFailedException( "Batch update failed: " + batchPosition );
			case SUCCESS_NO_INFO:
				BATCH_MESSAGE_LOGGER.batchSuccessUnknown( batchPosition );
				break;
			default:
				if ( expectedRowCount > rowCount ) {
					throw new StaleStateException(
							"Batch update returned unexpected row count from update " + batchPosition
									+ actualVsExpected( expectedRowCount, rowCount )
									+ " [" + sql + "]"
					);
				}
				else if ( expectedRowCount < rowCount ) {
					throw new BatchedTooManyRowsAffectedException(
							"Batch update returned unexpected row count from update " + batchPosition
									+ actualVsExpected( expectedRowCount, rowCount ),
							expectedRowCount, rowCount, batchPosition );
				}
		}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Temporarily remove hibernate.jdbc.batch_size (run unbatched) and reproduce - the real SQLException for the offending statement then surfaces directly
  2. Map batchPosition to the data: it identifies the Nth statement of the flushed batch, so inspect the corresponding entity/row for constraint or type errors
  3. Fix the data or schema (unique key, FK, column length) and restore batching

Example fix

// before
props.put("hibernate.jdbc.batch_size", "50"); // failure reported as position only

// after (debugging)
props.remove("hibernate.jdbc.batch_size"); // unbatched run surfaces the real SQLException
// then fix the offending row and re-enable batching
Defensive patterns

Strategy: try-catch

Try / catch

try {
    session.flush();
} catch (org.hibernate.jdbc.BatchFailedException e) {
    // e.getMessage() = "Batch update failed: <position>" - index into this batch's statements
    // re-run the same data with hibernate.jdbc.batch_size removed to surface the root SQLException
    throw new IllegalStateException("Batch entry " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: hibernate.jdbc.batch_size > 0 with batched inserts/updates/deletes where one row violates a constraint (unique, FK, not-null, data too long) or times out, and the JDBC driver reports EXECUTE_FAILED for that entry while continuing the batch.

Common situations: Bulk-loading mixed-valid data through flush; one bad row among hundreds; driver-specific batch behavior where the underlying cause is swallowed and only the position survives.

Related errors


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