{"record":{"id":"3302f92305796249","repo":"hibernate/hibernate-orm","slug":"type-usertype-does-support-parameter-value-ex","errorCode":null,"errorMessage":"Type [${userType}] does support parameter value extraction","messagePattern":"Type \\[(.+?)\\] does support parameter value extraction","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/type/CustomType.java","lineNumber":339,"sourceCode":"\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 );\n\t\t}\n\t\telse {\n\t\t\tthrow new UnsupportedOperationException(\n\t\t\t\t\t\"Type [\" + getUserType() + \"] does support parameter value extraction\"\n\t\t\t);\n\t\t}\n\t}\n\n\t@Override\n\tpublic J extract(CallableStatement statement, String paramName, 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, paramName, session );\n\t\t}\n\t\telse {\n\t\t\tthrow new UnsupportedOperationException(\n\t\t\t\t\t\"Type [\" + getUserType() + \"] does support parameter value extraction\"\n\t\t\t);\n\t\t}","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/type/CustomType.java#L321-L357","documentation":"CustomType.extract(CallableStatement, int, session) (CustomType.java:327-341) throws UnsupportedOperationException when canDoExtraction() is false, i.e. the wrapped UserType does not implement ProcedureParameterExtractionAware (or its canDoExtraction() returns false). Extraction-by-position is what backs StoredProcedureQuery.getOutputParameterValue(int) for OUT/INOUT parameters, so reading an output parameter typed with a plain UserType fails.","triggerScenarios":"StoredProcedureQuery.registerStoredProcedureParameter(i, MyCustomType.class, ParameterMode.OUT) followed by getOutputParameterValue(i), where the custom type does not implement org.hibernate.usertype.ProcedureParameterExtractionAware; also frameworks that reflectively read all output parameters after executing a call.","commonSituations":"Custom types for JSONB/enum/monetary columns reused as procedure OUT parameters; migrating from direct JDBC calls (which handled extraction manually) to JPA StoredProcedureQuery; upgrading Hibernate versions where extraction was previously never invoked for these types.","solutions":["Implement ProcedureParameterExtractionAware on the UserType: add extract(CallableStatement, int, SharedSessionContract) and return true from canDoExtraction().","If the type cannot be changed, register the parameter as a basic type (e.g. String) and convert the returned value yourself.","Check INOUT parameters too - they also pass through extraction after execution.","Keep positional and named extract implementations consistent if you support both call styles."],"exampleFix":"// before\npublic class JsonUserType implements UserType<Payload> { /* no extraction support */ }\n\n// after\npublic class JsonUserType implements UserType<Payload>, ProcedureParameterExtractionAware<Payload> {\n    @Override public boolean canDoExtraction() { return true; }\n\n    @Override public Payload extract(CallableStatement statement, int startIndex,\n                                     SharedSessionContract session) throws SQLException {\n        String json = statement.getString(startIndex);\n        return json == null ? null : parse(json);\n    }\n\n    @Override public Payload extract(CallableStatement statement, String paramName,\n                                     SharedSessionContract session) throws SQLException {\n        String json = statement.getString(paramName);\n        return json == null ? null : parse(json);\n    }\n    // ... rest of UserType\n}","handlingStrategy":"type-guard","validationCode":"UserType<?> t = resolveUserType(paramType);\nif (!(t instanceof org.hibernate.usertype.ProcedureParameterExtractionAware<?> ea)\n        || !ea.canDoExtraction()) {\n    // read as String/basic and convert, instead of getOutputParameterValue(int)\n    String raw = (String) query.getOutputParameterValue(index);\n    return parse(raw);\n}","typeGuard":"static boolean supportsExtraction(UserType<?> t) {\n    return t instanceof org.hibernate.usertype.ProcedureParameterExtractionAware<?> ea\n        && ea.canDoExtraction();\n}","tryCatchPattern":"try {\n    return (Money) query.getOutputParameterValue(index);\n} catch (UnsupportedOperationException e) {\n    if (e.getMessage().contains(\"parameter value extraction\")) {\n        return parse((String) query.getOutputParameterValue(index));\n    }\n    throw e;\n}","preventionTips":["Treat ProcedureParameterExtractionAware as part of the definition-of-done for any UserType used in callable statements.","Read OUT parameters of unsupported custom types through a basic-typed registration and convert at the call site."],"tags":["hibernate","stored-procedure","user-type","custom-type","output-parameter"],"backgroundTag":"stored-procedure-output-extraction","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}