{"record":{"id":"5a0ef84b71dfd598","repo":"mybatis/mybatis-3","slug":"mapped-statements-with-nested-result-mappings-cann-5a0ef8","errorCode":null,"errorMessage":"Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. Use safeResultHandlerEnabled=false setting to bypass this check or ensure your statement returns ordered data and set resultOrdered=true on it.","messagePattern":"Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler\\. Use safeResultHandlerEnabled=false setting to bypass this check or ensure your statement returns ordered data and set resultOrdered=true on it\\.","errorType":"exception","errorClass":"ExecutorException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java","lineNumber":389,"sourceCode":"      checkResultHandler();\n      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);\n    } else {\n      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);\n    }\n  }\n\n  private void ensureNoRowBounds() {\n    if (configuration.isSafeRowBoundsEnabled() && rowBounds != null\n        && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {\n      throw new ExecutorException(\n          \"Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. \"\n              + \"Use safeRowBoundsEnabled=false setting to bypass this check.\");\n    }\n  }\n\n  protected void checkResultHandler() {\n    if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {\n      throw new ExecutorException(\n          \"Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. \"\n              + \"Use safeResultHandlerEnabled=false setting to bypass this check \"\n              + \"or ensure your statement returns ordered data and set resultOrdered=true on it.\");\n    }\n  }\n\n  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,\n      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {\n    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();\n\n    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();\n    ResultSet resultSet = rsw.getResultSet();\n    skipRows(resultSet, rowBounds);\n    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {\n      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw, resultMap, null);\n      Object rowValue = getRowValue(rsw, discriminatedResultMap, null, null);\n      if (!useCollectionConstructorInjection) {\n        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java#L371-L407","documentation":"Nested resultMap mapping groups rows in memory, so a custom ResultHandler receiving partial groups would see incomplete parent objects. When safeResultHandlerEnabled=true (default true) and a custom resultHandler is supplied for a nested-mapping statement that is not marked resultOrdered=true, checkResultHandler throws.","triggerScenarios":"handleRowValues with a resultMap having nested resultMaps, resultHandler != null, configuration.isSafeResultHandlerEnabled(), and mappedStatement.isResultOrdered()==false — e.g. session.select(statement, param, myResultHandler) on a JOIN-mapped query.","commonSituations":"Streaming results with a custom ResultHandler onto statements that use <collection>/<association> resultMap nesting; forgetting resultOrdered='true' on ordered-join statements that the developer knows are safe.","solutions":["If the SQL returns all rows of a parent contiguously (ordered by parent key), set resultOrdered=\"true\" on the <select> to declare safety","Otherwise use the default handler (selectList) for nested-mapped statements and post-process the returned graph","As last resort set <setting name=\"safeResultHandlerEnabled\" value=\"false\"/> and accept partial-group risk"],"exampleFix":"<!-- before -->\n<select id=\"selectOrdersWithItems\" resultMap=\"orderWithItemsMap\">\n  SELECT * FROM orders o JOIN items i ON i.order_id = o.id\n</select>\n<!-- after: ORDER BY guarantees parents arrive contiguously -->\n<select id=\"selectOrdersWithItems\" resultMap=\"orderWithItemsMap\" resultOrdered=\"true\">\n  SELECT * FROM orders o JOIN items i ON i.order_id = o.id ORDER BY o.id\n</select>","handlingStrategy":"validation","validationCode":"MappedStatement ms = session.getConfiguration().getMappedStatement(\"sel.ordersWithItems\");\nboolean nested = ms.getResultMaps().stream().anyMatch(ResultMap::hasNestedResultMaps);\nif (nested && customHandler != null && !ms.isResultOrdered()) {\n  throw new IllegalArgumentException(\"Set resultOrdered=true or use selectList for \" + ms.getId());\n}","typeGuard":null,"tryCatchPattern":"try { session.select(stmt, param, handler); } catch (ExecutorException e) { if (e.getMessage().contains(\"custom ResultHandler\")) { session.selectList(stmt, param); /* then post-process */ } else throw e; }","preventionTips":["Mark ordered JOIN statements resultOrdered=true with a matching ORDER BY","Route nested-mapped queries through selectList unless ordering is proven","Cover ResultHandler code paths in integration tests"],"tags":["mybatis","resulthandler","nested-results","streaming","configuration"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}