mybatis/mybatis-3 · error · TypeException

Failed to invoke constructor {}

Error message

Failed to invoke constructor {}

What it means

When a type handler is instantiated on demand, TypeHandlerRegistry calls candidate.newInstance(type) through reflection. Any ReflectiveOperationException — including an exception thrown INSIDE the constructor, such as EnumTypeHandler's 'Type argument cannot be null' — surfaces as this TypeException naming the handler class.

Source

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

    if (candidate == null) {
      if (type instanceof Class) {
        Class<?> clazz = (Class<?>) type;
        if (Enum.class.isAssignableFrom(clazz)) {
          Class<?> enumClass = (clazz.isAnonymousClass() || !clazz.isEnum()) ? clazz.getSuperclass() : clazz;
          TypeHandler<?> enumHandler = getInstance(enumClass, defaultEnumTypeHandler);
          register(new Type[] { enumClass }, new JdbcType[] { jdbcType }, enumHandler);
          return enumHandler;
        }
      }
      return null;
    }

    try {
      TypeHandler<?> typeHandler = (TypeHandler<?>) candidate.newInstance(type);
      register(type, jdbcType, typeHandler);
      return typeHandler;
    } catch (ReflectiveOperationException e) {
      throw new TypeException("Failed to invoke constructor " + candidate.toString(), e);
    }
  }

  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
    if (jdbcHandlerMap != null) {
      return NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap) ? null : jdbcHandlerMap;
    }
    if (type instanceof Class) {
      Class<?> clazz = (Class<?>) type;
      if (!Enum.class.isAssignableFrom(clazz)) {
        jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
      }
    }
    typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
    return jdbcHandlerMap;
  }

View on GitHub (pinned to 008069adb1)

Solutions

  1. Read the cause: if it is another exception, the constructor itself threw — fix that root cause (usually a null/unresolvable java type)
  2. Give the handler class a public no-arg constructor and a public constructor taking Class
  3. Register the handler with an explicit javaType so the registry never passes null

Example fix

// before
class MyHandler extends BaseTypeHandler<MyType> {
  public MyHandler(Class<?> type) { if (type == null) throw new IllegalStateException(); }
}

// after
class MyHandler extends BaseTypeHandler<MyType> {
  public MyHandler() {}
  public MyHandler(Class<?> type) { /* tolerate null, default sensibly */ }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// verify a usable constructor exists before registering
boolean usable = Arrays.stream(handlerClass.getConstructors())
  .anyMatch(c -> c.getParameterCount() == 1 && (Type.class.equals(c.getParameterTypes()[0]) || Class.class.equals(c.getParameterTypes()[0])));
if (!usable && Arrays.stream(handlerClass.getConstructors()).noneMatch(c -> c.getParameterCount() == 0)) {
  throw new IllegalStateException(handlerClass + " has no (Class) or () constructor");
}

Try / catch

try { registry.getInstance(type, handlerClass); } catch (TypeException e) { // unwrap cause; if constructor logic threw, fix the constructor rather than retrying }

Prevention

When it happens

Trigger: A custom or built-in handler whose Class-arg constructor throws (null java type passed by the registry, unsupported type argument); a handler class with a non-public constructor (IllegalAccessException surfaces here too).

Common situations: Custom handlers that validate their constructor argument and throw for null; registering a handler class whose only constructor is package-private; handler constructors that perform external lookups (JNDI, service) that fail at init.

Related errors


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