Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Boost Your Salesforce Query Performance with @ReadOnly for Large Data | Salesforce @ReadOnly annotation - Salesforce developer tutorial
Apex

Boost Your Salesforce Query Performance with @ReadOnly for Large Data | Salesforce @ReadOnly annotation

The short answer

The Salesforce @ReadOnly annotation increases the SOQL query row limit from 50,000 to 1,000,000 records for view-only operations. It enables high-volume data retrieval for reporting, data exports, and custom analytics where records do not require modification.

Key takeaways Apply the @ReadOnly annotation to increase the SOQL query limit to 1,000,000 records in supported contexts like REST/SOAP web services, Schedulable classes, and @AuraEnabled methods. Avoid running DML statements, sending emails, or triggering asynchronous jobs like System.schedule, Queueable, or future methods inside read-only transactions. Manage heap size and CPU time closely when querying large datasets, as these governor limits are not relaxed by the annotation. Write selective SOQL queries with indexed fields in WHERE clauses to maintain query performance when querying up to 1,000,000 rows.

What is the Salesforce @ReadOnly annotation?

Ever hit that 50,001st row and watched your code blow up? We've all been there. If you're dealing with massive datasets, the Salesforce @ReadOnly annotation is your best friend for getting around those standard SOQL limits. It's a simple tool, but it's one of those things that most people forget exists until they're staring at a "Too many query rows" error.

Look, the standard governor limits are there for a reason. Salesforce doesn't want one bad query hogging all the resources. But sometimes you just need to pull a massive amount of data for a report or a data export. When you use the Salesforce @ReadOnly annotation, the platform relaxes the rules and lets you query up to 1,000,000 rows in a single request. That's a huge jump from the usual 50,000 row cap.

But here's the catch - and it's a big one. You can't change anything. No updates, no inserts, no deletes. It's exactly what it says on the tin: read-only. I've seen developers try to sneak a little DML into a read-only method, and it never ends well. The system will stop you cold.

A professional code editor displaying a Salesforce Apex method with the @ReadOnly annotation next to a blurred data dashboard.

A professional code editor displaying a Salesforce Apex method with the @ReadOnly annotation next to a blurred data dashboard.

When should you use the Salesforce @ReadOnly annotation?

So when does it actually make sense to use this? In my experience, it's perfect for those heavy-duty "view only" tasks. Think about a custom dashboard that needs to aggregate data from across the entire org or a scheduled job that prepares data for an external system. If you aren't planning to touch the records, why stick with the lower limit?

Common scenarios I've run into include:

  • Building custom reporting engines where standard Salesforce reports don't cut it.
  • Creating data export tools that need to grab a year's worth of records at once.
  • Running complex analytics inside a Schedulable class.
  • Powering read-only UI components through Aura or LWC controllers.

One thing that trips people up is the context. You can't just throw this annotation on any old method and expect it to work. It only works in specific spots like REST or SOAP web services and classes that implement the Schedulable interface. If you're trying to handle massive amounts of data in other ways, you might want to look into managing Salesforce large data volumes using different architectural patterns.

The constraints you need to know

Now, don't go thinking this is a "get out of jail free" card for all governor limits. While the row limit goes up, other limits stay the same. You still have to worry about CPU time and heap size. If you query a million rows and try to store them all in a single list, you're going to hit a heap limit error long before you finish. Here's what's blocked when you use the Salesforce @ReadOnly annotation:

  • DML operations: No insert, update, delete, or undelete allowed.
  • Async jobs: You can't call System.schedule or fire off a Queueable or Future method.
  • Email: You can't send emails from within the read-only transaction.

Pro Tip: If you're using this in an @AuraEnabled method for a Lightning component, remember that the read-only behavior only applies if you actually include the annotation. It's an easy way to speed up your data-heavy components without hitting those frustrating row limits.

How to implement it in your code

Implementing the Salesforce @ReadOnly annotation is straightforward. You just add it right above your method definition. Here is a quick example of how I usually set this up for an LWC controller that needs to fetch a large list of accounts from the previous year.

public with sharing class AccountDataService {
    @AuraEnabled
    @ReadOnly
    public static List<Account> getHistoricalAccounts() {
        // This query can now return up to 1 million rows
        return [SELECT Id, Name, Industry, CreatedDate 
                FROM Account 
                WHERE CreatedDate = LAST_YEAR];
    }
}

But wait, just because you can query a million rows doesn't mean you should do it recklessly. You still need to write clean, selective SOQL. If your query is slow, the Salesforce @ReadOnly annotation won't magically make it fast. It just gives you more room to breathe. Always use indexed fields in your WHERE clause to keep things moving.

Key Takeaways

  • The Salesforce @ReadOnly annotation increases your SOQL row limit from 50,000 to 1,000,000.
  • It only works in specific contexts like Schedulable, REST, and SOAP services.
  • You cannot perform any DML operations (insert, update, delete) while this annotation is active.
  • CPU time and heap size limits still apply, so don't try to process all 1 million rows in memory at once.
  • It's the go-to solution for high-volume data exports and heavy-duty reporting.

So, should you use it? If you're building something that's strictly for reading data and you're worried about hitting that 50k limit, then yes, absolutely. Just make sure you're mindful of your heap size. I usually recommend processing records in smaller chunks or using streaming patterns if you're worried about memory. It's a powerful tool, but like anything else in Apex, you've got to use it the right way to keep your org running smoothly.

Frequently asked questions

What does the @ReadOnly annotation do in Salesforce?

The @ReadOnly annotation increases the SOQL query row limit from the default 50,000 records to 1,000,000 records in a single request. It is used for read-only operations such as data exports, custom dashboards, and reporting.

Where can you use the @ReadOnly annotation in Apex?

You can use the @ReadOnly annotation on REST and SOAP web services, classes that implement the Schedulable interface, and @AuraEnabled controller methods for Aura and LWC components.

What operations are blocked when using the @ReadOnly annotation?

The @ReadOnly annotation blocks all DML operations (insert, update, delete, and undelete), email sending, and asynchronous invocations including System.schedule, Queueable, and future methods.

Does @ReadOnly increase the Apex heap size limit?

No, the @ReadOnly annotation only increases the SOQL row count limit. Other governor limits, including heap size and CPU time, remain strictly enforced.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment