“MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): User, original object: Account”
You can easily run into this error if you are trying to perform DML on setup and non-setup objects in the same transaction. Non-Setup objects can be any one of standard objects like Account or any custom object, here are few examples of the Setup Objects
Group1
GroupMember
QueueSObject
User2
UserRole
UserTerritory
Territory
This error typically comes in two different scenarios i.e.
Non-Test code
Test Code
We will cover both these scenarios in detail below. But for sake of example lets take
an example scenario:
AFTER INSERT Trigger on Account
This trigger create a GROUP from all newly created accounts.
The same trigger also adds current user as member to those newly created groups.
1. Non-Test Code
Non-Test code means any Apex code that is not written for test cases, for example triggers.
Typically trigger code for the same would be something like this
trigger Account on Account (after insert) {
//AccountHandler.afterInsert(Trigger.newMap);
List<Group> newGroups = new List<Group>();
for (Account acc : Trigger.new) {
newGroups.add(new Group(name=acc.Name, type='Regular', DoesIncludeBosses=false));
}
insert newGroups;
List<GroupMember> newGroupMembers = new List<GroupMember>();
for (Group grp : newGroups) {
newGroupMembers.add(new GroupMember(GroupId = grp.Id, UserOrGroupId=UserInfo.getUserId()));
}
insert newGroupMembers;
}
On creating an Account, this trigger will fail for this error:
“Apex trigger abhinav.Account caused an unexpected exception, contact your administrator: abhinav.Account: execution of AfterInsert caused by: System.DmlException: Insert failed. First exception on row 0; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): GroupMember, original object: Account: []: Trigger.abhinav.Account: line 14, column 1”

The solution is simple you split the DML into future and non future context i.e.
Perform DML on Non-Setup object type
Perform DML on Setup object type in @
future
methods.
Vice versa will also work. Here is the fixed code, we create new Apex class for the trigger code
Account.trigger
trigger Account on Account (after insert) {
List<group> newGroups = new List<group>();
for (Account acc : Trigger.new) {
newGroups.add(new Group(name=acc.Name, type='Regular', DoesIncludeBosses=false));
}
insert newGroups;
Set<id> groupIds = new Map<id , Group> (newGroups).keySet();
// call in future context to avoid MIXED DML conflicts
AccountHandler.createGroupMembers(groupIds);
}
AccountHandler.cls
This is the class with the future method to do setup and non-setup DML in different context
public class AccountHandler {
@future
public static void createGroupMembers(Set<Id> groupIds) {
List<GroupMember> newGroupMembers = new List<GroupMember>();
for (Id grpId : groupIds) {
newGroupMembers.add(new GroupMember(GroupId=grpId, UserOrGroupId=UserInfo.getUserId()));
}
insert newGroupMembers;
}
}
For more details on this, please check salesforce docs
here
.
2. Test Code
Take an example scenario, where you are having a trigger created in above fashion i.e. Setup and Non Setup objects are updated in different transactions via @future methods. But in Test case we tend to serialize all async operations like future calls via Test.startTest() and Test.stopTest(), so we are again on same condition that within same context. Here is the test code that will start failing again for the same error.
public static testmethod void test() {
// use this to make future methods execute synchronously
Test.startTest();
insert new Account(Name = 'Some Account Name');
Test.stopTest();
}
Here is the error that comes on executing this test
System.DmlException: Insert failed. First exception on row 0; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): GroupMember, original object: Account: []

Now, how to fix this ?
As this problem is specific to test code only, this can be fixed by starting new context in the future method or the conflicting DML point. Here is the fixed future method that works well from both UI and Test code
public class AccountHandler {
@future
public static void createGroupMembers(Set<Id> groupIds) {
List<GroupMember> newGroupMembers = new List<GroupMember>();
for (Id grpId : groupIds) {
newGroupMembers.add(new GroupMember(GroupId=grpId, UserOrGroupId=UserInfo.getUserId()));
}
if (Test.isRunningTest()) {
// start new context via system.runAs() for the same user for test code only
System.runAs(new User(Id = Userinfo.getUserId())) {
insert newGroupMembers;
}
} else {
// in non-test code insert normally
insert newGroupMembers;
}
}
}
But this is not clean, imagine a big complex force.com development project, that is having so many future methods and triggers. Doing this patch everywhere will not look neat. So, I tried coming up with some utility class that will handle this patching at one place only, this class is named “MixedDMLOps.cls” and here is the source for the same:
MixedDMLOps.cls
/**
Handles mixed dml situations in code. It runs the DML operation in different context for Test code only, so that conflict between DML on setup and non-setup object is gone.
PLEASE NOTE:
============
methods are not named as delete, insert because they are reserved words by Apex
*/
public without sharing class MixedDMLOps {
// DML UPDATE operation
public static Database.SaveResult[] up (Sobject[] objs) {
Database.Saveresult[] updateRes;
if (Test.isRunningTest()) {
System.runAs(new User(Id = Userinfo.getUserId())) {
updateRes = database.update(objs);
}
} else {
updateRes = database.update(objs);
}
return updateRes;
}
// DML DELETE
public static Database.DeleteResult[] del (Sobject[] objs) {
Database.DeleteResult[] delRes;
if (Test.isRunningTest()) {
System.runAs(new User(Id = Userinfo.getUserId())) {
delRes = database.delete(objs);
}
} else {
delRes = database.delete(objs);
}
return delRes;
}
// DML INSERT
public static Database.Saveresult[] ins (Sobject[] objs) {
Database.Saveresult[] res;
if (Test.isRunningTest()) {
System.runAs(new User(Id = Userinfo.getUserId())) {
res = database.insert(objs);
}
} else {
res = database.insert(objs);
}
return res;
}
}
The future method code can be simplified a lot now, here is the one that uses MixedDMLOps
public class AccountHandler {
@future
public static void createGroupMembers(Set<Id> groupIds) {
List<GroupMember> newGroupMembers = new List<GroupMember>();
for (Id grpId : groupIds) {
newGroupMembers.add(new GroupMember(GroupId=grpId, UserOrGroupId=UserInfo.getUserId()));
}
// the change
MixedDMLOps.ins(newGroupMembers);
}
}
Best fix for this problem ?
Ideally we shouldn’t be doing such hacks to fix MIXED DML in tests, the Apex test harness should handle it correctly. So an idea is posted for the same on App Exchange : https://sites.secure.force.com/success/ideaView?id=08730000000hE5tAAE
Your thoughts and views
Looking forward for the same 🙂
Comments (11)
Anonymoussays:
April 11, 2012 at 8:06 pmHello Abhinav,We face this kind of situations million times, specially in large implementation and unfortunately there is no standard documentation that clearly states what to do if encounter something like this.The mixedDMLops looks like something that could included as “best practice”, so that in furture at least this issue doesn't come to bite. Thank you very much for sharing. 🙂
Anonymoussays:
April 12, 2012 at 2:17 amThanks Swaleh, I am happy you liked it. MixedDMLOps can be used a practice, but I would say Salesforce team should fix the Apex Test Context to handle such situations. System.runAs() and Test.startTest()/stopTest() need to be more robust here.
Anonymoussays:
April 12, 2012 at 6:05 amAgreed. Do we have any IDEA regarding this? else we can create one and promote it. i tried to search ideaexchange and had no luck.. 🙂
Anonymoussays:
April 12, 2012 at 10:02 amGood idea, posted idea for the same : https://sites.secure.force.com/success/ideaView?id=08730000000hE5tAAE
Anonymoussays:
April 14, 2012 at 10:45 pmVoted for the Idea..:-)
Anonymoussays:
April 15, 2012 at 3:29 amAwesome @Swaleh 🙂
Anonymoussays:
April 25, 2012 at 1:14 pmThis again saved my life today…Thanks Abhinav..:-)
Anonymoussays:
April 25, 2012 at 5:04 pmGlad it helped ! 🙂
Anonymoussays:
June 20, 2012 at 9:03 amHi Abhinav ,Nice article ,as we are using similar pattern But what will be your thoughts on the issue like this : If this @future Class is required in Batch class .In that case , error occurs with batch class cannot call another batch or Future annotation . Do we have any other approach without @Future Annotation or @future can be used inside trigger does the trick ? Thanks,Mayank
Anonymoussays:
June 20, 2012 at 9:48 amKey is switching the context, but I am sure why a future job called from Batch class is failing with this fix as well. I will try testing it sometime and will let you know.
Anonymoussays:
June 21, 2012 at 1:48 amThanks , also I am checking by switching the context can help .