{"record":{"id":"ff605369f0efdd18","repo":"mybatis/mybatis-3","slug":"the-jdbc-type-must-be-specified-for-output-paramet","errorCode":null,"errorMessage":"The JDBC Type must be specified for output parameter.  Parameter: {}","messagePattern":"The JDBC Type must be specified for output parameter\\.  Parameter: (.+?)","errorType":"validation","errorClass":"ExecutorException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/executor/statement/CallableStatementHandler.java","lineNumber":105,"sourceCode":"    if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {\n      return connection.prepareCall(sql);\n    }\n    return connection.prepareCall(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);\n  }\n\n  @Override\n  public void parameterize(Statement statement) throws SQLException {\n    registerOutputParameters((CallableStatement) statement);\n    parameterHandler.setParameters((CallableStatement) statement);\n  }\n\n  private void registerOutputParameters(CallableStatement cs) throws SQLException {\n    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();\n    for (int i = 0, n = parameterMappings.size(); i < n; i++) {\n      ParameterMapping parameterMapping = parameterMappings.get(i);\n      if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {\n        if (null == parameterMapping.getJdbcType()) {\n          throw new ExecutorException(\n              \"The JDBC Type must be specified for output parameter.  Parameter: \" + parameterMapping.getProperty());\n        }\n        if (parameterMapping.getNumericScale() != null && (parameterMapping.getJdbcType() == JdbcType.NUMERIC\n            || parameterMapping.getJdbcType() == JdbcType.DECIMAL)) {\n          cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getNumericScale());\n        } else {\n          if (parameterMapping.getJdbcTypeName() == null) {\n            cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE);\n          } else {\n            cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE,\n                parameterMapping.getJdbcTypeName());\n          }\n        }\n      }\n    }\n  }\n\n}","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/executor/statement/CallableStatementHandler.java#L87-L123","documentation":"For CALLABLE statements, every OUT or INOUT parameter must be registered with the driver via registerOutParameter, which requires a JDBC type. MyBatis validates this before registration: if a ParameterMapping has mode OUT/INOUT but jdbcType == null, it throws this ExecutorException naming the parameter property.","triggerScenarios":"Calling a stored procedure ({call ...}) with parameterMode=\"OUT\" or \"INOUT\" on <parameter property=\"outParam\" .../> (or @Param-driven parameter map) where the jdbcType attribute is missing and no default JDBC type can be inferred for null.","commonSituations":"Stored procedure mappings copied from IN-only examples without jdbcType; relying on defaults that exist for IN params but not OUT; NULL INOUT params where the driver cannot infer the type so MyBatis cannot fall back.","solutions":["Add an explicit jdbcType to the OUT/INOUT parameter: <parameter property=\"result\" mode=\"OUT\" jdbcType=\"INTEGER\"/> (or in annotations: #{result, mode=OUT, jdbcType=INTEGER}).","If the type is driver-specific, also set typeName for named types via jdbcTypeName (e.g. Oracle STRUCT/ARRAY types).","For INOUT parameters with null input values, always specify jdbcType so both binding and registration have a type."],"exampleFix":"<!-- before -->\n<select id=\"calc\" statementType=\"CALLABLE\">{ call calc(#{x, mode=INOUT}, #{total, mode=OUT}) }</select>\n\n<!-- after -->\n<select id=\"calc\" statementType=\"CALLABLE\">{ call calc(#{x, mode=INOUT, jdbcType=INTEGER}, #{total, mode=OUT, jdbcType=DECIMAL}) }</select>","handlingStrategy":"validation","validationCode":"// before executing, assert every OUT/INOUT parameter mapping declares a jdbcType\nList<ParameterMapping> pms = boundSql.getParameterMappings();\nfor (ParameterMapping pm : pms) {\n  if ((pm.getMode() == ParameterMode.OUT || pm.getMode() == ParameterMode.INOUT)\n      && pm.getJdbcType() == null) {\n    throw new IllegalStateException(\"OUT/INOUT parameter '\" + pm.getProperty()\n        + \"' is missing jdbcType\");\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always write OUT/INOUT parameters as #{name, mode=OUT, jdbcType=...} — make it a code-review rule.","Keep a linter/regex check over mapper XML/annotations flagging mode=(OUT|INOUT) without jdbcType."],"tags":["mybatis","stored-procedures","callable","jdbctype","parameters"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}