hibernate/hibernate-orm · error · HibernateException

Cannot mix named parameters and REF_CURSOR parameter on Post

Error message

Cannot mix named parameters and REF_CURSOR parameter on PostgreSQL

What it means

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.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgreSQLCallableStatementSupport.java:90

		if ( functionReturn == null && parameterMetadata.hasNamedParameters() ) {
			// That's just a rough estimate. I guess most params will have fewer than 8 chars on average
			paramStringSizeEstimate = registrations.size() * 10;
		}
		else {
			// For every param rendered as '?' we have a comma, hence the estimate
			paramStringSizeEstimate = registrations.size() * 2;
		}
		final JdbcCallImpl.Builder builder = new JdbcCallImpl.Builder();

		final int jdbcParameterOffset;
		final int startIndex;
		final CallMode callMode;
		if ( functionReturn != null ) {
			if ( functionReturn.getJdbcTypeCode() == SqlTypes.REF_CURSOR ) {
				if ( firstParamIsRefCursor ) {
					// validate that the parameter strategy is positional (cannot mix, and REF_CURSOR is inherently positional)
					if ( parameterMetadata.hasNamedParameters() ) {
						throw new HibernateException( "Cannot mix named parameters and REF_CURSOR parameter on PostgreSQL" );
					}
					callMode = CallMode.CALL_RETURN;
					startIndex = 1;
					jdbcParameterOffset = 1;
					builder.addParameterRegistration( registrations.get( 0 ).toJdbcParameterRegistration( 1, procedureCall ) );
				}
				else {
					callMode = CallMode.TABLE_FUNCTION;
					startIndex = 0;
					jdbcParameterOffset = 1;
					// Old style
//					callMode = CallMode.CALL_RETURN;
//					startIndex = 0;
//					jdbcParameterOffset = 2;
//					builder.setFunctionReturn( functionReturn.toJdbcFunctionReturn( procedureCall.getSession() ) );
				}
			}
			else {

View on GitHub (pinned to fad1729dce)

Solutions

  1. Register every parameter positionally (no names) whenever REF_CURSOR is involved on PostgreSQL.
  2. Prefer a table function returning SETOF rows and consume results with getResultList() — no REF_CURSOR registration needed.
  3. If names are required for readability, keep a positional map in your code and bind by position.

Example fix

// before (named + refcursor return -> throws)
proc.registerStoredProcedureParameter("in_user", Long.class, ParameterMode.IN);
proc.registerParameter(0, void.class, ParameterMode.REF_CURSOR); // function return

// after (all positional)
proc.registerStoredProcedureParameter(0, void.class, ParameterMode.REF_CURSOR);
proc.registerStoredProcedureParameter(1, Long.class, ParameterMode.IN);
Defensive patterns

Strategy: validation

Validate before calling

// forbid named parameters on a PostgreSQL call that uses REF_CURSOR
boolean hasRefCursor = proc.getParameters().stream()
        .anyMatch( p -> p.getMode() == ParameterMode.REF_CURSOR );
boolean hasNamed = proc.getParameters().stream()
        .anyMatch( p -> p.getName() != null );
if ( hasRefCursor && hasNamed ) {
    throw new IllegalArgumentException( "PostgreSQL: register all parameters positionally when REF_CURSOR is used" );
}

Type guard

static boolean usesRefCursor(StoredProcedureQuery q) {
    return q.getParameters().stream()
            .anyMatch( p -> p.getMode() == ParameterMode.REF_CURSOR );
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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