mybatis/mybatis-3 · error · IllegalArgumentException

'{}' does not implement TypeHandler.

Error message

'{}' does not implement TypeHandler.

What it means

Before scanning a handler class's constructors, TypeHandlerRegistry.register verifies TypeHandler.class.isAssignableFrom(handlerClass). A class that does not implement the interface is rejected immediately with this IllegalArgumentException naming the class.

Source

Thrown at src/main/java/org/apache/ibatis/type/TypeHandlerRegistry.java:467

  @Deprecated(since = "3.6.0", forRemoval = true)
  public void register(String javaTypeClassName, String typeHandlerClassName) throws ClassNotFoundException {
    register(Resources.classForName(javaTypeClassName), Resources.classForName(typeHandlerClassName));
  }

  public void register(Type mappedJavaType, Class<?> handlerClass) {
    register(new Type[] { mappedJavaType }, mappedJdbcTypes(handlerClass), handlerClass);
  }

  // java type + jdbc type + handler type

  public void register(Type mappedJavaType, JdbcType mappedJdbcType, Class<?> handlerClass) {
    register(new Type[] { mappedJavaType }, new JdbcType[] { mappedJdbcType }, handlerClass);
  }

  private void register(Type[] mappedJavaTypes, JdbcType[] mappedJdbcTypes, Class<?> handlerClass) {
    if (!TypeHandler.class.isAssignableFrom(handlerClass)) {
      throw new IllegalArgumentException(String.format("'%s' does not implement TypeHandler.", handlerClass.getName()));
    }
    for (Constructor<?> constructor : handlerClass.getConstructors()) {
      if (constructor.getParameterCount() != 1) {
        continue;
      }
      Class<?> argType = constructor.getParameterTypes()[0];
      if (Type.class.equals(argType) || Class.class.equals(argType)) {
        for (Type javaType : mappedJavaTypes) {
          smartHandlers.computeIfAbsent(javaType, k -> constructor);
        }
        return;
      }
    }
    // It is not a smart handler
    register(mappedJavaTypes, mappedJdbcTypes, getInstance(null, handlerClass));
  }

  private Type[] mappedJavaTypes(Class<?> clazz) {

View on GitHub (pinned to 008069adb1)

Solutions

  1. Make the class implement org.apache.ibatis.type.TypeHandler (usually by extending BaseTypeHandler<T>)
  2. Fix the handler attribute to reference the actual handler class
  3. If an alias is used for the handler, verify it resolves to the intended class

Example fix

// before
public class MoneyMapper { ... }

// after
public class MoneyTypeHandler extends BaseTypeHandler<Money> { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

if (!TypeHandler.class.isAssignableFrom(handlerClass)) {
  throw new IllegalArgumentException(handlerClass.getName() + " is not a TypeHandler");
}

Type guard

static boolean isTypeHandler(Class<?> c) {
  return c != null && TypeHandler.class.isAssignableFrom(c);
}

Prevention

When it happens

Trigger: <typeHandler handler="com.example.NotAHandler"/> where the class is a plain utility/mapper class; passing a type-alias-resolved class that points to the wrong type; copy-paste of a handler registration with an outdated class name.

Common situations: Refactoring that renamed the real handler and left an old class in place; alias collisions resolving handler= to a different class; pointing handler= at an interceptor (org.apache.ibatis.plugin.Interceptor) instead of a TypeHandler.

Related errors


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