{"record":{"id":"cf2eedf02d2d6f9e","repo":"mybatis/mybatis-3","slug":"mapped-statements-with-nested-result-mappings-cann","errorCode":null,"errorMessage":"Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. Use safeRowBoundsEnabled=false setting to bypass this check.","messagePattern":"Mapped Statements with nested result mappings cannot be safely constrained by RowBounds\\. Use safeRowBoundsEnabled=false setting to bypass this check\\.","errorType":"exception","errorClass":"ExecutorException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java","lineNumber":381,"sourceCode":"  //\n  // HANDLE ROWS FOR SIMPLE RESULTMAP\n  //\n\n  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,\n      RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {\n    if (resultMap.hasNestedResultMaps()) {\n      ensureNoRowBounds();\n      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","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java#L363-L399","documentation":"With nested resultMaps (JOIN-based collections/associations), RowBounds cannot work correctly because nested rows must be grouped in memory, so skipping/limiting raw rows corrupts grouping. When safeRowBoundsEnabled=true (default false) and a RowBounds with a limit below NO_ROW_LIMIT or offset above 0 is passed to such a statement, ensureNoRowBounds throws.","triggerScenarios":"handleRowValues on a resultMap with hasNestedResultMaps(), configuration.isSafeRowBoundsEnabled()==true, and a RowBounds whose getLimit() < RowBounds.NO_ROW_LIMIT (Integer.MAX_VALUE) or getOffset() > 0 — i.e. passing new RowBounds(offset, limit) to a nested-mapping query.","commonSituations":"Enabling safeRowBoundsEnabled globally then reusing RowBounds-based pagination code on JOIN statements with <collection>/<association>; migrating pagination from simple to nested resultMaps without switching the SQL to LIMIT/OFFSET.","solutions":["Paginate in SQL (LIMIT/OFFSET or ROWNUM) instead of RowBounds for statements with nested resultMaps","If you accept the risk, set <setting name=\"safeRowBoundsEnabled\" value=\"false\"/> to bypass the guard","Use a separate non-nested resultMap (flat DTO + manual grouping) when RowBounds pagination is mandatory"],"exampleFix":"// before\nList<Order> orders = session.selectList(\"sel.ordersWithItems\", custId,\n    new RowBounds(20, 10)); // nested resultMap -> throws when safeRowBounds=true\n// after: paginate in SQL\nList<Order> orders = session.selectList(\"sel.ordersWithItemsPaged\",\n    Map.of(\"id\", custId, \"offset\", 20, \"limit\", 10)); // SQL: ... LIMIT #{limit} OFFSET #{offset}","handlingStrategy":"validation","validationCode":"// Refuse RowBounds on nested statements when the guard is on\nResultMap rm = session.getConfiguration().getResultMap(\"orderWithItemsMap\");\nif (rm.hasNestedResultMaps() && session.getConfiguration().isSafeRowBoundsEnabled()) {\n  throw new IllegalArgumentException(\"Paginate in SQL for nested resultMaps: \" + rm.getId());\n}","typeGuard":null,"tryCatchPattern":"try { session.selectList(stmt, param, new RowBounds(off, lim)); } catch (ExecutorException e) { if (e.getMessage().contains(\"RowBounds\")) { rerun with SQL-level pagination } else throw e; }","preventionTips":["Prefer SQL LIMIT/OFFSET pagination everywhere","If using RowBounds, keep nested resultMaps off those statements","Document why safeRowBoundsEnabled is set if anyone disables it"],"tags":["mybatis","rowbounds","pagination","nested-results","configuration"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}