What is mixed DML?

Understanding Mixed DML in Salesforce

Mixed DML is a Salesforce runtime error that occurs when a single transaction performs DML operations on both setup (metadata-like) objects and non-setup (data) objects. Salesforce prohibits mixing these two categories of objects in the same transaction to avoid platform inconsistencies. This is commonly encountered in Apex code and unit tests.

Which objects are considered setup objects?

Setup objects typically include User, Group, GroupMember, QueueSObject, PermissionSet, ApexClass, and other metadata/configuration-like objects. Non-setup objects include Account, Contact, Leads, Opportunities, and custom objects (e.g., MyObject__c).

Typical error message

The error you’ll see is usually:

System.DmlException: DML operation on setup object is not permitted after you have updated a non-setup object type

Why Salesforce restricts mixed DML

This restriction preserves transactional integrity between platform configuration (setup) and runtime data. Setup actions can have far-reaching effects on org configuration and security; mixing them with transactional data DML could create inconsistent states if a rollback occurs.

Common scenarios that trigger Mixed DML

  • Creating or updating a User record in the same transaction that inserts Accounts or Contacts.
  • Adding GroupMember or QueueSObject entries alongside standard object inserts/updates.
  • Test methods that create test data and then create Users without isolation.

Example that causes mixed DML

// This will cause mixed DML if executed in the same transaction
Account a = new Account(Name = 'Acme');
insert a;

User u = new User(
    Username = '[email protected]',
    LastName = 'User',
    Email = '[email protected]',
    Alias = 'tuser',
    TimeZoneSidKey = 'America/Los_Angeles',
    LocaleSidKey = 'en_US',
    EmailEncodingKey = 'UTF-8',
    ProfileId = [SELECT Id FROM Profile WHERE Name='Standard User' LIMIT 1].Id
);
insert u; // Mixed DML error occurs here

Workarounds and best practices

To avoid mixed DML errors, separate the setup and non-setup DML into different transactions. Common approaches:

  • Use asynchronous Apex: Move setup DML into an @future or Queueable method so it runs in a separate transaction.
  • Perform setup DML first (or last) in a separate call: Call a separate web service or separate Apex transaction to handle setup objects.
  • In tests, use System.runAs(): Test classes can use System.runAs to create Users in a controlled context, which helps avoid mixed DML in many cases.

Fixed example using @future

public class SetupHelper {
    @future
    public static void createUserAsync(String profileId) {
        User u = new User(
            Username = 'asyncuser' + Datetime.now().getTime() + '@example.com',
            LastName = 'Async',
            Email = '[email protected]',
            Alias = 'async',
            TimeZoneSidKey = 'America/Los_Angeles',
            LocaleSidKey = 'en_US',
            EmailEncodingKey = 'UTF-8',
            ProfileId = profileId
        );
        insert u;
    }
}

// Caller transaction (non-setup DML)
Account a = new Account(Name = 'Acme');
insert a;

// Call setup DML asynchronously
SetupHelper.createUserAsync([SELECT Id FROM Profile WHERE Name='Standard User' LIMIT 1].Id);

When System.runAs doesn’t help

System.runAs only affects code in test context and can sometimes bypass certain restrictions for tests, but it does not remove the mixed DML restriction in normal runtime transactions. Use asynchronous separation for production logic.

Summary

Mixed DML in Salesforce occurs when setup and non-setup object DML are performed in the same transaction. Avoid it by separating DML into different transactions (asynchronous Apex, separate calls) or using test-specific patterns like System.runAs. Understanding which objects are considered setup objects and planning transaction boundaries are essential to preventing these runtime exceptions.