Apex Cursor and Its Advantages Over Batch Processing

by Sudipta Deb | Jan 15, 2026 | Advanced Apex, Apex, Asynchronous Apex, Salesforce, Salesforce Release, SOQL, Spring 26 Salesforce Release | 0 comments

Sudipta Deb

Sudipta Deb

Founder of Technical Potpourri, Co-Founder of Shrey Tech, Enterprise Cloud Architect

When working with large datasets in Salesforce, developers often face a critical decision: should they use traditional batch processing or leverage the power of Apex Cursor? While batch processing has been the go-to solution for years, Apex Cursor introduces a new paradigm that offers significant advantages in specific scenarios. In this post, we'll explore what Apex Cursor is, how it differs from batch processing, and when you should choose one approach over the other.

Watch The Video Or Read The Article (Or Do Both smile)

The form you have selected does not exist.

What is Apex Cursor?

Apex Cursor is a feature that allows developers to efficiently iterate through large query results without loading all records into memory at once. It provides a streaming approach to data processing, where records are fetched incrementally as needed. This is particularly useful when dealing with queries that return millions of records that would otherwise exceed governor limits or cause memory issues.

The key characteristic of Apex Cursor is its ability to maintain a pointer to the current position in the query results, fetching batches of records on demand while maintaining the overall query context.

Traditional Batch Processing Overview

Batch Apex has been the standard solution for processing large volumes of data in Salesforce. It divides the workload into manageable chunks (typically 200 records per batch by default) and processes them sequentially. Each batch runs in its own transaction context with its own set of governor limits.

Key characteristics of batch processing include:

  • Chunked execution: Data is processed in discrete batches
  • Separate transactions: Each batch runs in its own transaction
  • Three-phase lifecycle: Start, Execute, and Finish methods
  • Asynchronous processing: Batches run in the background
  • Job monitoring: Built-in monitoring through the Apex Jobs interface

Key Advantages of Apex Cursor

1. Memory Efficiency

Apex Cursor excels at memory management by streaming records rather than loading entire result sets into memory. When processing millions of records, traditional approaches might hit heap size limits, but Apex Cursor fetches records incrementally, keeping memory usage consistently low regardless of total dataset size.

2. Reduced Governor Limit Pressure

By processing records in a streaming fashion, Apex Cursor can help avoid certain governor limit issues. Since it doesn't require loading all records at once, you're less likely to hit heap size limits during the query phase. This is particularly valuable when dealing with records that have large field values or complex relationships.

3. Simplified Code for Sequential Processing

When your use case involves sequential processing without complex batch orchestration, Apex Cursor can result in cleaner, more maintainable code. You write a single loop that processes records one by one, without managing the complexity of batch boundaries and the start-execute-finish lifecycle.

4. Real-Time Processing Potential

Unlike batch jobs that run asynchronously in a queue, Apex Cursor can be used in synchronous contexts (within governor limits) for more immediate data processing. This makes it suitable for scenarios where you need to process and respond to large datasets within a single user interaction.

5. Continuous Transaction Context

With Apex Cursor, you maintain a continuous transaction context throughout processing (up to governor limits). This can simplify logic that depends on maintaining state or performing operations that span multiple records without the complexity of batch-to-batch communication.

With this new component, you can now list down only accepted file formats, whether to allow multiple file upload, File Field Name (Custom Field Name of Content Version object), Record Id (related to the files being uploaded), URL to navigate to after successful upload etc. 

When to Use Apex Cursor vs Batch Processing

Choose Apex Cursor When:

  • You need to process records sequentially in a streaming fashion
  • Memory efficiency is critical and you're dealing with very large datasets
  • Your processing logic is straightforward and doesn't require complex batch orchestration
  • You want to avoid the overhead of batch job queuing and scheduling
  • You need to maintain continuous state throughout processing
  • Your use case fits within transaction timeout limits

Choose Batch Processing When:

  • You need guaranteed asynchronous execution with job monitoring
  • Your processing logic requires parallel execution across multiple batches
  • You need clear separation between batches for error handling and recovery
  • You want built-in retry mechanisms and job failure handling
  • Your processing involves callouts or operations that require separate transaction contexts
  • You need to schedule regular, recurring processing jobs
  • Processing time will exceed transaction timeout limits

Performance Considerations

While Apex Cursor offers compelling advantages, it's important to understand the performance trade-offs. Batch processing provides better isolation and can be more resilient to partial failures since each batch is independent. If one batch fails, others can still succeed. With Apex Cursor, a failure during processing could require restarting the entire operation.

Additionally, batch processing can sometimes be faster for very large datasets because multiple batches can be processed in parallel by the Salesforce platform infrastructure, whereas Apex Cursor processing is typically sequential.

Code Example: Apex Cursor in Action

public with sharing class QueryAllContactsUsingCursor implements Queueable {
 private Database.Cursor locator;
 private Integer position;  // Tracks the current processing offset 
 private static final Integer CHUNK_SIZE = 20;
 public QueryAllContactsUsingCursor() { 
 // Step #1: Initialize the cursor
 locator = Database.getCursor('SELECT Id FROM Contact ORDER BY Id');  //Fetching all contacts
 position = 0; 
 } 
 public void execute(QueueableContext ctx) { 
 Integer total = locator.getNumRecords();   //Returns the total number of records that the query will yield.
 Integer remaining = total - position;
 if (remaining <= 0) {
 return; // nothing left to fetch
 }
 Integer count = Math.min(CHUNK_SIZE, remaining);
 List<Contact> scope = locator.fetch(position, count);
 position += scope.size();
 System.debug('Processing ' + scope.size() + ' contacts. position=' + position + ' total=' + total);
 if (position < total) {
 System.enqueueJob(this); // continue with updated state
 }
 } 
}

Following the Spring 26 release, the new Kanban Board component enables the display of records as cards within columns that signify workflow stages, eliminating the need for specialized Lightning implementations. Your users have immediate insight on record advancement and specifics without disrupting the workflow. The Kanban Board is in a read-only format, preventing users from moving cards between stages during runtime.

With this Kanban Board component, you will have the flexibility to configure the columns, how headers will be displayed (simple vs. path), perform grouping at column level, and also implement the summary field. On top of that, you can configure what will be displayed in the individual card, display in the footer etc.

I personally think this is a very powerful addition in Spring 26 release. Though this will only show the data in read-only mode, but I am quite sure in upcoming features, there will be interactivity within the Kanban board.

This is how all the opportunities will look like in Kanban Board grouped by "Opportunity Type" field and summarize the "Amount" field.

Code Example: Apex Cursor With Retry Mechanism

public with sharing class QueryAllContactsUsingCursorWithRetry implements Queueable {
 private static final Integer CHUNK_SIZE = 20;
 private static final Integer MAX_RETRIES = 2;
 // A custom class to hold the necessary state for the job
 private class JobState {
 Database.Cursor locator;
 Integer currentPosition;
 Integer currentRetryCount;
 }
 private JobState state;
 public QueryAllContactsUsingCursorWithRetry() { 
 this.state = new JobState();
 // Step #1: Initialize the cursor
 this.state.locator = Database.getCursor('SELECT Id FROM Contact ORDER BY Id'); 
 this.state.currentPosition = 0;
 this.state.currentRetryCount = 0;
 } 
 public void execute(QueueableContext ctx) { 
 try{
 Integer total = this.state.locator.getNumRecords();   //Returns the total number of records that the query will yield.
 Integer remaining = total - this.state.currentPosition;
 if (remaining <= 0) {
 return; // nothing left to fetch
 }
 Integer count = Math.min(CHUNK_SIZE, remaining);
 List<Contact> scope = this.state.locator.fetch(this.state.currentPosition, count);
 if (!scope.isEmpty()) {
 processRecords(scope);
 this.state.currentPosition += scope.size();
 System.debug('Processing ' + scope.size() + ' contacts. position=' + this.state.currentPosition + ' total=' + total);
 if (this.state.currentPosition < total) {
 System.enqueueJob(this); // continue with updated state
 }
 } 
 }catch (System.TransientCursorException e) {
 // Handle the transient exception and retry if retry count allows
 if (this.state.currentRetryCount < MAX_RETRIES) {
 this.state.currentRetryCount++;
 // Re-enqueue the same job to retry the *same* fetch operation
 System.enqueueJob(this);
 } else {
 // Log the error or send a notification if retries are exhausted
 System.debug('Max retries reached for cursor operation: ' + e.getMessage());
 // Add error logging/notification logic here
 }
 }catch (Exception e) {
 // Handle other, non-transient exceptions
 System.debug('A non-transient error occurred: ' + e.getMessage());
 // Add other exception handling/logging logic here
 }
 } 
 private void processRecords(List<SObject> records) {
 // Your specific business logic goes here
 System.debug('Processing ' + records.size() + ' records.');
 }
}

The Salesforce Spring '26 release features enhanced navigation within the Flow Builder canvas. Users now possess various methods to navigate the Flow canvas, facilitating the management of extensive and intricate flows.

Supported Navigation Methods

  • Trackpad scrolling
  • Arrow keys
  • Mouse wheel
  • Scroll bars

Github Link

Conclusion

Apex Cursor represents a powerful addition to the Salesforce developer toolkit, offering a more efficient approach to handling large datasets in specific scenarios. While batch processing remains the better choice for many use cases, particularly those requiring robust error handling and asynchronous execution, Apex Cursor shines when memory efficiency, sequential processing, and simplified code are priorities.

The key to making the right choice is understanding your specific requirements around data volume, processing complexity, error handling needs, and execution context. By carefully evaluating these factors, you can select the approach that best balances performance, maintainability, and reliability for your use case.

As with many architectural decisions in software development, there's no one-size-fits-all answer. Both Apex Cursor and batch processing have their place in a well-designed Salesforce application, and the best developers know when to leverage each approach to its fullest potential.

Disclaimer

This article is not endorsed by Salesforce, Google, or any other company in any way. I shared my knowledge on this topic in this blog post. Please always refer to Official Documentation for the latest information.

0 Comments

Leave a Reply

Written by Sudipta Deb

Enterprise Cloud Architect, Content Creator, 20x Salesforce Certified, 1x Google Cloud Certified, 2x Copado Certified

Related Posts

Salesforce Cancels the “Permissions in Profiles” Retirement: What It Means for Admins and Architects

Salesforce Cancels the “Permissions in Profiles” Retirement: What It Means for Admins and Architects

For the last few years, Salesforce administrators, architects, and security teams have been preparing for a major platform change: the retirement of permissions managed directly through Profiles. Salesforce had previously announced plans to move organizations toward a Permission Set–led security model, with enforcement expected to begin around the Spring ’26 timeframe.

read more...
New Apex Method | Extracting Picklist Values Based on Record Type Inside Apex

New Apex Method | Extracting Picklist Values Based on Record Type Inside Apex

For years, Salesforce developers have faced a common challenge: programmatically retrieving picklist values that are specific to a certain Record Type directly within Apex. The “solutions” often involved either complex SOQL queries against metadata, relying on the UI API (with its associated callout limits and serialization overhead), or maintaining clunky custom metadata/settings.

Good news, Technical Potpourri readers! The Salesforce Spring ’26 release is bringing a game-changer that will significantly simplify your Apex code and improve performance: native Apex methods to filter picklist values by Record Type!

read more...

0 Comments

Leave a Reply