{"record":{"id":"3dc154802d2eda22","repo":"hibernate/hibernate-orm","slug":"could-not-extract-row-count-from-callablestatement","errorCode":null,"errorMessage":"Could not extract row count from CallableStatement","messagePattern":"Could not extract row count from CallableStatement","errorType":"exception","errorClass":"GenericJDBCException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java","lineNumber":227,"sourceCode":"\t/**\n\t * Essentially identical to {@link RowCount} except that the row count\n\t * is obtained via an output parameter of a {@linkplain CallableStatement\n\t * stored procedure}.\n\t * <p>\n\t * Statement batching is disabled when {@code OutParameter} is used.\n\t *\n\t * @since 6.5\n\t */\n\tclass OutParameter implements Expectation {\n\t\t@Override\n\t\tpublic final void verifyOutcome(int rowCount, PreparedStatement statement, int batchPosition, String sql) {\n\t\t\tfinal int result;\n\t\t\ttry {\n\t\t\t\tresult = toCallableStatement( statement ).getInt( parameterIndex() );\n\t\t\t}\n\t\t\tcatch ( SQLException sqle ) {\n\t\t\t\tsqlExceptionHelper.logExceptions( sqle, \"Could not extract row count from CallableStatement\" );\n\t\t\t\tthrow new GenericJDBCException( \"Could not extract row count from CallableStatement\", sqle );\n\t\t\t}\n\t\t\tif ( batchPosition < 0 ) {\n\t\t\t\tcheckNonBatched( expectedRowCount(), result, sql );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcheckBatched( expectedRowCount(), result, batchPosition, sql );\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void validate(boolean callable) throws MappingException {\n\t\t\tif ( !callable ) {\n\t\t\t\tthrow new MappingException( \"Expectation.OutParameter operates exclusively on CallableStatements\" );\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic int getNumberOfParametersUsed() {","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java#L209-L245","documentation":"Expectation.OutParameter verifies write outcomes by reading the affected-row count from a stored-procedure OUT parameter via CallableStatement.getInt(parameterIndex()) (prepare() registers it as Types.NUMERIC). If that getInt call throws SQLException - parameter not registered, wrong index, or non-numeric value - Hibernate logs the SQLException and wraps it in GenericJDBCException with this message instead of verifying the row count.","triggerScenarios":"Custom SQL with @Expectation(type = ExpectationType.OUT_PARAM) (or a custom subclass of Expectation.OutParameter) where the callable statement has no NUMERIC out parameter at the expected index, e.g. the procedure does not return the row count, or the out parameter sits at a different position than parameterIndex() expects.","commonSituations":"Stored-procedure insert/update/delete where the count parameter was dropped or reordered during a proc refactor; mixing function-style {?=call...} with procedure-style {call...} signatures; drivers that require out parameters to be registered before input parameters.","solutions":["Make the row-count available as a NUMERIC out parameter at the index the expectation reads (typically the first parameter) and keep callable = true on the annotation","Align the procedure signature with the custom SQL: {? = call proc(?, ?)} if the count is a return value, or {call proc(?, ?, ?)} with the count as an explicit out parameter","Inspect the wrapped SQLException (cause) to see whether registration, index, or type conversion failed"],"exampleFix":"// before\n@SQLInsert(sql = \"{ call insert_person(?, ?, ?) }\", callable = true) // no out parameter for row count\n@Expectation(type = ExpectationType.OUT_PARAM)\nclass Person { }\n\n// after\n@SQLInsert(sql = \"{ ? = call insert_person(?, ?, ?) }\", callable = true) // returns affected row count\n@Expectation(type = ExpectationType.OUT_PARAM)\nclass Person { }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    session.flush();\n} catch (org.hibernate.exception.GenericJDBCException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"CallableStatement\")) {\n        // out parameter not registered / wrong index / non-numeric:\n        // check the stored procedure signature and parameterIndex of the expectation\n    }\n    throw e;\n}","preventionTips":["Keep the row-count out parameter as the first parameter of callable SQL and register expectations accordingly","Smoke-test every @Expectation(OUT_PARAM) entity against the real procedure in CI","Ensure the procedure returns the affected-row count as a NUMERIC-compatible type"],"tags":["stored-procedures","custom-sql","expectation","jdbc"],"backgroundTag":"stored-procedure-out-parameter","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}