| <!DOCTYPE html> |
| |
| |
| <!-- |
| | Generated by Apache Maven Doxia Site Renderer 2.0.0 from src/site/xdoc/docs/json-pagination.xml at 2026-05-18 |
| | Rendered using Apache Maven Fluido Skin 2.0.0-M11 |
| --> |
| <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| <meta name="generator" content="Apache Maven Doxia Site Renderer 2.0.0" /> |
| <title>Offset/Limit Pagination for JSON-RPC Services – Apache Axis2</title> |
| <link rel="stylesheet" href="../css/apache-maven-fluido-2.0.0-M11.min.css" /> |
| <link rel="stylesheet" href="../css/site.css" /> |
| <link rel="stylesheet" href="../css/print.css" media="print" /> |
| <script src="../js/apache-maven-fluido-2.0.0-M11.min.js"></script> |
| </head> |
| <body> |
| <div class="container-fluid container-fluid-top"> |
| <header> |
| <div id="banner"> |
| <div class="pull-left"><div id="bannerLeft"><h1><a href="https://www.apache.org/"><img class="class java.lang.Object" src="https://www.apache.org/images/asf_logo_wide.png" /> Apache Axis2</a></h1></div></div> |
| <div class="pull-right"><div id="bannerRight"><h1><a href="https://axis.apache.org/axis2/java/core/"><img class="class java.lang.Object" src="https://axis.apache.org/axis2/java/core/images/axis.jpg" /></a></h1></div></div> |
| <div class="clear"><hr/></div> |
| </div> |
| |
| <div id="breadcrumbs"> |
| <ul class="breadcrumb"> |
| <li id="publishDate">Last Published: 2026-05-17<span class="divider">|</span> |
| </li> |
| <li id="projectVersion">Version: 2.0.1<span class="divider">|</span></li> |
| <li><a href="https://www.apache.org" class="externalLink">Apache</a><span class="divider">/</span></li> |
| <li><a href="../index.html">Axis2/Java</a><span class="divider">/</span></li> |
| <li class="active">Offset/Limit Pagination for JSON-RPC Services</li> |
| </ul> |
| </div> |
| </header> |
| <div class="row-fluid"> |
| <header id="leftColumn" class="span2"> |
| <nav class="well sidebar-nav"> |
| <ul class="nav nav-list"> |
| <li class="nav-header">Axis2/Java</li> |
| <li><a href="../index.html">Home</a></li> |
| <li><a href="../download.html">Downloads</a></li> |
| <li><a href="javascript:void(0)"><span class="icon-chevron-down"></span>Release Notes</a> |
| <ul class="nav nav-list"> |
| <li><a href="../release-notes/1.6.1.html">1.6.1</a></li> |
| <li><a href="../release-notes/1.6.2.html">1.6.2</a></li> |
| <li><a href="../release-notes/1.6.3.html">1.6.3</a></li> |
| <li><a href="../release-notes/1.6.4.html">1.6.4</a></li> |
| <li><a href="../release-notes/1.7.0.html">1.7.0</a></li> |
| <li><a href="../release-notes/1.7.1.html">1.7.1</a></li> |
| <li><a href="../release-notes/1.7.2.html">1.7.2</a></li> |
| <li><a href="../release-notes/1.7.3.html">1.7.3</a></li> |
| <li><a href="../release-notes/1.7.4.html">1.7.4</a></li> |
| <li><a href="../release-notes/1.7.5.html">1.7.5</a></li> |
| <li><a href="../release-notes/1.7.6.html">1.7.6</a></li> |
| <li><a href="../release-notes/1.7.7.html">1.7.7</a></li> |
| <li><a href="../release-notes/1.7.8.html">1.7.8</a></li> |
| <li><a href="../release-notes/1.7.9.html">1.7.9</a></li> |
| <li><a href="../release-notes/1.8.0.html">1.8.0</a></li> |
| <li><a href="../release-notes/1.8.1.html">1.8.1</a></li> |
| <li><a href="../release-notes/1.8.2.html">1.8.2</a></li> |
| <li><a href="../release-notes/2.0.0.html">2.0.0</a></li> |
| <li><a href="../release-notes/2.0.1.html">2.0.1</a></li> |
| </ul></li> |
| <li><a href="../modules/index.html">Modules</a></li> |
| <li><a href="../tools/index.html">Tools</a></li> |
| <li class="nav-header">Documentation</li> |
| <li><a href="../docs/toc.html">Table of Contents</a></li> |
| <li><a href="../docs/installationguide.html">Installation Guide</a></li> |
| <li><a href="../docs/quickstartguide.html">QuickStart Guide</a></li> |
| <li><a href="../docs/userguide.html">User Guide</a></li> |
| <li><a href="../docs/jaxws-guide.html">JAXWS Guide</a></li> |
| <li><a href="../docs/pojoguide.html">POJO Guide</a></li> |
| <li><a href="../docs/spring.html">Spring Guide</a></li> |
| <li><a href="../docs/webadminguide.html">Web Administrator's Guide</a></li> |
| <li><a href="../docs/migration.html">Migration Guide (from Axis1)</a></li> |
| <li class="nav-header">Resources</li> |
| <li><a href="../faq.html">FAQ</a></li> |
| <li><a href="https://github.com/apache/axis-axis2-java-core" class="externalLink">Source Code</a></li> |
| <li class="nav-header">Get Involved</li> |
| <li><a href="../overview.html">Overview</a></li> |
| <li><a href="../mail-lists.html">Mailing Lists</a></li> |
| <li><a href="../release-process.html">Release Process</a></li> |
| <li><a href="../guidelines.html">Developer Guidelines</a></li> |
| <li><a href="../siteHowTo.html">Build the Site</a></li> |
| <li class="nav-header">Project Information</li> |
| <li><a href="https://github.com/apache/axis-axis2-java-core/graphs/contributors" class="externalLink">Contributors</a></li> |
| <li><a href="https://issues.apache.org/jira/projects/AXIS2/issues" class="externalLink">Issues</a></li> |
| <li class="nav-header">Apache</li> |
| <li><a href="https://www.apache.org/licenses/LICENSE-2.0.html" class="externalLink">License</a></li> |
| <li><a href="https://www.apache.org/foundation/sponsorship.html" class="externalLink">Sponsorship</a></li> |
| <li><a href="https://www.apache.org/foundation/thanks.html" class="externalLink">Thanks</a></li> |
| <li><a href="https://www.apache.org/security/" class="externalLink">Security</a></li> |
| </ul> |
| </nav> |
| <div class="well sidebar-nav"> |
| <div id="poweredBy"> |
| <div class="clear"></div> |
| <div class="clear"></div> |
| <a href="https://maven.apache.org/" class="builtBy" target="_blank"><img class="builtBy" alt="Built by Maven" src="../images/logos/maven-feather.png" /></a> |
| </div> |
| </div> |
| </header> |
| <main id="bodyColumn" class="span10"> |
| <html xmlns="http://www.w3.org/1999/xhtml"> |
| |
| |
| <section><a id="Offset.2FLimit_Pagination_for_JSON-RPC_Services"></a> |
| <h1 id="overview">Offset/Limit Pagination for JSON-RPC Services</h1> |
| |
| |
| <p>Axis2 provides a generic pagination framework for JSON-RPC services |
| backed by SQL databases. The two classes — |
| <code>PaginationRequest</code> and <code>PaginatedResponse<T></code> — |
| map directly to JPA/Hibernate's <code>setFirstResult(offset)</code> and |
| <code>setMaxResults(limit)</code> pattern.</p> |
| |
| |
| <p><strong>Package:</strong> <code>org.apache.axis2.json.rpc</code></p> |
| |
| <section><a id="Wire_Format"></a> |
| <h2 id="wire_format">Wire Format</h2> |
| |
| |
| <p>A paginated response wraps the result list with metadata:</p> |
| |
| |
| <pre> |
| { |
| "response": { |
| "data": [ ... ], |
| "pagination": { |
| "offset": 0, |
| "limit": 50, |
| "totalCount": 1247, |
| "hasMore": true |
| } |
| } |
| } |
| </pre> |
| |
| |
| <ul> |
| |
| <li><strong>offset</strong> — zero-based index of the first item in this page</li> |
| |
| <li><strong>limit</strong> — maximum items requested (page size)</li> |
| |
| <li><strong>totalCount</strong> — total items matching the query across all pages</li> |
| |
| <li><strong>hasMore</strong> — true when <code>offset + limit < totalCount</code></li> |
| </ul> |
| |
| </section><section><a id="Service_Integration"></a> |
| <h2 id="service_integration">Service Integration</h2> |
| |
| |
| <p>A typical service method delegates offset/limit to the DAO:</p> |
| |
| |
| <pre> |
| public PaginatedResponse<Product> findProducts(ProductQuery query) { |
| List<Product> items = dao.findList(query.getOffset(), query.getLimit()); |
| long total = dao.count(query); |
| return PaginatedResponse.of(items, query.getOffset(), query.getLimit(), total); |
| } |
| </pre> |
| |
| |
| <p>The request POJO can embed <code>PaginationRequest</code> fields directly |
| or accept them as separate parameters:</p> |
| |
| |
| <pre> |
| // Client sends: |
| { |
| "searchTerm": "AAPL", |
| "offset": 100, |
| "limit": 50 |
| } |
| </pre> |
| |
| <section><a id="Unpaginated_Responses"></a> |
| <h3>Unpaginated Responses</h3> |
| |
| |
| <p>For small lookup tables (e.g., a list of 15 departments), use the |
| convenience factory to wrap the full list with <code>hasMore=false</code>:</p> |
| |
| |
| <pre> |
| return PaginatedResponse.unpaginated(departments); |
| // → offset=0, limit=15, totalCount=15, hasMore=false |
| </pre> |
| |
| </section></section><section><a id="Safety.3A_maxLimit_Clamping_and_Input_Validation"></a> |
| <h2 id="safety">Safety: maxLimit Clamping and Input Validation</h2> |
| |
| |
| <p><code>PaginationRequest</code> enforces safety constraints at the getter level:</p> |
| |
| |
| <table class="bodyTableBorder"> |
| |
| <tr class="a"> |
| <th>Input</th> |
| <th>Behavior</th></tr> |
| |
| <tr class="b"> |
| <td><code>offset < 0</code></td> |
| <td>Clamped to 0</td></tr> |
| |
| <tr class="a"> |
| <td><code>limit <= 0</code></td> |
| <td>Default: 50</td></tr> |
| |
| <tr class="b"> |
| <td><code>limit > maxLimit</code></td> |
| <td>Capped at maxLimit (default: 2000)</td></tr> |
| </table> |
| |
| |
| <p>Services that handle expensive entities can lower the cap per-operation:</p> |
| |
| |
| <pre> |
| // Large text fields — cap at 100 per page |
| request.setMaxLimit(100); |
| int safeLimit = request.getLimit(); // capped at 100 |
| </pre> |
| |
| </section><section><a id="Frontend_Patterns"></a> |
| <h2 id="frontend_patterns">Frontend Patterns</h2> |
| |
| <section><a id="Page_Controls_.28.22Showing_151.E2.80.93200_of_1.2C247.22.29"></a> |
| <h3>Page Controls ("Showing 151–200 of 1,247")</h3> |
| |
| <pre> |
| // JavaScript / TypeScript |
| const { offset, limit, totalCount } = pagination; |
| const currentPage = Math.floor(offset / limit) + 1; |
| const totalPages = Math.ceil(totalCount / limit); |
| const showingFrom = offset + 1; |
| const showingTo = offset + data.length; |
| </pre> |
| |
| </section><section><a id="Virtual_Scroll_.2F_Infinite_Scroll"></a> |
| <h3>Virtual Scroll / Infinite Scroll</h3> |
| |
| <pre> |
| // Load next chunk when user scrolls |
| const nextOffset = pagination.offset + pagination.limit; |
| if (pagination.hasMore) { |
| fetchPage(nextOffset, pagination.limit); |
| } |
| </pre> |
| |
| </section><section><a id="Grid_startRow.2FendRow_Translation"></a> |
| <h3>Grid startRow/endRow Translation</h3> |
| |
| <pre> |
| // Grid sends startRow=300, endRow=350 |
| // Service translates: offset = startRow, limit = endRow - startRow |
| int offset = startRow; |
| int limit = endRow - startRow; |
| </pre> |
| |
| </section></section><section><a id="Why_Offset.2FLimit_Instead_of_Cursor"></a> |
| <h2>Why Offset/Limit Instead of Cursor</h2> |
| |
| |
| <ul> |
| |
| <li><strong>DAO compatibility</strong> — existing Hibernate/JPA DAOs use |
| <code>query.setFirstResult(offset)</code> and <code>query.setMaxResults(limit)</code>. |
| Cursor pagination requires a stable sort key and stateful server-side tokens.</li> |
| |
| <li><strong>Frontend grids</strong> — data grids (AG Grid, React Table, etc.) natively |
| speak offset/limit via <code>startRow</code>/<code>endRow</code> or |
| <code>page</code>/<code>pageSize</code>.</li> |
| |
| <li><strong>totalCount</strong> — enables "Showing 1–50 of 1,247" UI patterns |
| and page-count calculations. Cursor APIs typically omit total counts because |
| they are expensive for the cursor model, but they are cheap when the DAO |
| already runs <code>SELECT COUNT(*)</code>.</li> |
| </ul> |
| |
| </section><section><a id="Test_Coverage"></a> |
| <h2>Test Coverage</h2> |
| |
| |
| <p>The <code>PaginatedResponseTest</code> class provides 20 tests covering:</p> |
| |
| <ul> |
| |
| <li>First page, last page, partial last page, single page, empty result</li> |
| |
| <li>Null data treated as empty list</li> |
| |
| <li>Unpaginated convenience factory</li> |
| |
| <li>Negative offset clamping, zero/negative limit defaults, maxLimit enforcement</li> |
| |
| <li>Enterprise scenarios: 8,543-item virtual scroll, soft-delete filtering, |
| service-specific maxLimit, grid startRow/endRow translation</li> |
| |
| <li>Request → response round-trip simulation</li> |
| </ul> |
| |
| </section> |
| </html> </main> |
| </div> |
| </div> |
| <hr/> |
| <footer> |
| <div class="container-fluid"> |
| <div class="row-fluid"> |
| <p>© 2004–2026 |
| <a href="https://www.apache.org/">The Apache Software Foundation</a> |
| </p> |
| </div> |
| </div> |
| </footer> |
| </body> |
| </html> |