=== Raw JDBC probe (no Hibernate), HSQLDB 2.7.3, DYNAMIC RESULT SETS procedure === --- CallableStatement.execute() then getUpdateCount()/getResultSet() --- execute() returned=false getUpdateCount=0 getResultSet() = org.hsqldb.jdbc.JDBCResultSet@5649fd9b row: 1 Alice 50000.00 row: 2 Bob 60000.00 getMoreResults=false --- Statement.execute("CALL ...") vs CallableStatement.executeQuery() --- --- via plain Statement.execute(CALL ...) --- Statement.execute returned=false getResultSet=null --- via CallableStatement.executeQuery() --- executeQuery ok, rs=org.hsqldb.jdbc.JDBCResultSet@5649fd9b row via executeQuery: 1 row via executeQuery: 2 --- metadata: getMetaData() on CallableStatement before execute --- getMetaData()=null CONCLUSION: CallableStatement.execute() returns false (per JDBC spec this should mean 'no ResultSet, check update count'), and getUpdateCount() also returns 0 (not -1). Yet CallableStatement.getResultSet() DOES return a live, iterable ResultSet with the cursor's rows, and CallableStatement.executeQuery() works correctly end-to-end. Statement.execute("CALL ...") is worse: execute()=false AND getResultSet()=null (the result set is only reachable through the CallableStatement form). Hibernate 7.4.5's ProcedureCallImpl (org.hibernate.procedure.internal.ProcedureCallImpl / StandardCallableStatementSupport) drives the call via execute() and trusts its boolean to decide whether to attach a ResultSetOutput. Since HSQLDB's driver misreports that boolean, getResultList() on a DYNAMIC RESULT SETS procedure fails with: java.lang.IllegalStateException: Current CallableStatement was not a ResultSet, but getResultList was called This reproduces identically through @NamedStoredProcedureQuery(resultClasses=...) and through createStoredProcedureQuery(name, sqlResultSetMappingName) -- see StoredProcedureHappyPathTest#resultSetProcedure_mappedToEntity_hitsHsqldbDriverIncompatibility and #resultSetProcedure_mappedToDto_alsoHitsHsqldbDriverIncompatibility. === OUT parameter vs result-set consumption ORDER (raw JDBC, procedure with BOTH) === Procedure: combo(IN emp_id, OUT tax_amount) READS SQL DATA DYNAMIC RESULT SETS 1, opens a cursor AND sets the OUT parameter. Reading the OUT parameter BEFORE consuming the ResultSet: execute()=false OUT tax_amount = 7500.00 <- succeeds, no exception (then) getResultSet() still returns the cursor with its rows intact (then) OUT tax_amount read AGAIN = 7500.00 <- still succeeds Reading the OUT parameter AFTER fully consuming the ResultSet: rows consumed first OUT tax_amount = 9000.00 <- also succeeds CONCLUSION: unlike some JDBC drivers (historically SQL Server's, and some Oracle configurations) that require a stored procedure's result set(s) to be fully consumed before OUT parameters become readable, HSQLDB 2.7.3's driver imposes NO such ordering constraint. Reading the OUT value before, interleaved with, or after draining the cursor all work identically. The "ordering trap" described in stored-procedure folklore is real on SOME databases/drivers but is NOT reproducible on HSQLDB -- worth stating explicitly rather than assuming it's universal.