Process millions of records in controlled chunks — no start/execute/finish lifecycle required
If you've hit the SOQL row limit trying to process large datasets, your first instinct was probably Batch Apex. It works, but it comes with overhead — the start/execute/finish lifecycle, limited chaining, and fixed chunk sizes. Apex Cursors offer a lighter, more flexible alternative. Here's how they work and when to use them.
What Are Apex Cursors?
According to the official Apex Developer Guide, Apex cursors let you break up the processing of a SOQL query result into pieces that can be processed within the bounds of a single transaction. They give you the ability to work with large query result sets without actually returning the entire result set at once.
In practice: you run a SOQL query, get back a cursor pointing to the full result set, then pull records out in small batches — 200 at a time, 500 at a time, whatever fits your use case.
The Two Methods You Need to Know
You create a cursor using Database.getCursor() and retrieve records using cursor.fetch(). The cursor.getNumRecords() method tells you the total record count so you know when to stop.
For dynamic queries with bind variables, use Database.getCursorWithBinds() instead, which accepts a query string and a map of bind variables.
Real-World Example: Queueable + Cursor
Cursors are designed to be used with Queueable Apex. You store the cursor and current position as instance variables, process a batch in each execute() call, and re-enqueue the job if records remain. The official Salesforce Developer Guide shows exactly this pattern:
position variable. Each time execute() runs, you advance position by the number of records returned, then pass it back into fetch() on the next call.Cursor vs. Batch Apex — When to Use Which
| Scenario | Use Cursor | Use Batch Apex |
|---|---|---|
| Need to chain jobs | ✅ Yes — works with Queueable chaining | ❌ Batch chaining is limited |
| Need flexible chunk sizes | ✅ Yes — you control the fetch count | ❌ Fixed scope size |
| Simple fire-and-forget processing | ⚠️ More setup required | ✅ Simpler lifecycle |
| Need start/finish callbacks | ❌ Not available | ✅ Built in |
| High-volume async processing | ✅ Up to 50M rows per cursor | ✅ Also handles large volumes |
Governor Limits to Know
Apex Cursors have their own set of limits, separate from standard SOQL limits:
- Maximum rows per cursor: 50 million (sync and async)
- Maximum fetch calls per transaction: 10
- Maximum cursors per day: 10,000
- Maximum rows per day (aggregate): 100 million
You can monitor usage in code using the new Limits class methods: Limits.getApexCursorRows(), Limits.getLimitApexCursorRows(), Limits.getFetchCallsOnApexCursor(), and Limits.getLimitFetchCallsOnApexCursor().
Gotchas to Watch Out For
cursor.fetch() counts against the limit of 10 fetch calls per transaction. Design your chunk size accordingly — fetching 200 records per call means you can process up to 2,000 records per transaction before needing to re-enqueue.position variable between enqueue calls, you'll start over from 0. Make sure position is an instance variable on your Queueable class, not a local variable.System.TransientCursorException indicates a temporary issue. The Apex Developer Guide notes this transaction can be retried — build retry logic into your Queueable if you're processing critical data.Summary & Key Takeaways
- Create with
Database.getCursor(query)orDatabase.getCursorWithBinds(query, binds) - Fetch records with
cursor.fetch(position, count)— you manage the position offset - Check total records with
cursor.getNumRecords() - Designed to be used with Queueable Apex for chained async processing
- Up to 50M rows per cursor, 10 fetch calls per transaction, 100M rows per day
- Cursor results expire after 2 days
- Monitor limits via
Limits.getApexCursorRows()andLimits.getFetchCallsOnApexCursor()
Apex Cursors sit nicely between synchronous SOQL and the full Batch Apex lifecycle. If you find yourself writing Batch Apex just to loop through a large dataset and process it in chunks — and you don't need the start/finish callbacks — Cursors with Queueable are worth looking at.
📄 Sources: Apex Cursors — Apex Developer Guide | API Query Cursor Limits — Salesforce Developer Limits | Summer '24 Developer Guide — Salesforce Developer Blog


0 Comments