mybatis/mybatis-3 · error · ExecutorException
Cannot lazy load property [" + this.property + "] of deseria
Error message
Cannot lazy load property [" + this.property + "] of deserialized object [" + userObject.getClass() + "] because configuration does not contain statement [" + this.mappedStatement + "]"
What it means
Thrown when a lazily-loaded property of a Java-serialized (deserialized) domain object is accessed, and the rebuilt Configuration no longer contains the MappedStatement that was saved in the LoadPair at serialization time. Lazy-loading proxies survive serialization by storing only the statement id plus a serializable parameter, then re-resolving the statement through the configurationFactory. If the Configuration returned by that factory does not have a statement registered under the saved id, this ExecutorException is thrown.
Source
Thrown at src/main/java/org/apache/ibatis/executor/loader/ResultLoaderMap.java:202
}
if (this.resultLoader == null) {
throw new IllegalArgumentException("resultLoader is null");
}
this.load(null);
}
public void load(final Object userObject) throws SQLException {
if (this.metaResultObject == null || this.resultLoader == null) {
if (this.mappedParameter == null) {
throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
+ "required parameter of mapped statement [" + this.mappedStatement + "] is not serializable.");
}
final Configuration config = this.getConfiguration();
final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
if (ms == null) {
throw new ExecutorException(
"Cannot lazy load property [" + this.property + "] of deserialized object [" + userObject.getClass()
+ "] because configuration does not contain statement [" + this.mappedStatement + "]");
}
this.metaResultObject = config.newMetaObject(userObject);
this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
metaResultObject.getSetterType(this.property), null, null);
}
/*
* We are using a new executor because we may be (and likely are) on a new thread and executors aren't thread
* safe. (Is this sufficient?) A better approach would be making executors thread safe.
*/
if (this.serializationCheck == null) {
final ResultLoader old = this.resultLoader;
this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
}View on GitHub (pinned to 008069adb1)
Solutions
- Make sure the configurationFactory class returns a Configuration built from exactly the same mapper XML/annotations (same statement ids) that produced the serialized object
- Verify the statement id named in the message exists in the current SqlSessionFactory: sqlSessionFactory.getConfiguration().getMappedStatementNames()
- Force lazy properties to load before serializing (touch the getters, or call the proxy's fully-loaded state) so no LoadPair has to rebuild after deserialization
- If the statement was renamed, keep the old id as an alias or migrate serialized objects before upgrading
Example fix
// before: session replicated with lazy objects, factory returns a config missing 'com.foo.Bar.selectItems'
public class MyConfigurationFactory {
public static Configuration getConfiguration() {
return otherFactory.getConfiguration(); // different mapper set
}
}
// after: factory returns the Configuration that owns the mapped statements
public class MyConfigurationFactory {
public static Configuration getConfiguration() {
return AppSqlSessionFactoryBean.getSqlSessionFactory().getConfiguration();
}
} Defensive patterns
Strategy: validation
Validate before calling
// Before touching a deserialized lazy object, verify the factory's config still knows the statement
Configuration cfg = MyConfigFactory.getConfiguration();
if (!cfg.getMappedStatementNames().contains(expectedStatementId)) {
throw new IllegalStateException("Statement gone: " + expectedStatementId + " - force reload before serializing");
} Try / catch
try { order.getItems(); } catch (ExecutorException e) { if (e.getMessage() != null && e.getMessage().contains("configuration does not contain statement")) { // re-fetch the entity fresh from DB instead of using the deserialized copy } else { throw e; } } Prevention
- Always pair lazyLoadingEnabled with a configurationFactory backed by the same SqlSessionFactory
- Touch lazy getters before writing objects into sessions/caches
- Regression-test statement id renames against serialized-object consumers
When it happens
Trigger: ResultLoaderMap.LoadPair.load(userObject) is invoked after deserialization (metaResultObject/resultLoader are null), mappedParameter is not null, configurationFactory returns a Configuration, and config.getMappedStatement(this.mappedStatement) returns null because the statement id stored at serialize time is absent from that Configuration.
Common situations: Objects with unresolved lazy properties are serialized (HTTP session replication, caches, messaging) and read back in a JVM whose SqlSessionFactory was built from a different or smaller mybatis-config/mapper set; renamed or removed statement ids between application versions; a configurationFactory pointing at a different SqlSessionFactory than the one that produced the object.
Related errors
- Cannot get Configuration as configuration factory was not se
- Property [" + this.property + "] cannot be loaded because re
- Cannot get Configuration as factory method [" + this.configu
- Cannot get Configuration as factory class [" + this.configur
- Cannot get Configuration as factory method [" + this.configu
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/e626abf0fc4b88e8.
Report an issue: GitHub.