{"record":{"id":"3dedd613daca272a","repo":"hibernate/hibernate-orm","slug":"cannot-mix-named-parameters-and-ref-cursor-paramet","errorCode":null,"errorMessage":"Cannot mix named parameters and REF_CURSOR parameter on PostgreSQL","messagePattern":"Cannot mix named parameters and REF_CURSOR parameter on PostgreSQL","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgreSQLCallableStatementSupport.java","lineNumber":90,"sourceCode":"\t\tif ( functionReturn == null && parameterMetadata.hasNamedParameters() ) {\n\t\t\t// That's just a rough estimate. I guess most params will have fewer than 8 chars on average\n\t\t\tparamStringSizeEstimate = registrations.size() * 10;\n\t\t}\n\t\telse {\n\t\t\t// For every param rendered as '?' we have a comma, hence the estimate\n\t\t\tparamStringSizeEstimate = registrations.size() * 2;\n\t\t}\n\t\tfinal JdbcCallImpl.Builder builder = new JdbcCallImpl.Builder();\n\n\t\tfinal int jdbcParameterOffset;\n\t\tfinal int startIndex;\n\t\tfinal CallMode callMode;\n\t\tif ( functionReturn != null ) {\n\t\t\tif ( functionReturn.getJdbcTypeCode() == SqlTypes.REF_CURSOR ) {\n\t\t\t\tif ( firstParamIsRefCursor ) {\n\t\t\t\t\t// validate that the parameter strategy is positional (cannot mix, and REF_CURSOR is inherently positional)\n\t\t\t\t\tif ( parameterMetadata.hasNamedParameters() ) {\n\t\t\t\t\t\tthrow new HibernateException( \"Cannot mix named parameters and REF_CURSOR parameter on PostgreSQL\" );\n\t\t\t\t\t}\n\t\t\t\t\tcallMode = CallMode.CALL_RETURN;\n\t\t\t\t\tstartIndex = 1;\n\t\t\t\t\tjdbcParameterOffset = 1;\n\t\t\t\t\tbuilder.addParameterRegistration( registrations.get( 0 ).toJdbcParameterRegistration( 1, procedureCall ) );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcallMode = CallMode.TABLE_FUNCTION;\n\t\t\t\t\tstartIndex = 0;\n\t\t\t\t\tjdbcParameterOffset = 1;\n\t\t\t\t\t// Old style\n//\t\t\t\t\tcallMode = CallMode.CALL_RETURN;\n//\t\t\t\t\tstartIndex = 0;\n//\t\t\t\t\tjdbcParameterOffset = 2;\n//\t\t\t\t\tbuilder.setFunctionReturn( functionReturn.toJdbcFunctionReturn( procedureCall.getSession() ) );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgreSQLCallableStatementSupport.java#L72-L108","documentation":"PostgreSQLCallableStatementSupport renders the JDBC call string for PostgreSQL. When the function return is a REF_CURSOR and the first parameter is the refcursor placeholder ({? = call f(?)}) the syntax is inherently positional; PostgreSQL cannot mix 'param => ?' named notation with a refcursor placeholder. Hibernate checks parameterMetadata.hasNamedParameters() at that point and fails fast with HibernateException instead of emitting an invalid call string.","triggerScenarios":"Registering a REF_CURSOR function return plus any named parameter on PostgreSQL, e.g. proc.registerStoredProcedureParameter(\"in_user\", Long.class, ParameterMode.IN) together with a refcursor return; using ProcedureCall#setParameter by name in the same call.","commonSituations":"Porting Oracle procedures that combine named parameters with cursor returns; naming parameters for readability while keeping a refcursor API; shared DAO code that always binds by name.","solutions":["Register every parameter positionally (no names) whenever REF_CURSOR is involved on PostgreSQL.","Prefer a table function returning SETOF rows and consume results with getResultList() — no REF_CURSOR registration needed.","If names are required for readability, keep a positional map in your code and bind by position."],"exampleFix":"// before (named + refcursor return -> throws)\nproc.registerStoredProcedureParameter(\"in_user\", Long.class, ParameterMode.IN);\nproc.registerParameter(0, void.class, ParameterMode.REF_CURSOR); // function return\n\n// after (all positional)\nproc.registerStoredProcedureParameter(0, void.class, ParameterMode.REF_CURSOR);\nproc.registerStoredProcedureParameter(1, Long.class, ParameterMode.IN);","handlingStrategy":"validation","validationCode":"// forbid named parameters on a PostgreSQL call that uses REF_CURSOR\nboolean hasRefCursor = proc.getParameters().stream()\n        .anyMatch( p -> p.getMode() == ParameterMode.REF_CURSOR );\nboolean hasNamed = proc.getParameters().stream()\n        .anyMatch( p -> p.getName() != null );\nif ( hasRefCursor && hasNamed ) {\n    throw new IllegalArgumentException( \"PostgreSQL: register all parameters positionally when REF_CURSOR is used\" );\n}","typeGuard":"static boolean usesRefCursor(StoredProcedureQuery q) {\n    return q.getParameters().stream()\n            .anyMatch( p -> p.getMode() == ParameterMode.REF_CURSOR );\n}","tryCatchPattern":null,"preventionTips":["On PostgreSQL, register refcursor calls fully positionally — never mix named parameters in.","Prefer SETOF-returning table functions over refcursor for new code.","In @NamedStoredProcedureQuery, omit parameter names for PostgreSQL refcursor procedures."],"tags":["hibernate","postgresql","ref-cursor","named-parameters","stored-procedure"],"backgroundTag":"postgres-ref-cursor-limitations","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}