hibernate/hibernate-orm · error · UnsupportedOperationException

Type [${userType}] does support parameter binding by name

Error message

Type [${userType}] does support parameter binding by name

What it means

CustomType.nullSafeSet(CallableStatement, value, String, session) (CustomType.java:307-323) can only bind a stored-procedure parameter by NAME when the wrapped UserType implements ProcedureParameterNamedBinder (and its canDoSetting() returns true); otherwise it throws UnsupportedOperationException. Note the message text 'does support parameter binding by name' is a known Hibernate wording bug - it means 'does NOT support'.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/type/CustomType.java:318

		return checkable[0] && isDirty( old, current, session );
	}

	@Override
	public boolean canDoSetting() {
		return getUserType() instanceof ProcedureParameterNamedBinder<?> procedureParameterNamedBinder
			&& procedureParameterNamedBinder.canDoSetting();
	}

	@Override
	public void nullSafeSet(CallableStatement statement, J value, String name, SharedSessionContractImplementor session)
			throws SQLException {
		if ( canDoSetting() ) {
			//noinspection unchecked
			( (ProcedureParameterNamedBinder<J>) getUserType() )
					.nullSafeSet( statement, value, name, session );
		}
		else {
			throw new UnsupportedOperationException(
					"Type [" + getUserType() + "] does support parameter binding by name"
			);
		}
	}

	@Override
	public boolean canDoExtraction() {
		return getUserType() instanceof ProcedureParameterExtractionAware<?> procedureParameterExtractionAware
			&& procedureParameterExtractionAware.canDoExtraction();
	}

	@Override
	public J extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session)
			throws SQLException {
		if ( canDoExtraction() ) {
			//noinspection unchecked
			return ((ProcedureParameterExtractionAware<J>) getUserType() )
					.extract( statement, startIndex, session );

View on GitHub (pinned to fad1729dce)

Solutions

  1. Implement ProcedureParameterNamedBinder on your UserType: add nullSafeSet(CallableStatement st, J value, String name, SharedSessionContract session) and return true from canDoSetting().
  2. If you cannot change the type, register the procedure parameter positionally instead of by name so the positional binder is used.
  3. Alternatively declare the parameter with a basic built-in type (e.g. String) and convert the value at the call site.
  4. Upgrade Hibernate if affected by the misleading message; parse it as 'does not support' regardless.

Example fix

// before
public class MoneyUserType implements UserType<Money> {
    // only implements positional nullSafeSet(PreparedStatement, ...) -> named binding throws
}

// after
public class MoneyUserType implements UserType<Money>, ProcedureParameterNamedBinder<Money> {
    @Override public boolean canDoSetting() { return true; }

    @Override public void nullSafeSet(CallableStatement st, Money value, String name,
                                      SharedSessionContract session) throws SQLException {
        st.setBigDecimal(name, value != null ? value.getAmount() : null);
    }
    // ... rest of UserType
}
Defensive patterns

Strategy: type-guard

Validate before calling

UserType<?> t = resolveUserType(paramType);
if (!(t instanceof org.hibernate.usertype.ProcedureParameterNamedBinder<?> nb)
        || !nb.canDoSetting()) {
    // bind positionally or use a basic type instead of a named custom-typed parameter
    registerPositionally(call, index, value);
}

Type guard

static boolean supportsNamedBinding(UserType<?> t) {
    return t instanceof org.hibernate.usertype.ProcedureParameterNamedBinder<?> nb
        && nb.canDoSetting();
}

Try / catch

try {
    query.setParameter("amount", money);
} catch (UnsupportedOperationException e) {
    if (e.getMessage().contains("parameter binding by name")) {
        // degrade to positional registration for this custom type
        call.bindPositional(1, money);
    } else throw e;
}

Prevention

When it happens

Trigger: A StoredProcedureQuery / ProcedureCall with a named parameter whose Java type is bound through a custom UserType (or AttributeConverter-backed custom type) that does not implement org.hibernate.usertype.ProcedureParameterNamedBinder; binding happens when you setParameter('name', value) on the registered named parameter. Positional binding does not go through this path.

Common situations: Custom UserTypes for things like JSON columns, monetary amounts or enums used as stored-procedure parameters; code migrated from positional to named procedure parameters after a DB refactoring; reusing a UserType written only for plain Statement/PreparedStatement binding.

Related errors


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