Preventing Data Gaps in Large Jira API Exports
Avoid data gaps and duplicates when exporting large Jira issue sets. Learn how to use deterministic JQL ordering and efficient pagination to ensure reliable API results.
21 Sept 2025, 07:48 UTC

The Pagination Trap
When extracting thousands of issues from Jira via the REST API, a simple request to /rest/api/3/search rarely returns the full dataset. Most developers implement a basic loop using startAt and maxResults, but without a strict ordering strategy, these exports often suffer from "ghost issues"—records that are skipped or duplicated as the result set shifts during the paging process.
The core problem is that Jira's default search order is not guaranteed. If a new issue is created or an existing one is updated while your script is moving from page one to page two, the index shifts. This can push an issue from the first page (which you've already processed) to the second page, causing a duplicate, or push it off the second page entirely, causing a data gap.
Ensuring Deterministic Results
To prevent data drift, every JQL (Jira Query Language) query used for pagination must include a deterministic ORDER BY clause. Ordering by a unique, immutable field ensures that the position of an issue remains constant regardless of concurrent updates to the project.
Ordering by key ASC is the safest choice. The issue key is indexed and unique, minimizing the performance hit on the Jira database while ensuring that the sequence of results is identical across every API call in your loop.
Optimizing Payload and Performance
Large exports often fail not because of the number of issues, but because of the size of the JSON response. By default, Jira returns a wide array of system fields that you likely don't need for a data migration or report.
- Field Filtering: Use the
fieldsparameter to request only the specific keys you need (e.g.,summary,status,assignee). This reduces memory consumption and lowers the risk of request timeouts. - Dynamic Total Tracking: Do not hard-code the number of pages. Use the
totalfield returned in the first response to calculate the loop boundary. This allows your script to adapt to the actual size of the result set at the moment of execution.
Implementation Example: Paged Search Loop
This logic should be executed by a service account or user with "Browse Projects" permissions for the target project. Run this via a script (Python, Node.js) targeting your Jira Cloud or Data Center instance.
# Conceptual Logic for Paged Export
# Parameters
JQL = "project = 'PROJ' ORDER BY key ASC"
MAX_RESULTS = 50 # Check your instance limit; typically 50-100
START_AT = 0
ALL_ISSUES = []
while True:
# API Request: GET /rest/api/3/search?
# jql={JQL}&startAt={START_AT}&maxResults={MAX_RESULTS}&fields=summary,status
response = call_jira_api(jql=JQL, startAt=START_AT, maxResults=MAX_RESULTS)
data = response.json()
issues = data.get('issues', [])
total = data.get('total', 0)
ALL_ISSUES.extend(issues)
# Break loop when we have collected all issues
if len(ALL_ISSUES) >= total:
break
START_AT += MAX_RESULTS
Trade-offs and Limitations
While index-based pagination is the standard, it has a significant limitation: Deep Paging Performance. As the startAt value increases (e.g., requesting the 10,000th issue), Jira must scan through all preceding records to find the offset. This can lead to increased latency or 504 Gateway Timeouts on very large datasets.
If you encounter timeouts on deep pages, the most practical workaround is to narrow the JQL window. Instead of one massive request, split the export into smaller time-based chunks using the created field (e.g., one request per month of data). This keeps the startAt values low and the responses snappy.
Verification and Safety
To verify your implementation, run a test against a project with a known issue count. Compare the total value from the first API response against the final length of your collected list. If the numbers differ, or if you find duplicate keys in your list, verify that your ORDER BY key ASC clause is being passed correctly in the JQL string.
Rollback: Because this is a read-only GET operation, there is no state change to roll back. However, be mindful of rate limits; if you receive a 429 Too Many Requests response, implement an exponential backoff (pause and retry) to avoid being temporarily blocked by the Atlassian API gateway.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.