Apex Cursors (Beta)

Use Apex cursors to break up the processing of a SOQL query result into pieces that can be processed within the bounds of a single transaction. Cursors provide you with the ability to work with large query result sets, while not actually returning the entire result set. You can traverse a query result in parts, with the flexibility to navigate forward and back in the result set. Package developers and advanced developers can use cursors effectively to work with high-volume and high-resource processing jobs. Cursors combined with chained queueable Apex jobs are a powerful alternative to batch Apex and address some of batch Apex’s limitations.

This feature is a Beta Service. Customer may opt to try such Beta Service in its sole discretion. Any use of the Beta Service is subject to the applicable Beta Services Terms provided at Agreements and Terms. You can provide feedback and suggestions for the feature in the Trailblazer Community.

Note

Apex cursors are stateless and generate results from the offset position that is specified in the Cursor.fetch(integer position, integer count) method. You must track the offsets or positions of the results within your particular processing scenario.

A cursor is created when a SOQL query is executed on a Database.getCursor() or Database.getCursorWithBinds() call. When a Cursor.fetch(integer position, integer count) method is invoked with an offset position and the count of records to fetch, the corresponding rows are returned from the cursor. The maximum number of rows per cursor is 50 million, regardless of the operation being synchronous or asynchronous. To get the number of cursor rows returned from the SOQL query, use Cursor.getNumRecords().

Apex cursors throw these new System exceptions: System.FatalCursorException and System.TransientCursorException. Transactions that fail with System.TransientCursorException can be retried.

Apex cursors have the same expiration limits as API Query cursors. See API Query Cursor Limits.

To get Apex cursor limits, use these new methods in the Limits class.

  • Limits.getApexCursorRows() and its upper bound Limits.getLimitApexCursorRows() method
  • Limits.getFetchCallsOnApexCursor() and its upper bound Limits.getLimitFetchCallsOnApexCursor() method

Apex Cursor Example

public class QueryChunkingQueuable implements Queueable {
    private Database.Cursor locator;
    private integer position;

    public QueryChunkingQueuable() {
        locator = Database.getCursor
                  ('SELECT Id FROM Contact WHERE LastActivityDate = LAST_N_DAYS:400');
        position = 0;
    }

    public void execute(QueueableContext ctx) {
        List<Contact> scope = locator.fetch(position, 200);
        position += scope.size();
        // do something, like archive or delete the scope list records
        if(position < locator.getNumRecords() ) {
            // process the next chunk
            System.enqueueJob(this);
        }
    }
}