blob: 6e11aafb99dfb5b3cde41fd46344db7e5928d122 [file]
<!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&apos;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 &#x2014;
<code>PaginationRequest</code> and <code>PaginatedResponse&lt;T&gt;</code> &#x2014;
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>
{
&quot;response&quot;: {
&quot;data&quot;: [ ... ],
&quot;pagination&quot;: {
&quot;offset&quot;: 0,
&quot;limit&quot;: 50,
&quot;totalCount&quot;: 1247,
&quot;hasMore&quot;: true
}
}
}
</pre>
<ul>
<li><strong>offset</strong> &#x2014; zero-based index of the first item in this page</li>
<li><strong>limit</strong> &#x2014; maximum items requested (page size)</li>
<li><strong>totalCount</strong> &#x2014; total items matching the query across all pages</li>
<li><strong>hasMore</strong> &#x2014; true when <code>offset + limit &lt; 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&lt;Product&gt; findProducts(ProductQuery query) {
List&lt;Product&gt; 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:
{
&quot;searchTerm&quot;: &quot;AAPL&quot;,
&quot;offset&quot;: 100,
&quot;limit&quot;: 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);
// &#x2192; 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 &lt; 0</code></td>
<td>Clamped to 0</td></tr>
<tr class="a">
<td><code>limit &lt;= 0</code></td>
<td>Default: 50</td></tr>
<tr class="b">
<td><code>limit &gt; 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 &#x2014; 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 (&quot;Showing 151&#x2013;200 of 1,247&quot;)</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> &#x2014; 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> &#x2014; 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> &#x2014; enables &quot;Showing 1&#x2013;50 of 1,247&quot; 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 &#x2192; 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>