Implementing Offset and Cursor Pagination in Harvester Query Interfaces
Learn how to paginate large Harvester datasets safely using offset‑based or cursor‑based methods, with steps to configure page size, persist state, and recover from failures.
21 May 2026, 14:51 UTC

Desired Outcome
Implement a pagination mechanism that lets a Java‑based Harvester client retrieve all rows from a large table without exceeding memory limits or introducing duplicate/missing records.
Prerequisites
- A Harvester endpoint that accepts
limitandoffset(or a cursor filter) parameters. - A column that is unique and indexed (e.g.,
record_idBIGINT) to serve as a cursor. - A persistent store (file, database table, or distributed cache) to save the last processed offset or cursor between runs.
- Java 8+ with a HTTP client (e.g., HttpURLConnection or Apache HttpComponents) and JSON parsing library.
Strategy 1 – Offset‑Based Pagination
Procedure
- Choose a page size that fits comfortably in memory (e.g.,
pageSize = 500). - Initialize
offset = 0. - Build the request URL:
${HARVESTER_ENDPOINT}?limit=${pageSize}&offset=${offset}. - Execute the GET request with read‑only credentials.
- Parse the JSON array; if the array length is zero, stop.
- Process each record as needed by your application.
- Update the offset:
offset += pageSizeand persist the new value. - Repeat from step 3.
Expected Checks
- After each page, confirm that the number of returned records ≤
pageSize. - When the returned count is
pageSize, continue; when it is less, you have reached the end. - Optionally, keep a running total of records and compare it to a separate count query (
SELECT COUNT(*) FROM table) after the harvest finishes.
Recovery
If the process crashes, read the persisted offset value and resume the loop from that offset. Because offset‑based pagination assumes a static dataset, verify that no inserts or deletes occurred in the already‑processed range; if the source table is volatile, prefer the cursor method.
Strategy 2 – Cursor‑Based Pagination
Procedure
- Identify a monotonic, unique column (e.g.,
record_id) and ensure it is indexed. - Set an initial cursor value lower than any possible key (commonly
0or the minimum value). - Build the request:
${HARVESTER_ENDPOINT}?limit=${pageSize}&cursor=${lastSeenId}where the Harvester interprets cursor asWHERE record_id > ${lastSeenId}. - Execute the request with read‑only access.
- Parse the response; if empty, stop.
- Process each record.
- Extract the
record_idof the last record in the batch and persist it as the new cursor. - Repeat from step 3.
Expected Checks
- Verify that the
record_idof the first record in a page is strictly greater than the cursor used to request that page. - Ensure no duplicate
record_idappears across pages by keeping a Set of seen IDs (optional for small validation runs). - When a page returns fewer than
pageSizerecords, the harvest is complete.
Recovery
On failure, read the persisted cursor (lastSeenId) and restart the loop. Because the cursor always points to the last successfully processed key, inserts that occur with lower IDs will not be missed, and inserts with higher IDs will be picked up in subsequent pages, eliminating skip/duplicate risk.
Validation Example (offset‑based)
To confirm correct behavior on a test dataset of 2 350 rows:
- Set
pageSize = 500. - Run the offset loop and record the offset after each iteration: 0, 500, 1000, 1500, 2000, 2500.
- Observe that the fifth page returns 350 rows (less than pageSize) and the sixth page returns zero.
- Sum the rows returned per page (500+500+500+500+350) = 2 350, matching the known total.
- No offset value is repeated, indicating no overlap.
For cursor‑based validation, persist the last record_id after each page and re‑run the query with that cursor; the result set should start with the next higher record_id and contain no overlap with the previous batch.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.