Skip to main content

How to Fix System.LimitException: Too Many DML Statements: 1 in Salesforce

Encountering governor limit errors in Salesforce is common, but one specific error often catches developers off guard: System.LimitException: Too many DML statements: 1. This exception does not mean you have exceeded the standard 150 DML statement limit—it means you have attempted to execute even a single insert, update, or delete operation inside a strictly read-only execution context.

In plain words: When a Visualforce page is configured with readOnly="true" (or an Apex method is marked with @ReadOnly), Salesforce sets the maximum allowed DML statements to 0. Any attempt to modify data triggers Too many DML statements: 1 immediately.
Salesforce Limit Exception Too Many DML Statements: 1

1. Why Does This Exception Occur?

In standard Visualforce transactions, you can query up to 50,000 records and execute up to 150 DML statements. To allow high-volume data rendering, Salesforce provides the readOnly="true" attribute on the <apex:page> tag.

ReadOnly attribute set to true in Visualforce

Enabling readOnly="true" alters your execution limits:

  • SOQL Record Query Limit: Increases from 50,000 to 1,000,000 records.
  • Max DML Statements Allowed: Reduced from 150 to 0.

Because the platform limit for DML is zero in this mode, checking the Developer Console or limit monitor shows that you are already at or near the boundary:

Close to limit error message in Developer Console
Developer Trap: Even implicit data updates—such as updating a timestamp field, inserting application log records, or invoking a getter method with lazy-initialization DML—will crash the page if executed in a read-only context.

2. What Code Causes This Error?

Consider the following Visualforce page and Apex controller pair:

<!-- Visualforce Page configured as Read-Only -->
<apex:page controller="ExportReportController" readOnly="true">
    <apex:form>
        <apex:commandButton value="Log & Download" action="{!logAndExport}"/>
    </apex:form>
</apex:page>
// Apex Controller
public with sharing class ExportReportController {
    public PageReference logAndExport() {
        // ERROR: Attempting DML in a readOnly="true" context throws LimitException
        Audit_Log__c log = new Audit_Log__c(Action__c = 'Report Exported');
        insert log; 

        return null;
    }
}

3. How to Resolve the Error

Depending on your technical requirement, choose one of the following architectural fixes:

Solution Options:
  • Option A (If DML is Required): Remove readOnly="true" from the <apex:page> tag. If you query large record volumes, handle pagination using StandardSetController to stay under the 50,000-record query limit.
  • Option B (If 1M Records are Required): Keep readOnly="true" for rendering large exports (like CSVs or PDFs), but remove all insert, update, upsert, or delete operations from the controller lifecycle.
  • Option C (Asynchronous Decoupling): If you must capture audit logs from a read-only export screen, fire a separate asynchronous call (such as a JavaScript @RemoteAction or an enqueued job on a subsequent transactional page) rather than executing DML directly during page load.

4. Summary of Limits: Standard vs. Read-Only Context

Execution Context Comparison:
  • Standard Visualforce Page: Max 150 DML statements, 50,000 SOQL query rows retrieved, standard view state constraints (135 KB).
  • Read-Only Visualforce Page (readOnly="true"): 0 DML statements allowed, up to 1,000,000 SOQL query rows retrieved, relaxed collection limits for large data displays.
  • Apex Web Services (@ReadOnly): Enforces the identical 0-DML constraint for REST/SOAP endpoints designed solely for read operations.
Core Rule: Never combine DML statements with readOnly="true" or the @ReadOnly annotation. If your process requires database writes, execute within a standard transactional context.