mybatis/mybatis-3 · error · BindingException
Type {type} is already known to the MapperRegistry.
Error message
Type {type} is already known to the MapperRegistry. What it means
MapperRegistry.addMapper() refuses double registration of the same interface. The registry is keyed by Class, and re-parsing a mapper (XML plus annotation builder, or two config files both including it) triggers this BindingException instead of silently replacing the factory.
Source
Thrown at src/main/java/org/apache/ibatis/binding/MapperRegistry.java:63
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
View on GitHub (pinned to 008069adb1)
Solutions
- Register each mapper interface through exactly one mechanism (pick XML include, package scan, or programmatic addMapper)
- Guard programmatic registration with hasMapper(type) before addMapper
- In Spring, let one SqlSessionFactory + one MapperScan own mapper registration; do not also list them in mybatis-config.xml
Example fix
// before
configuration.addMapper(FooMapper.class); // may already be registered
// after
if (!configuration.getMapperRegistry().hasMapper(FooMapper.class)) {
configuration.addMapper(FooMapper.class);
} Defensive patterns
Strategy: validation
Validate before calling
MapperRegistry registry = configuration.getMapperRegistry();
if (!registry.hasMapper(FooMapper.class)) {
registry.addMapper(FooMapper.class);
} Prevention
- Register each mapper through exactly one mechanism (XML, package scan, or programmatic)
- Build the SqlSessionFactory once at startup, not per request
- Integration tests should construct fresh Configuration objects rather than sharing static state
When it happens
Trigger: Calling configuration.addMapper(X.class) after the same interface was already loaded via XML <mapper resource=.../>; mybatis-config listing the mapper both as <package> and as <mapper class>; mybatis-spring re-scanning an already-registered mapper; multiple SqlSessionFactory configurations sharing state incorrectly.
Common situations: Mixing registration styles (annotation + XML + package scan) for the same interface; programmatic bootstrap code that registers mappers on every request instead of once; integration tests building a factory per test against shared static state.
Related errors
- Type {type} is not known to the MapperRegistry.
- Unknown execution method for: {name}
- Mapper method '{name}' attempted to return null from a metho
- Mapper method '{name}' has an unsupported return type: {retu
- method {name} needs either a @ResultMap annotation, a @Resul
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/e3b9d56b19e3b705.
Report an issue: GitHub.