mybatis/mybatis-3 · error · ExecutorException

No setter found for the keyProperty '${propertyName}' in '${

Error message

No setter found for the keyProperty '${propertyName}' in '${className}'.

What it means

KeyAssigner.assign writes a generated key into the target parameter object via reflection. Before writing it verifies with MetaObject.hasSetter that the parameter object actually has a setter for the configured keyProperty; if not, it throws this ExecutorException naming the missing property and the target class. Note the check is against the object reached after resolving any paramName prefix.

Source

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

        String propertyName) {
      this.configuration = configuration;
      this.rsmd = rsmd;
      this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
      this.columnPosition = columnPosition;
      this.paramName = paramName;
      this.propertyName = propertyName;
    }

    protected void assign(ResultSet rs, Object param) {
      if (paramName != null) {
        // If paramName is set, param is ParamMap
        param = ((ParamMap<?>) param).get(paramName);
      }
      MetaObject metaParam = configuration.newMetaObject(param);
      try {
        if (typeHandler == null) {
          if (!metaParam.hasSetter(propertyName)) {
            throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
                + metaParam.getOriginalObject().getClass().getName() + "'.");
          }
          Type propertyType = metaParam.getGenericSetterType(propertyName).getKey();
          JdbcType jdbcType = JdbcType.forCode(rsmd.getColumnType(columnPosition));
          typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
          if (typeHandler == null) {
            typeHandler = typeHandlerRegistry.getTypeHandler(jdbcType);
          }
        }
        if (typeHandler == null) {
          // Error?
        } else {
          Object value = typeHandler.getResult(rs, columnPosition);
          metaParam.setValue(propertyName, value);
        }
      } catch (SQLException e) {
        throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
            e);

View on GitHub (pinned to 008069adb1)

Solutions

  1. Add a setter for the keyProperty on the parameter class (or make it a normal mutable POJO)
  2. Fix the property name spelling/case to match the class's setter
  3. If keyProperty includes a parameter prefix, verify it resolves to the right nested object

Example fix

// before
class User { private Long id; public Long getId() { return id; } } // no setter

// after
class User { private Long id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } }
Defensive patterns

Strategy: validation

Validate before calling

// Fail fast at startup that the insert parameter has a writable keyProperty
MetaObject mo = configuration.newMetaObject(new User());
if (!mo.hasSetter("id")) throw new IllegalStateException("User needs setId for useGeneratedKeys");

Prevention

When it happens

Trigger: useGeneratedKeys with keyProperty="userId" when the parameter object only has a field/getter but no setter, or the property name is misspelled, or keyProperty still contains a parameter prefix that was not stripped (checked against the raw object).

Common situations: Immutable parameter objects (no setters), Lombok @Getter-only classes, records, or property-name casing mistakes (id vs Id).

Related errors


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