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

  1. Make sure the configurationFactory class returns a Configuration built from exactly the same mapper XML/annotations (same statement ids) that produced the serialized object
  2. Verify the statement id named in the message exists in the current SqlSessionFactory: sqlSessionFactory.getConfiguration().getMappedStatementNames()
  3. 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
  4. 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

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


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