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
- Temporarily remove hibernate.jdbc.batch_size (run unbatched) and reproduce - the real SQLException for the offending statement then surfaces directly
- 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
- 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
- Validate data against schema constraints (lengths, uniques, FKs) before flushing large batches
- Keep a switch to disable batching so failures reproduce with full SQL errors
- Chunk large batches so a failure localizes to a small set of rows
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
- Batch update returned unexpected row count from update {} (e
- JDBC driver does not support named parameters for setArray.
- PostgreSQL only supports REF_CURSOR parameters as the first
- PostgreSQL only supports accessing REF_CURSOR parameters by
- cannot recreate collection while filter is enabled: " + coll
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/b4f57492324aa53e.
Report an issue: GitHub.