Manage Large Data Volumes In Salesforce

Blog > Salesforce Apex Cursor API

How to Use Apex Cursor API to process Millions of Records in Salesforce

Salesforce has finally solved one of the biggest developer challenges: How to process millions of records efficiently without Batch Apex complexity.

Salesforce Spring'26 introduced a powerful feature called Apex Cursors, designed to handle massive SOQL datasets (millions of records) without hitting governor limits or overloading memory. Lets learn how using Apex Cursors you can now process up to 50 million records in a controlled, scalable, and flexible way.


salesforce apex cursor

What are Apex Cursors?

An Apex Cursor is a server-side reference (pointer) to a SOQL result set, not the data itself. The Apex Cursor API allows developers to execute large SOQL queries and iterate over the results incrementally across transactions. Instead of loading all records at once: It fetches small chunks of data on demand.

Unlike standard SOQL queries, it:


  • Fetches data in parts instead of all at once
  • Isn’t restricted by the 50K row limit
  • Keeps track of progress between executions

Because of this, Apex Cursors are ideal for handling large datasets where normal SOQL queries or collections would run into limits.


Why not simply use Batch Apex instead?

Batch Apex has been the go-to solution for processing large datasets in Salesforce for years. But while it’s powerful, it comes with limitations that make it less ideal for modern, high-control data processing.

  • Fixed batch size
  • Less flexibility in navigation
  • More overhead for some use cases

How Apex Cursors Work ?

  • Execute a SOQL query using Database.getCursor() or Database.getCursorWithBinds()
  • Salesforce returns a cursor reference instead of actual records
  • Use Cursor.fetch(position, count) to retrieve a subset of data
  • Maintain the current position manually
  • Repeat the fetch process until all records are processed

Apex Class — Cursor Processor (Main Logic)

OpportunityCursorProcessor.cls


public class OpportunityCursorProcessor implements Queueable, Database.AllowsCallouts {

    private Cursor oppCursor;
    private static final Integer BATCH_SIZE = 500;

    // Constructor
    public OpportunityCursorProcessor(Cursor cursor) {
        this.oppCursor = cursor;
    }

    public void execute(QueueableContext context) {
        try {
            // Fetch next chunk
            List opps =
                (List) oppCursor.fetch(BATCH_SIZE);

            if (!opps.isEmpty()) {
                processOpportunities(opps);
            }

            // Re-enqueue if cursor still has data
            if (!oppCursor.isDone()) {
                System.enqueueJob(new OpportunityCursorProcessor(oppCursor));
            }
        } catch (Exception e) {
            logError(e);
            throw e; // Fail fast so admins can retry safely
        }
    }

    // Business logic method (easy to extend)
    private void processOpportunities(List opps) {
        for (Opportunity opp : opps) {
            // Example logic
            if (opp.Amount != null) {
                opp.Custom_Rollup_Amount__c = opp.Amount * 0.95;
            }
        }
        update opps;
    }

    // Centralized error logging
    private void logError(Exception e) {
        System.debug('Cursor Processing Error: ' + e.getMessage());
        // Optional: Insert into a Custom Error Log object
    }
}

Apex Class — Job Starter (Admin / On-Demand)

This class creates the cursor and starts the processing.
OpportunityCursorJobStarter.cls

  
    public class OpportunityCursorJobStarter {

    public static void startJob() {
        Cursor oppCursor = Database.createCursor(
            'SELECT Id, Amount, StageName, CloseDate ' +
            'FROM Opportunity ' +
            'WHERE IsClosed = true'
        );

        System.enqueueJob(new OpportunityCursorProcessor(oppCursor));
    }
}
    
  • SOQL runs once
  • Cursor handles millions of records
  • No row limits

Cursor-Specific Exceptions

Apex cursors introduce two new system exceptions:

System.FatalCursorException
Non-recoverable error. The transaction fails.

System.TransientCursorException
Temporary issue. The transaction can be retried safely.

  • Remove duplicate records
  • Delete test data
  • Remove obsolete records
  • Clean regularly

Wrap Up

The Apex Cursor API is Salesforce’s next-generation alternative to Batch Apex. As Salesforce continues modernizing its async capabilities, Cursor API is a must-learn tool for advanced Salesforce developers and architects.

Crafting Tomorrow's Solutions Today.

Got something on your mind! Connect with us.

Fill out the following form & we will get back to you.