For out project we need to put users into correct public groups. This happens in a trigger when a user's contact is updated, depending on some of the fields there we decided where to put a user.

The problem is that sometimes we get this row locking error:

Failed to invoke future method 'public static void futurePutUserIntoCorrectPublicGroup(Set)' on class 'BP_User' for job id '707w000003xIXBI'
caused by: System.DmlException: Insert failed. First exception on row 0; first error: UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record: []

This method(futurePutUserIntoCorrectPublicGroup()) is just @future annotated method that calls another method(putUserIntoCorrectPublicGroup()) which does all the logic. Future here is needed to avoid MIXED_DML_OPERATION error.

After some googling and cheatsheeting, we found out that insert of a GroupMember record will lock Group table and the way to "fix" it is to query the locked records with FOR UPDATE keywords.

However, this was not possible due to the compilation error:

Result: [OPERATION FAILED]: classes/BP_User.cls: SObject type does not allow locking rows (Line: 397, Column: 36)

So the question is: how can we handle UNABLE_TO_LOCK_ROW errors on insert of a GroupMember record?

Stripped code of this method if needed:

public static void putUserIntoCorrectPublicGroup(Set<Id> userIds) {
    // Querying users, accountcontactroles and account
    Map<Id, User> userMap; // = query
    List<AccountContactRole> acrList; // = query
    Map<Id, Account> dealerAccs; // = query
    // Creating a list of public group names that we need
    List<String> consShares = new List<String>();
    // Querying all public groups with these names
    // Can't use FOR UPDATE here, compilation error:
    List<Group> publicGroups = [SELECT Id, DeveloperName
                                FROM Group
                                WHERE DeveloperName IN :consShares];
    List<GroupMember> gmList = new List<GroupMember>();
    // Looping through the AccountContactRoles
    for (AccountContactRole acr : acrList) {
        // Getting the account and the user of this ACR from the maps
        Account a;
        User u;
        // Finding needed public group
        Group g;
        // Create a new GroupMember object/record with GroupId being equal to the group we just found
        // And UserOrGroupId being equal to the user which is linked via Contact to this ACR
        GroupMember gm = new GroupMember(GroupId = g.Id, UserOrGroupId = u.Id);
    // Deleting all old GroupMember records so that we only have up-to-date data
    delete [SELECT Id FROM GroupMember WHERE UserOrGroupId IN :userIds];
    // Inserting new GroupMember records
    insert gmList;

I omitted most queries and loops to strip down the code and make it more readable. If we get this error, then it is on the very last line, where we insert that list.

  • 1
    I had the same issue, Fixed by creating groups within the unit test, and that fixed the issue for me. Oct 21 '19 at 23:21

Pretty tricky but you could perform this logic in a Batch job, specifically in the start() method of that job. The start() methods of Batch jobs are guaranteed to never run in parallel with another start() method, i.e. you will/should never have locking problems there.

Documentation on this: Execution Governors and Limits

Maximum number of batch Apex job start method concurrent executions: 1

Note that you can only queue up to 100 Batch jobs using the Apex Flex Queue, or even just 5 if you don't have Flex Queue enabled.


We have also gone so far at a customer to create a Mutex that is used to guarantee that pieces of code never run at the same moment. We found a way to store in the DB that a piece of code is executing, without actually using DML. If you want to know more about this, give me a PM.


I realize this is old but I kinda solved for this problem another way and wanted to share.

We put the group ID on a custom object corresponding to which the group member inserts happen - kind of like a custom group object, if you will. So I decided to put the FOR UPDATE on the custom object query (although, we didn't really need the custom object records in this context, we introduced to query just so we can use the lock).

Our challenge, though, was that the GroupMember inserts would often take more than 10 seconds to complete causing QueryExceptions in other waiting processes

  • Can you explain further how this works? Don't you still need to insert the GroupMember record? How does the custom object avoid the lock issue on Group? Sep 9 '20 at 18:57
  • We have a one to one mapping between groups and the custom object records. Instead of locking the Groups, we lock the corresponding custom object records
    – Vid L
    Sep 13 '20 at 2:23

Your Answer

