Apex Cursors Explained for Large SOQL Processing
Introduction
Ever hit a SOQL limit trying to process millions of records in Salesforce Apex? You’re not alone.
Processing large datasets in Apex used to mean complex Batch Apex jobs or work-arounds with OFFSET pagination — often slow, clunky, and full of governor limit hurdles.
But now, Apex Cursors are here to change the game.
In this article, you’ll learn:
- What Apex cursors are and how they work
- Why they matter for large SOQL processing
- Real limits you must watch out for
- How they compare to Batch Apex
- Simple code examples, best practices, and FAQs
Table of Contents
- What Are Apex Cursors?
- Why Apex Cursors Matter in Large SOQL Queries
- How Apex Cursors Work (Simple Flow + Example)
- Key Limits (including apex cursors limit)
- Apex Cursors vs Batch Apex (Quick Comparison)
- Best Practices for Large SOQL Processing
- FAQs (Common Questions)
- Conclusion & Next Steps
What Are Apex Cursors?
Apex Cursors are pointers to a SOQL query result, not the full result itself. Instead of retrieving all rows at once, a cursor lets you fetch smaller chunks of records on demand — just like scrolling through pages in a long book without loading the entire book at once.
This makes Apex cursors ideal for processing large datasets efficiently with controlled memory and less risk of running into platform limits.
👉 Imagine trying to load a million rows into memory all at once — dangerous for Apex! A cursor avoids that by fetching only what you need.
Why Apex Cursors Matter in Large SOQL Queries
Traditionally, developers used Batch Apex or SOQL with OFFSET to handle big data. But:
- OFFSET stops working effectively past 2,000 rows
- Batch Apex introduces overhead and rigid structures
- Memory and governor limits still bite you
Apex Cursors solve these because they:
✔ Keep memory usage low
✔ Let you control fetch size
✔ Work well with Queueable Apex
✔ Can process up to 50 million rows per cursor

Read this Salesforce Documentation on apex cursor here.
How Apex Cursors Work — Simple Flow + Example
Typical Cursor Flow
- Create a cursor using
Database.getCursor() - Fetch chunks of data using
cursor.fetch(position, count) - Track position yourself (cursor doesn’t track it internally)
- Repeat until done
Real-life analogy: Think of a movie buffer streaming only 10 seconds at a time instead of downloading the whole movie first.
Sample Apex Cursor Code
Database.Cursor cursor = Database.getCursor(
'SELECT Id, Name FROM Account ORDER BY CreatedDate'
);
Integer position = 0;
Integer batchSize = 500;
while(true) {
// Fetch next batch of records
List<SObject> records = cursor.fetch(position, batchSize);
if (records.isEmpty()) {
break;
}
// Process the records
for (SObject r : records) {
// your logic here
}
position += batchSize;
}
Note: You must manage the position (index) yourself.
Key Limits You Must Know (aka apex cursors limit)
Salesforce imposes limits to protect platform performance. These are important if you work with large SOQL operations.
| Limit Type | Value |
|---|---|
| Max rows per cursor | 50 million records |
| Max fetch calls per transaction | 10 fetch calls |
| Max cursors per day | 10,000 |
| Total rows fetched per day | 100 million |
| Fetched rows count toward SOQL limits | Yes (counts like regular SOQL) |
Quick take: You can handle huge result sets — but you still must design your logic to stay within these limits. For example, each .fetch() counts toward your SOQL query budget.
Apex Cursors vs Batch Apex — Quick Comparison
| Feature | Batch Apex | Apex Cursors |
|---|---|---|
| Structure | Fixed (Start → Execute → Finish) | Flexible |
| Scalability | Good | Excellent (up to 50M rows) |
| Memory Use | Higher | Lower |
| Position Tracking | Managed by framework | You manage it |
| Use Case | Standard bulk async jobs | High-control large processing |
When to choose which?
- Use Batch Apex for scheduled, high-volume jobs with simple needs.
- Use Apex Cursors when you need fine-grained, controlled chunking, or advanced navigation through results.
Best Practices for Large SOQL Processing
✔ Always use cursors with Queueable Apex or async logic for extended processing.
✔ Avoid large .fetch() sizes — keep them manageable (500–2,000 records).
✔ Use Indexes and selective filters in your SOQL to avoid performance bottlenecks.
✔ Track your cursor positions persistently if needed across transactions.
✔ Handle exceptions like System.TransientCursorException and retry safely.
FAQs
1. What is the difference between Apex Cursors and Pagination Cursors?
Apex Cursors are for server-side data processing, while Pagination Cursors are optimized for UI pagination with consistent page sizes even when records change.
2. Do Apex Cursors load all records into memory?
No — they fetch only the requested chunk. The cursor itself maintains a pointer and does not store the entire dataset in heap.
3. Can Apex Cursors replace Batch Apex entirely?
Not always. Batch Apex is still great for scheduled bulk operations. Cursors shine when you want custom control over data navigation and chunk sizes.
4. Do cursor fetch calls count toward SOQL limits?
Yes — both fetch calls and rows returned count toward your SOQL governor limits.
5. What happens if a fetch fails?
Salesforce can throw exceptions like System.TransientCursorException — indicating that the failure is retryable. Handle these with safe retry logic.
Conclusion
Apex cursors are a powerful new way to process large SOQL results in Salesforce — giving developers the ability to chunk query results efficiently without overwhelming memory or hitting hard limits. Used with Queueable Apex or in custom large-data flows, cursors can dramatically simplify and scale your logic.