Skip to main content

Mixed DML Operation Error in Salesforce: Causes, Setup vs. Non-Setup & Fixes

In plain words: The Mixed DML Operation Error (System.DmlException: MIXED_DML_OPERATION) happens when an Apex transaction attempts to perform Data Manipulation Language (DML) operations (such as insert, update, or delete) on both Setup Objects (like Users, Groups, or Permission Sets) and Non-Setup Objects (like Accounts, Contacts, or custom objects) in the same execution context.

When developing automated onboarding flows, automated user provisioning, or trigger logic in Salesforce, you may encounter an unexpected transaction rollback with the error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa). Understanding the underlying security architecture and transaction boundaries is key to resolving this exception permanently.

1. Why Salesforce Enforces the Mixed DML Restriction

Salesforce separates database tables into two broad categories to protect security access controls and metadata integrity:

  • Setup Objects: Govern user permissions, organizational hierarchy, and access metadata. Examples include User, UserRole, Group, GroupMember, PermissionSetAssignment, and QueueSobject.
  • Non-Setup Objects: Represent standard business data and custom records. Examples include Account, Contact, Opportunity, Case, and all custom objects (CustomObject__c).

Because modifying a Setup record immediately changes sharing calculations and object-level permissions, modifying regular data in the exact same database transaction can cause permission race conditions and data corruption. Salesforce blocks this by requiring separate transaction contexts.

360 Mixed DML Rule Card:
  • Error Signature: System.DmlException: MIXED_DML_OPERATION
  • Core Trigger: Mixing DML on Setup (e.g., User, Group) & Non-Setup (e.g., Account) in 1 context.
  • Production Fixes: Asynchronous execution via @future or Queueable Apex.
  • Unit Test Fix: Wrapping setup record creation in System.runAs() blocks.

2. Proven Strategies to Resolve Mixed DML Errors

Pattern 1: Asynchronous Execution with @future Methods
Move the Setup object modification into a separate asynchronous thread with its own transaction boundaries.
public with sharing class UserProvisioningService {

    // Non-setup DML in the main synchronous transaction
    public static void createCustomerAccount(String accountName, String userEmail) {
        Account newAcc = new Account(Name = accountName);
        insert as user newAcc;

        // Call asynchronous future method to create/assign user without Mixed DML
        assignPermissionSetAsync(userEmail, 'Standard_User_Access');
    }

    // Setup object DML isolated in its own transaction
    @future
    public static void assignPermissionSetAsync(String userEmail, String permSetName) {
        User targetUser = [SELECT Id FROM User WHERE Email = :userEmail LIMIT 1];
        PermissionSet ps = [SELECT Id FROM PermissionSet WHERE Name = :permSetName LIMIT 1];

        PermissionSetAssignment psa = new PermissionSetAssignment(
            AssigneeId = targetUser.Id,
            PermissionSetId = ps.Id
        );
        insert as user psa;
    }
}
Pattern 2: Queueable Apex (Recommended for Enterprise Workflows)
Queueable Apex supports complex sObject parameter passing and job chaining, making it superior to @future for multi-step processes.
public with sharing class AssignUserQueueable implements Queueable {
    private Id userId;
    private Id groupId;

    public AssignUserQueueable(Id userId, Id groupId) {
        this.userId = userId;
        this.groupId = groupId;
    }

    public void execute(QueueableContext context) {
        // Setup object DML executes in an isolated background thread
        GroupMember gm = new GroupMember(
            UserOrGroupId = this.userId,
            GroupId = this.groupId
        );
        insert as user gm;
    }
}

// In your main business logic:
// 1. Insert Non-Setup record (e.g., Contact)
insert as user contactRecord;

// 2. Enqueue the Setup DML into a distinct asynchronous queue
System.enqueueJob(new AssignUserQueueable(targetUserId, targetGroupId));
Pattern 3: Resolving Mixed DML in Apex Unit Tests using System.runAs()
In test classes, you can enclose Setup record creation within a System.runAs() block to create a distinct execution sub-context.
@isTest
private class UserCreationTest {

    @isTest
    static void testMixedDMLResolution() {
        // 1. Non-Setup DML (Account)
        Account acc = new Account(Name = 'Test Enterprise');
        insert acc;

        // Fetch an existing admin user to run setup changes
        User adminUser = [SELECT Id FROM User WHERE Profile.Name = 'System Administrator' AND IsActive = true LIMIT 1];

        // 2. Isolate Setup DML inside System.runAs block
        System.runAs(adminUser) {
            User standardUser = TestDataFactory.createTestUser();
            insert standardUser; // Setup DML succeeds without Mixed DML exception
        }

        // Verify outcomes
        Assert.areEqual(1, [SELECT COUNT() FROM Account WHERE Name = 'Test Enterprise']);
    }
}

3. Common Traps & Platform Best Practices

Trigger Trap: Calling Setup DML Directly Inside Non-Setup Object Triggers
Writing trigger logic on Contact or Account that directly inserts a User or GroupMember record will always throw a Mixed DML error upon execution. Triggers cannot change their own execution context synchronously—you must delegate the Setup DML to a Queueable or @future method.
Core Rule: Never execute DML on Setup and Non-Setup objects in the same synchronous execution block; decouple operations using Queueable Apex or @future methods in production, and use System.runAs() in test methods.
  • Prefer Queueable Over @future: Use Queueable Apex instead of @future because it allows passing complex sObjects, returns a Job ID for monitoring, and supports job chaining.
  • Enforce Security Standards: Always include as user or WITH USER_MODE on database operations to respect current user permissions.
  • Be Mindful of Async Limits: Remember that Salesforce enforces daily governor limits on asynchronous Apex executions (such as 250,000 asynchronous calls or 200 times the number of user licenses).

Summary

The Mixed DML Operation error is a safeguard designed to maintain platform access control and metadata consistency. By cleanly separating Setup and Non-Setup DML into distinct transactions using Queueable Apex, @future methods, or System.runAs() in test contexts, developers can build scalable, error-free provisioning and automation workflows across Salesforce.