hibernate/hibernate-orm · error · HibernateException

Expectation.OutParameter operates exclusively on CallableSta

Error message

Expectation.OutParameter operates exclusively on CallableStatements: ${statement.getClass()}

What it means

Expectations.toCallableStatement narrows the PreparedStatement handed to verifyOutcome/prepare to a CallableStatement. When the statement executed at flush time is not a CallableStatement (custom SQL not actually callable, or a programmatic call path), this HibernateException is thrown and names the statement's concrete class so you can see what the driver really produced.

Source

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

	}

	private static Expectation instantiate(Supplier<? extends Expectation> supplier, boolean callable) {
		if ( supplier == null ) {
			return callable
					? new Expectation.OutParameter()
					: new Expectation.RowCount();
		}
		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 + "]"
					);

View on GitHub (pinned to fad1729dce)

Solutions

  1. Ensure the custom SQL for the entity is declared callable = true and really is a {call ...} statement
  2. Confirm the driver returns a CallableStatement for the SQL (the exception prints the actual class)
  3. If the statement genuinely is not callable, use a COUNT or NONE expectation instead of OUT_PARAM

Example fix

// before
@SQLUpdate(sql = "update Person set name = ? where id = ?", callable = false)
@Expectation(type = ExpectationType.OUT_PARAM)
class Person { } // flush-time HibernateException: statement is not a CallableStatement

// after
@SQLUpdate(sql = "{ ? = call update_person(?, ?) }", callable = true)
@Expectation(type = ExpectationType.OUT_PARAM)
class Person { }
Defensive patterns

Strategy: try-catch

Try / catch

try {
    session.flush();
} catch (org.hibernate.HibernateException e) {
    if (e.getMessage() != null && e.getMessage().contains("exclusively on CallableStatements")) {
        // the executed statement was: e.getMessage().substring(e.getMessage().lastIndexOf(':') + 1)
        // fix: mark the custom SQL callable or use a COUNT/NONE expectation
    }
    throw e;
}

Prevention

When it happens

Trigger: An OutParameter expectation is active but the executed statement is a plain PreparedStatement - e.g. callable flag lost on a native/programmatic path, or the driver/connection pool not wrapping {call ...} SQL in a CallableStatement.

Common situations: Same misconfiguration as the validate() MappingException but surfacing at flush instead of bootstrap (programmatic expectations, statements built outside annotation validation); unusual driver behavior wrapping callable syntax.

Related errors


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