{"record":{"id":"8a849d303cf87b19","repo":"hibernate/hibernate-orm","slug":"type-usertype-does-support-parameter-binding","errorCode":null,"errorMessage":"Type [${userType}] does support parameter binding by name","messagePattern":"Type \\[(.+?)\\] does support parameter binding by name","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/type/CustomType.java","lineNumber":318,"sourceCode":"\t\treturn checkable[0] && isDirty( old, current, session );\n\t}\n\n\t@Override\n\tpublic boolean canDoSetting() {\n\t\treturn getUserType() instanceof ProcedureParameterNamedBinder<?> procedureParameterNamedBinder\n\t\t\t&& procedureParameterNamedBinder.canDoSetting();\n\t}\n\n\t@Override\n\tpublic void nullSafeSet(CallableStatement statement, J value, String name, SharedSessionContractImplementor session)\n\t\t\tthrows SQLException {\n\t\tif ( canDoSetting() ) {\n\t\t\t//noinspection unchecked\n\t\t\t( (ProcedureParameterNamedBinder<J>) getUserType() )\n\t\t\t\t\t.nullSafeSet( statement, value, name, session );\n\t\t}\n\t\telse {\n\t\t\tthrow new UnsupportedOperationException(\n\t\t\t\t\t\"Type [\" + getUserType() + \"] does support parameter binding by name\"\n\t\t\t);\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean canDoExtraction() {\n\t\treturn getUserType() instanceof ProcedureParameterExtractionAware<?> procedureParameterExtractionAware\n\t\t\t&& procedureParameterExtractionAware.canDoExtraction();\n\t}\n\n\t@Override\n\tpublic J extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session)\n\t\t\tthrows SQLException {\n\t\tif ( canDoExtraction() ) {\n\t\t\t//noinspection unchecked\n\t\t\treturn ((ProcedureParameterExtractionAware<J>) getUserType() )\n\t\t\t\t\t.extract( statement, startIndex, session );","sourceCodeStart":300,"sourceCodeEnd":336,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/type/CustomType.java#L300-L336","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Implement ProcedureParameterNamedBinder on your UserType: add nullSafeSet(CallableStatement st, J value, String name, SharedSessionContract session) and return true from canDoSetting().","If you cannot change the type, register the procedure parameter positionally instead of by name so the positional binder is used.","Alternatively declare the parameter with a basic built-in type (e.g. String) and convert the value at the call site.","Upgrade Hibernate if affected by the misleading message; parse it as 'does not support' regardless."],"exampleFix":"// before\npublic class MoneyUserType implements UserType<Money> {\n    // only implements positional nullSafeSet(PreparedStatement, ...) -> named binding throws\n}\n\n// after\npublic class MoneyUserType implements UserType<Money>, ProcedureParameterNamedBinder<Money> {\n    @Override public boolean canDoSetting() { return true; }\n\n    @Override public void nullSafeSet(CallableStatement st, Money value, String name,\n                                      SharedSessionContract session) throws SQLException {\n        st.setBigDecimal(name, value != null ? value.getAmount() : null);\n    }\n    // ... rest of UserType\n}","handlingStrategy":"type-guard","validationCode":"UserType<?> t = resolveUserType(paramType);\nif (!(t instanceof org.hibernate.usertype.ProcedureParameterNamedBinder<?> nb)\n        || !nb.canDoSetting()) {\n    // bind positionally or use a basic type instead of a named custom-typed parameter\n    registerPositionally(call, index, value);\n}","typeGuard":"static boolean supportsNamedBinding(UserType<?> t) {\n    return t instanceof org.hibernate.usertype.ProcedureParameterNamedBinder<?> nb\n        && nb.canDoSetting();\n}","tryCatchPattern":"try {\n    query.setParameter(\"amount\", money);\n} catch (UnsupportedOperationException e) {\n    if (e.getMessage().contains(\"parameter binding by name\")) {\n        // degrade to positional registration for this custom type\n        call.bindPositional(1, money);\n    } else throw e;\n}","preventionTips":["For every UserType that can appear in stored procedures, implement ProcedureParameterNamedBinder and ProcedureParameterExtractionAware up front.","Keep an integration test that calls a trivial procedure with each custom type, by name and by position."],"tags":["hibernate","stored-procedure","user-type","custom-type","named-parameter"],"backgroundTag":"stored-procedure-parameter-binding","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}