mybatis/mybatis-3 · error · ExecutorException

An attempt has been made to read a not loaded lazy property

Error message

An attempt has been made to read a not loaded lazy property '" + property + "' of a disconnected object"

What it means

For lazily-loaded enhanced objects (cglib/ javassist proxies with aggressiveLazyLoading or on-demand loading), each unread lazy property carries a LoadPair that knows how to fetch it. When such an object is serialized, deserialized in another JVM, and then a lazy property is read while the proxy is disconnected and has NO loadPair (the source comment itself doubts this happens outside tests), the property can never be loaded, so this ExecutorException is thrown.

Source

Thrown at src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java:92

        if (!FINALIZE_METHOD.equals(methodName) && PropertyNamer.isProperty(methodName) && !reloadingProperty) {
          final String property = PropertyNamer.methodToProperty(methodName);
          final String propertyKey = property.toUpperCase(Locale.ENGLISH);
          if (unloadedProperties.containsKey(propertyKey)) {
            final ResultLoaderMap.LoadPair loadPair = unloadedProperties.remove(propertyKey);
            if (loadPair != null) {
              try {
                reloadingProperty = true;
                loadPair.load(enhanced);
              } finally {
                reloadingProperty = false;
                ErrorContext.instance().reset();
              }
            } else {
              /*
               * I'm not sure if this case can really happen or is just in tests - we have an unread property but no
               * loadPair to load it.
               */
              throw new ExecutorException("An attempt has been made to read a not loaded lazy property '" + property
                  + "' of a disconnected object");
            }
          }
        }

        return enhanced;
      } finally {
        lock.unlock();
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  protected abstract AbstractSerialStateHolder newSerialStateHolder(Object userBean,
      Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
      List<Class<?>> constructorArgTypes, List<Object> constructorArgs);

View on GitHub (pinned to 008069adb1)

Solutions

  1. Disable lazy loading (lazyLoadingEnabled=false) for objects that will be serialized
  2. Use default (non-enhanced) result objects for caching/serialization: set proxyFactory to nothing or eagerly load with fetchType="eager" on associations
  3. Trigger all needed lazy properties BEFORE serializing the object

Example fix

<!-- before -->
<resultMap id="userMap" type="User"><association property="orders" select="selectOrders" fetchType="lazy"/></resultMap>

<!-- after: eager for objects that get serialized -->
<resultMap id="userMap" type="User"><association property="orders" select="selectOrders" fetchType="eager"/></resultMap>
Defensive patterns

Strategy: try-catch

Validate before calling

// Before serializing, force lazy properties to load
if (result instanceof ResultLoaderMap.Host) { /* call the generated loadAll()/inspect */ }
configuration.setLazyLoadingEnabled(false); // simplest: disable for serializable flows

Try / catch

try { orders = user.getOrders(); } catch (PersistenceException e) { if (String.valueOf(e.getMessage()).contains("disconnected object")) { /* refetch from DB instead of using the stale proxy */ } else throw e; }

Prevention

When it happens

Trigger: Serializing a lazy-loading proxy result object, deserializing it elsewhere, and accessing a lazy property whose LoadPair was lost; constructed in unit tests that strip or fake the proxy state.

Common situations: Caching MyBatis results in Redis/memcached with lazy loading enabled; passing enhanced objects between JVMs; session replication.

Related errors


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