home Home build Tools bug_report Errors menu_book Guides lightbulb Tips smart_toy Prompts extension Extensions folder_open Resources info About
search
error

System.LimitException: Too many DML statements: 151

What does this error mean?

Every insert, update, delete, upsert, undelete, and merge statement in an Apex transaction counts as one DML statement — regardless of how many records it operates on. When the total count across your entire transaction reaches 151, Salesforce throws this exception and rolls back all work.

The key insight: operating on 1 record or 10,000 records in a single DML call counts as exactly 1 statement. The fix is always to batch your records into collections and call DML once per operation type.

DML statement limits at a glance
150
Sync limit
200
Async limit
1
DML per collection
(any size)
10k
Max records
per DML
add_circle insert
edit update
delete delete
merge upsert
restore undelete
call_merge merge
lock Database.*

Common Causes

1. DML Inside a Loop — The Classic Mistake

Placing any DML statement inside a for loop is the single most common cause. Each iteration fires a separate DML call, burning through the 150-statement budget rapidly.

Apex — Bad Example
// ❌ BAD: 1 DML per record = 200 DML for 200 records
for (Account acc : Trigger.new) {
    Contact c = new Contact(
        LastName = acc.Name + ' Contact',
        AccountId = acc.Id
    );
    insert c; // 🔥 Fires once per account
}

2. Multiple Insert/Update Pairs on the Same Object

Inserting records, then immediately updating them in a second DML call (e.g., to set a field that required the ID), wastes DML budget. Use upsert or restructure logic to avoid the second call.

3. Helper Methods with Hidden DML

Utility or service methods that call DML internally, invoked once per record from a loop, compound into hundreds of statements without it being obvious at the trigger level.

4. Flow + Apex DML in the Same Transaction

Record-triggered Flows that fire alongside Apex triggers share the same DML budget. A Flow that inserts or updates records as part of its action steps counts toward the 150 limit for the whole transaction.

5. Recursive Triggers Causing Repeated DML

A trigger that updates a field, causing the same trigger to fire again, can multiply DML calls rapidly until the limit is hit — even if each individual call only processes a few records.

How to Fix It

Solution 1: Collect Records, Then DML Once

The golden rule: never put DML inside a loop. Build a collection of all records to insert or update during the loop, then call DML once outside the loop with the entire collection.

Apex — Correct
// ✅ GOOD: Collect all contacts, 1 DML regardless of record count
List<Contact> contactsToInsert = new List<Contact>();

for (Account acc : Trigger.new) {
    contactsToInsert.add(new Contact(
        LastName = acc.Name + ' Contact',
        AccountId = acc.Id
    ));
}

// One insert for ALL contacts — counts as 1 DML statement
if (!contactsToInsert.isEmpty()) {
    insert contactsToInsert;
}

Solution 2: Use upsert to Combine Insert + Update

When you need to insert new records and update existing ones in the same operation, upsert does both in a single DML statement. Define an external ID field or use the standard Id field as the upsert key.

Apex
List<Account> accountsToSync = buildAccountList();

// upsert on External_ID__c: inserts new + updates existing
// — all in a single DML statement
upsert accountsToSync External_ID__c;

Solution 3: Track and Monitor DML Usage

Use Limits.getDmlStatements() at key points during development to understand your DML consumption and catch problems before they reach production.

Apex
// Instrument your code during development
System.debug('DML used: '
    + Limits.getDmlStatements()
    + ' / '
    + Limits.getLimitDmlStatements()
);

// Build a reusable guard
public static void assertDmlBudget(Integer needed) {
    Integer remaining = Limits.getLimitDmlStatements()
                        - Limits.getDmlStatements();
    if (remaining < needed) {
        throw new Application.LimitException(
            'Insufficient DML budget. Need: ' + needed
            + ' Remaining: ' + remaining
        );
    }
}

Solution 4: Separate DML by Object Type into One Pass

When handling multiple object types in one transaction, group all records of the same type together and call DML once per type — not once per record or per method.

Apex — Unit of Work Pattern
// Accumulate all DML work across the entire transaction
public class UnitOfWork {
    private List<SObject> toInsert = new List<SObject>();
    private List<SObject> toUpdate = new List<SObject>();
    private List<SObject> toDelete = new List<SObject>();

    public void registerNew(SObject record)     { toInsert.add(record); }
    public void registerDirty(SObject record)   { toUpdate.add(record); }
    public void registerDeleted(SObject record) { toDelete.add(record); }

    public void commitWork() {
        // 3 DML statements maximum — regardless of record count
        if (!toInsert.isEmpty()) insert toInsert;
        if (!toUpdate.isEmpty()) update toUpdate;
        if (!toDelete.isEmpty()) delete toDelete;
    }
}
lightbulb

Pro Tip: The Unit of Work pattern above is the foundation of the popular fflib Apex Common open-source library. If your org has complex multi-object transactions, consider adopting fflib's fflib_SObjectUnitOfWork — it handles ordering, relationships, and DML consolidation automatically.