mybatis/mybatis-3 · error · ExecutorException

Too many keys are generated. There are only %d target object

Error message

Too many keys are generated. There are only %d target object(s). A 'Map' passed as the sole parameter is treated as a single target object, so keys generated by a multi-row insert cannot be assigned to a collection nested in the 'Map'. Pass the collection itself as the sole parameter, or declare the parameters with @Param and prefix 'keyProperty' with the parameter name (e.g. 'list.id').

What it means

During a multi-row insert (single insert statement with a collection parameter), Jdbc3KeyGenerator iterates the generated-keys ResultSet side by side with the parameter objects. When a java.util.Map is the sole parameter, MyBatis treats the whole Map as ONE target object, so if the insert generated several keys (one per row) there are more keys than targets and this ExecutorException is thrown, explaining that a Map cannot receive per-row keys.

Source

Thrown at src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java:126

      // Single param without @Param
      assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
    }
  }

  private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
      String[] keyProperties, Object parameter) throws SQLException {
    Collection<?> params = collectionize(parameter);
    if (params.isEmpty()) {
      return;
    }
    List<KeyAssigner> assignerList = new ArrayList<>();
    for (int i = 0; i < keyProperties.length; i++) {
      assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
    }
    Iterator<?> iterator = params.iterator();
    while (rs.next()) {
      if (!iterator.hasNext()) {
        throw new ExecutorException(
            String.format(parameter instanceof Map ? MSG_TOO_MANY_KEYS_FOR_MAP : MSG_TOO_MANY_KEYS, params.size()));
      }
      Object param = iterator.next();
      assignerList.forEach(x -> x.assign(rs, param));
    }
  }

  private void assignKeysToParamMapList(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
      String[] keyProperties, ArrayList<ParamMap<?>> paramMapList) throws SQLException {
    Iterator<ParamMap<?>> iterator = paramMapList.iterator();
    List<KeyAssigner> assignerList = new ArrayList<>();
    long counter = 0;
    while (rs.next()) {
      if (!iterator.hasNext()) {
        throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
      }
      ParamMap<?> paramMap = iterator.next();
      if (assignerList.isEmpty()) {

View on GitHub (pinned to 008069adb1)

Solutions

  1. Pass the collection itself as the sole parameter: List<User> insert(List<User> users)
  2. Or declare parameters with @Param and prefix keyProperty with the parameter name: keyProperty="list.id"
  3. Do not use a bare Map as the sole parameter of a multi-row insert that needs generated keys assigned

Example fix

// before
Map<String,Object> m = Map.of("list", users);
mapper.insert(m); // with keyProperty="id" -> exception

// after
int insert(@Param("list") List<User> users); // keyProperty="list.id"
Defensive patterns

Strategy: validation

Validate before calling

// Guard before a multi-row insert with generated keys
if (params instanceof Map && !(params instanceof List)) {
  throw new IllegalArgumentException("Pass the collection itself or use @Param + 'keyProperty=list.id'");
}

Prevention

When it happens

Trigger: A multi-row INSERT ... VALUES (row1),(row2) with useGeneratedKeys where the single parameter is a Map (e.g. @Param map or a plain HashMap) that merely CONTAINS the collection (e.g. map.put("list", items)) instead of the collection being the parameter itself.

Common situations: Wrapping a list in a Map to pass extra data (map.put("items", list)) and still expecting each generated key to land inside map.items[i].id.

Related errors


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