Skip to content
Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
DictionaryQQuery Locator
DevelopmentAdvanced

Query Locator

A query locator is a server-side cursor that Salesforce returns from the Database.getQueryLocator() method in Apex. It holds the definition of a SOQL query and lets a Batch Apex job stream through…

§ 01

Definition

A query locator is a server-side cursor that Salesforce returns from the Database.getQueryLocator() method in Apex. It holds the definition of a SOQL query and lets a Batch Apex job stream through the matching records in chunks instead of loading them all into memory at once. Because the platform manages the cursor, a single query locator can span up to 50 million records.

That ceiling is the reason the feature exists. A normal SOQL query in a synchronous transaction stops at 50,000 retrieved rows. A query locator handed to a batch job lifts that ceiling by a thousandfold, so jobs can sweep an entire object even when it holds tens of millions of rows.

§ 02

How a query locator drives a Batch Apex job

What Database.getQueryLocator() actually returns

Database.getQueryLocator() takes a single SOQL string (or a bound query) and returns a Database.QueryLocator object. That object is not the records themselves. It is a handle, a pointer to a result set the platform will materialize later. You can call getQuery() on the locator to read back the SOQL string it was built from, which is handy for logging or debugging a job whose scope looks wrong. You can also call iterator() to get a QueryLocatorIterator, then walk the rows yourself with hasNext() and next() outside of a batch context. In the day-to-day case, though, you never touch the iterator directly. You build the locator inside a batch class and return it, and the framework does the rest. The locator captures the query at the moment it is created, including any filters, ORDER BY, and the objects and fields you selected. Keep the SELECT list lean. Every field you pull comes back for every record in each chunk, so a bloated query inflates heap usage in the execute method for no benefit.

The start, execute, finish contract

A query locator lives inside a class that implements the Database.Batchable interface. That interface has three methods. The start() method runs once at the very beginning of the job and returns the locator, which defines the full scope of records to process. The execute() method runs once per chunk and receives a List of records sized by the batch scope (200 by default, configurable down to 1 or up to 2,000). The finish() method runs once at the end, after every chunk has been processed, and is the place for follow-up work like sending a summary email or chaining another job. Each call to execute() is its own transaction with its own fresh set of governor limits. That separation is the whole point. A job that touches 10 million records never hits a per-transaction DML or CPU limit, because the work is spread across 50,000 small transactions when the scope is 200. The query locator is what makes that division possible. It hands the platform a stable, ordered cursor that can be sliced into batches reliably across hours of execution.

Why 50 million instead of 50,000

The 50,000-row cap is a synchronous governor limit. It protects shared infrastructure from a single transaction trying to pull an unbounded result set into memory. A query locator sidesteps that limit because it never loads everything at once. The cursor stays on the server, and the platform reads only one chunk into the execute method at a time. That design is why the documented ceiling jumps to 50 million records per locator. The contrast with Iterable matters here. The start() method can also return an Iterable instead of a query locator. An Iterable is the right tool when your scope comes from somewhere SOQL cannot reach, such as a callout response or a custom list you assemble in code. The catch is that an Iterable does not get the 50 million row exemption. The standard SOQL row limit still applies to whatever query feeds it. So the rule of thumb is simple. If the scope is a straight SOQL query over Salesforce data, return a query locator and enjoy the high ceiling. Reach for an Iterable only when the data genuinely does not come from a query.

Worked example: archiving stale cases

Picture a Service Cloud org with 8 million closed cases and a need to flag anything older than five years for archival. A synchronous query would die at 50,000 rows, so this is a textbook batch job. The start method returns Database.getQueryLocator('SELECT Id, ClosedDate FROM Case WHERE IsClosed = true AND ClosedDate < LAST_N_YEARS:5'). The platform reads that locator and begins handing the execute method lists of 200 cases at a time. Inside execute, the code stamps a custom Archive_Candidate__c checkbox and updates the chunk, then moves on. With 8 million matching rows and a scope of 200, the job runs roughly 40,000 execute transactions. Each one starts clean, so no single transaction approaches the 10,000-row DML limit. The finish method fires once at the end and emails the admin a count of processed records. If the team later wants smaller chunks because each case update touches heavy triggers, they pass a second argument to Database.executeBatch and set the scope to 50. The locator and the query never change. Only the slice size does.

Ordering, chunking, and the QueryLocatorIterator

The platform decides how to chunk a query locator, and that has practical consequences. When a locator is used in batch, Salesforce processes records in chunks based on the scope size, and it generally returns them in the order the underlying query produces. If you need a predictable sequence, put an explicit ORDER BY in the SOQL you pass to getQueryLocator(). Do not assume the database will hand back rows in creation order or Id order on its own. Outside of batch, the QueryLocatorIterator gives you a way to consume a locator manually. You call iterator() on the locator to get one, then loop with hasNext() and next() to pull records one at a time. This is occasionally useful for custom iterator patterns or for inspecting a locator's contents in an Anonymous Apex window. Be aware that consuming a locator this way in a synchronous context puts you back under the normal query row limits, since you are no longer inside the batch framework that grants the 50 million row exemption. The high ceiling is a property of how batch executes the locator, not of the locator object sitting on its own.

Limits and gotchas that bite real jobs

A few hard limits surround query locators. Only one start method query locator is allowed per batch job, and the locator counts as one of the SOQL queries your start transaction is allowed to issue. There is a separate, org-wide constraint on cursors: a user can have a limited number of open query cursors at a time, so spinning up many batch jobs or stateful cursors in parallel can collide. A common mistake is building a query locator from a SOQL string that includes an unbounded subquery or a relationship that fans out badly. Subqueries inside a batch locator are restricted, and a parent-child relationship query that returns too many child rows per parent can fail the job. Another trap is mutating the data your locator selected while the job runs. Because chunks are read across many transactions over time, records that move in or out of the filter mid-run can produce surprising counts. For very large or very volatile datasets, narrow the filter, add an ORDER BY on an indexed field, and keep the execute method idempotent so a retried chunk does no harm.

§

Trust & references

Sources

Cross-checked against the following references.

Official documentation

Straight from the source - Salesforce's reference material on Query Locator.

Was this entry helpful?
Help us write better definitions. Quick reactions or detailed edit suggestions.

About the Author

Dipojjal Chakrabarti is a B2C Solution Architect with 29 Salesforce certifications and over 13 years in the Salesforce ecosystem. He runs salesforcedictionary.com to help admins, developers, architects, and cert/interview candidates sharpen their fundamentals. More about Dipojjal.

§

Test your knowledge

Q1. What does Database.getQueryLocator() return in Apex, and what scale does it enable?

Q2. When should a developer reach for a Query Locator instead of a standard SOQL query?

Q3. How does a Query Locator integrate with the batch Apex execution model?

§

Discussion

Loading…

Loading discussion…