{"record":{"id":"7c4365cec36e0851","repo":"mybatis/mybatis-3","slug":"an-attempt-has-been-made-to-read-a-not-loaded-lazy","errorCode":null,"errorMessage":"An attempt has been made to read a not loaded lazy property '\" + property + \"' of a disconnected object\"","messagePattern":"An attempt has been made to read a not loaded lazy property '\" \\+ property \\+ \"' of a disconnected object\"","errorType":"exception","errorClass":"ExecutorException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java","lineNumber":92,"sourceCode":"        if (!FINALIZE_METHOD.equals(methodName) && PropertyNamer.isProperty(methodName) && !reloadingProperty) {\n          final String property = PropertyNamer.methodToProperty(methodName);\n          final String propertyKey = property.toUpperCase(Locale.ENGLISH);\n          if (unloadedProperties.containsKey(propertyKey)) {\n            final ResultLoaderMap.LoadPair loadPair = unloadedProperties.remove(propertyKey);\n            if (loadPair != null) {\n              try {\n                reloadingProperty = true;\n                loadPair.load(enhanced);\n              } finally {\n                reloadingProperty = false;\n                ErrorContext.instance().reset();\n              }\n            } else {\n              /*\n               * I'm not sure if this case can really happen or is just in tests - we have an unread property but no\n               * loadPair to load it.\n               */\n              throw new ExecutorException(\"An attempt has been made to read a not loaded lazy property '\" + property\n                  + \"' of a disconnected object\");\n            }\n          }\n        }\n\n        return enhanced;\n      } finally {\n        lock.unlock();\n      }\n    } catch (Throwable t) {\n      throw ExceptionUtil.unwrapThrowable(t);\n    }\n  }\n\n  protected abstract AbstractSerialStateHolder newSerialStateHolder(Object userBean,\n      Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,\n      List<Class<?>> constructorArgTypes, List<Object> constructorArgs);\n","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/executor/loader/AbstractEnhancedDeserializationProxy.java#L74-L110","documentation":"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.","triggerScenarios":"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.","commonSituations":"Caching MyBatis results in Redis/memcached with lazy loading enabled; passing enhanced objects between JVMs; session replication.","solutions":["Disable lazy loading (lazyLoadingEnabled=false) for objects that will be serialized","Use default (non-enhanced) result objects for caching/serialization: set proxyFactory to nothing or eagerly load with fetchType=\"eager\" on associations","Trigger all needed lazy properties BEFORE serializing the object"],"exampleFix":"<!-- before -->\n<resultMap id=\"userMap\" type=\"User\"><association property=\"orders\" select=\"selectOrders\" fetchType=\"lazy\"/></resultMap>\n\n<!-- after: eager for objects that get serialized -->\n<resultMap id=\"userMap\" type=\"User\"><association property=\"orders\" select=\"selectOrders\" fetchType=\"eager\"/></resultMap>","handlingStrategy":"try-catch","validationCode":"// Before serializing, force lazy properties to load\nif (result instanceof ResultLoaderMap.Host) { /* call the generated loadAll()/inspect */ }\nconfiguration.setLazyLoadingEnabled(false); // simplest: disable for serializable flows","typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["Use fetchType=\"eager\" for associations on objects placed in caches/sessions","Disable lazy loading globally if results are serialized"],"tags":["lazy-loading","serialization","proxy"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}