hibernate/hibernate-orm · error · MappingException
Expectation.OutParameter operates exclusively on CallableSta
Error message
Expectation.OutParameter operates exclusively on CallableStatements
What it means
At bootstrap Hibernate validates every custom-SQL expectation against its declaration. Expectation.OutParameter.validate throws this MappingException when an OUT-parameter expectation is attached to custom SQL that is not marked callable - the OutParameter verification path only works against CallableStatements, so a plain PreparedStatement declaration is a mapping error, not a runtime one.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java:240
try {
result = toCallableStatement( statement ).getInt( parameterIndex() );
}
catch ( SQLException sqle ) {
sqlExceptionHelper.logExceptions( sqle, "Could not extract row count from CallableStatement" );
throw new GenericJDBCException( "Could not extract row count from CallableStatement", sqle );
}
if ( batchPosition < 0 ) {
checkNonBatched( expectedRowCount(), result, sql );
}
else {
checkBatched( expectedRowCount(), result, batchPosition, sql );
}
}
@Override
public void validate(boolean callable) throws MappingException {
if ( !callable ) {
throw new MappingException( "Expectation.OutParameter operates exclusively on CallableStatements" );
}
}
@Override
public int getNumberOfParametersUsed() {
return 1;
}
@Override
public int prepare(PreparedStatement statement) throws SQLException, HibernateException {
toCallableStatement( statement ).registerOutParameter( parameterIndex(), Types.NUMERIC );
return 1;
}
@Override
public boolean canBeBatched() {
return false;
}View on GitHub (pinned to fad1729dce)
Solutions
- Set callable = true on the custom SQL annotation (@SQLInsert/@SQLUpdate/@SQLDelete)
- Or, if the SQL really is a plain statement, switch the expectation to ExpectationType.COUNT (default row-count check) or NONE
- Re-run bootstrap: this fails fast at mapping validation, so the fix is purely a mapping/annotation change
Example fix
// before
@SQLDelete(sql = "delete from Person where id = ?", callable = false)
@Expectation(type = ExpectationType.OUT_PARAM)
class Person { }
// after
@SQLDelete(sql = "{ ? = call delete_person(?) }", callable = true)
@Expectation(type = ExpectationType.OUT_PARAM)
class Person { } Defensive patterns
Strategy: validation
Validate before calling
// Fail fast on inconsistent mapping before first use (e.g. in a startup check)
for (Class<?> entity : scannedEntities) {
for (var a : entity.getAnnotations()) {
boolean callable = switch (a.annotationType().getSimpleName()) {
case "SQLInsert" -> ((org.hibernate.annotations.SQLInsert) a).callable();
case "SQLUpdate" -> ((org.hibernate.annotations.SQLUpdate) a).callable();
case "SQLDelete" -> ((org.hibernate.annotations.SQLDelete) a).callable();
default -> true;
};
org.hibernate.annotations.Expectation exp = entity.getAnnotation(org.hibernate.annotations.Expectation.class);
if (exp != null && exp.type() == org.hibernate.annotations.ExpectationType.OUT_PARAM && !callable) {
throw new IllegalStateException(entity + " uses OUT_PARAM expectation on non-callable SQL");
}
}
} Prevention
- Pair ExpectationType.OUT_PARAM only with callable = true custom SQL
- Review expectation annotations whenever custom SQL changes from plain to stored procedures
- Let bootstrap validation fail early in CI rather than at first flush
When it happens
Trigger: Combining an out-parameter expectation with non-callable custom SQL, e.g. @SQLDelete(sql = "delete from Person where id = ?", callable = false) together with @Expectation(type = ExpectationType.OUT_PARAM).
Common situations: Switching an entity's custom SQL from inline statements to stored procedures and forgetting the callable flag; copy-pasting @Expectation annotations between entities with different SQL styles.
Related errors
- Could not extract row count from CallableStatement
- Expectation.OutParameter operates exclusively on CallableSta
- write expression must contain exactly one value placeholder
- Unable to locate parameter `%s.%s` for %s - %s : %s
- Could not create connection
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/ed72f759f75b05a9.
Report an issue: GitHub.