mybatis/mybatis-3 · error · TypeException

Error determining JDBC type for column {}. Cause: {}

Error message

Error determining JDBC type for column {}.  Cause: {}

What it means

MyBatis throws this TypeException from UnknownTypeHandler when it must pick a TypeHandler at runtime by inspecting ResultSetMetaData (because no handler could be determined statically for the property) and the JDBC driver raises SQLException while supplying that metadata. The handler builds a column-name -> index lookup via rs.getMetaData(), getColumnCount(), and getColumnLabel()/getColumnName(), then resolves the JDBC/Java type for the column. Any SQLException from those driver calls is wrapped in TypeException with the offending column name, meaning the failure is in the driver/metadata layer, not in MyBatis' type registry itself. Note the per-column type lookups (getColumnType/getColumnClassName) are wrapped in safe* methods that swallow errors, so the surviving throwers are getMetaData(), getColumnCount(), and the label/name iteration.

Source

Thrown at src/main/java/org/apache/ibatis/type/UnknownTypeHandler.java:129

      columnIndexLookup = new HashMap<>();
      ResultSetMetaData rsmd = rs.getMetaData();
      int count = rsmd.getColumnCount();
      boolean useColumnLabel = config.isUseColumnLabel();
      for (int i = 1; i <= count; i++) {
        String name = useColumnLabel ? rsmd.getColumnLabel(i) : rsmd.getColumnName(i);
        columnIndexLookup.put(name, i);
      }
      Integer columnIndex = columnIndexLookup.get(column);
      TypeHandler<?> handler = null;
      if (columnIndex != null) {
        handler = resolveTypeHandler(rsmd, columnIndex);
      }
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = ObjectTypeHandler.INSTANCE;
      }
      return handler;
    } catch (SQLException e) {
      throw new TypeException("Error determining JDBC type for column " + column + ".  Cause: " + e, e);
    }
  }

  private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) {
    TypeHandler<?> handler = null;
    JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
    Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
    if (javaType != null && jdbcType != null) {
      handler = typeHandlerRegistrySupplier.get().getTypeHandler(javaType, jdbcType);
    } else if (javaType != null) {
      handler = typeHandlerRegistrySupplier.get().getTypeHandler(javaType);
    } else if (jdbcType != null) {
      handler = typeHandlerRegistrySupplier.get().getTypeHandler(jdbcType);
    }
    return handler;
  }

  private JdbcType safeGetJdbcTypeForColumn(ResultSetMetaData rsmd, Integer columnIndex) {

View on GitHub (pinned to 008069adb1)

Solutions

  1. Upgrade the JDBC driver to the latest version matching your DB — most metadata SQLExceptions (getColumnType/getColumnLabel failing on specific types) are driver bugs fixed in newer releases.
  2. Give MyBatis static type information so UnknownTypeHandler is never consulted: set javaType on the <result>/<id> mapping, or specify typeHandler explicitly, or map to a concrete property type that has a registered handler.
  3. If using streaming/cursor ResultSets, switch to regular fetch-size pagination (e.g. MySQL statement fetchSize > 0 instead of Integer.MIN_VALUE) so ResultSetMetaData stays available while rows are read.
  4. Check that the SqlSession/ResultSet is still open when results are mapped — remove any code that closes the session in a finally block before iteration completes, or that shares a session across threads.
  5. If the driver throws on the label/alias form, try switching configuration setting useColumnLabel (Configuration.setUseColumnLabel) so the lookup iterates getColumnName vs getColumnLabel, whichever your driver supports.
  6. As a last resort, register a custom TypeHandler for the problematic column's type (TypeHandlerRegistry.register) so resolution never falls through to UnknownTypeHandler.

Example fix

<!-- before: property type unknown -> UnknownTypeHandler inspects metadata at runtime -->
<resultMap id="rowMap" type="com.example.Row">
  <result column="PAYLOAD" property="payload"/>
</resultMap>

<!-- after: explicit javaType/typeHandler, metadata never queried -->
<resultMap id="rowMap" type="com.example.Row">
  <result column="PAYLOAD" property="payload" javaType="java.lang.String"/>
</resultMap>
Defensive patterns

Strategy: try-catch

Validate before calling

// Before running the query, confirm the driver can supply metadata for this statement's ResultSet
try (PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) {
  ResultSetMetaData md = rs.getMetaData();          // throws here, not mid-mapping, if the driver is broken
  int n = md.getColumnCount();
  for (int i = 1; i <= n; i++) {
    md.getColumnLabel(i);
    md.getColumnName(i);
  }
} catch (SQLException e) {
  // fail fast with a clear message about driver/metadata support before MyBatis maps anything
}

Type guard

// In the mapper XML/config side: guard the mapping so UnknownTypeHandler is never used
public static boolean mappingIsSafe(ResultMap rm) {
  for (ResultMapping m : rm.getResultMappings()) {
    if (m.getJavaType() == Object.class && m.getTypeHandler() == null
        && m.getJdbcType() == null) {
      return false; // would fall through to UnknownTypeHandler -> runtime metadata lookup
    }
  }
  return true;
}

Try / catch

try {
  List<Row> rows = sqlSession.selectList("com.example.selectRows");
} catch (org.apache.ibatis.type.TypeException e) {
  if (e.getMessage() != null && e.getMessage().startsWith("Error determining JDBC type for column")) {
    String column = e.getMessage().split("for column ")[1].split("\.\\s+Cause")[0];
    throw new IllegalStateException(
        "JDBC driver failed to report metadata for column [" + column
        + "] — check driver version / mapping javaType", e.getCause());
  }
  throw e; // not this error — rethrow
}

Prevention

When it happens

Trigger: A result mapping where the property has no javaType, no explicit typeHandler, and no matching registered handler, so UnknownTypeHandler.getNullableResult(rs, columnName) is invoked (e.g. mapping to Object or a property whose type has no registered handler). Then one of these SQLExceptions fires: (1) rs.getMetaData() on a closed or already-advanced streaming ResultSet; (2) rsmd.getColumnCount()/getColumnLabel(i) failing on drivers with limited metadata support (some streaming/cursor modes, forward-only cursors, certain Oracle/MySQL/SQLite driver versions); (3) the ResultSet being accessed concurrently or after the statement was closed; (4) a column label the driver cannot resolve during metadata iteration.

Common situations: Mapping a column to an Object property (resultType="map" with unknown JDBC types is a cousin but goes through a different path; this one is unknown property types); using streaming ResultSets (MySQL fetchSize=Integer.MIN_VALUE, Oracle cursor streaming) where metadata is unavailable mid-iteration; upgrading a JDBC driver that changed metadata behavior; using outdated SQLite/Informix/Derby drivers that throw on getColumnClassName or getColumnName for exotic types; closing the SqlSession/ResultSet in another thread while results are still being mapped; complex column labels (aliases, expressions) that drivers report inconsistently between getColumnName and getColumnLabel when useColumnLabel is toggled.

Related errors


AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14). Data as JSON: /api/errors/ffa45d6ab5510088. Report an issue: GitHub.