Recipes by Category

App Distribution (2) Bundle logic, interface and services for distribution. App Logic (37) The Apex programming language, workflow and formulas for logic. Collaboration (6) The Salesforce Chatter collaboration platform. Database (29) Data persistence, reporting and analytics. Integration (33) Web Service APIs and toolkits for integration. Security (9) Platform, application and data security. Tools (4) Force.com tooling User Interface (36) Visualforce MVC and metadata-drive user interfaces. Web Sites (12) Public web sites and apps with optional user registration and login.
Beta Feedback
Cookbook Home » Controlling Recursive Triggers

Controlling Recursive Triggers

Post by Developer Force  (2010-07-16)

Status: Certified
Level: novice

Problem

You want to write a trigger that creates a new record as part of its processing logic; however, that record may then cause another trigger to fire, which in turn causes another to fire, and so on. You don't know how to stop that recursion.

Solution

Use a static variable in an Apex class to avoid an infinite loop. Static variables are local to the context of a Web request (or test method during a call to runTests()), so all triggers that fire as a result of a user's action have access to it.

For example, consider the following scenario: frequently a Salesforce.com user wants to follow up with a customer the day after logging a call with that customer. Because this is such a common use case, you want to provide your users with a helpful checkbox on a task that allows them to automatically create a follow-up task scheduled for the next day.

You can use a before insert trigger on Task to insert the follow-up task, but this, in turn, refires the before insert trigger before the follow-up task is inserted. To exit out of this recursion, set a static class boolean variable during the first pass through the trigger to inform the second trigger that it should not insert another follow-up task:

For this Apex script to work properly, you first must define a custom checkbox field on Task. In this example, this field is named Create_Follow_Up_Task__c.

The following code defines the class with the static class variable:

public class FollowUpTaskHelper {

    // Static variables are local to the context of a Web request  
    // (or testMethod during a runTests call)  
    // Therefore, this variable will be initialized as false  
    // at the beginning of each Web request which accesses it.  

    private static boolean alreadyCreatedTasks = false;


    public static boolean hasAlreadyCreatedFollowUpTasks() {
        return alreadyCreatedTasks;
    }

    // By setting the variable to true, it maintains this  
    // new value throughout the duration of the request  
    // (or testMethod)  
    
    public static void setAlreadyCreatedFollowUpTasks() {
        alreadyCreatedTasks = true;
    }

    public static String getFollowUpSubject(String subject) {
        return 'Follow Up: ' + subject;
    }

}

The following code defines the trigger:

trigger AutoCreateFollowUpTasks on Task (before insert) {

    // Before cloning and inserting the follow-up tasks,
    // make sure the current trigger context isn't operating
    // on a set of cloned follow-up tasks.
    if (!FollowUpTaskHelper.hasAlreadyCreatedFollowUpTasks()) {

        List<Task> followUpTasks = new List<Task>();

        for (Task t : Trigger.new) {
            if (t.Create_Follow_Up_Task__c) {

                // False indicates that the ID should NOT
                // be preserved
                Task followUpTask = t.clone(false);
                System.assertEquals(null, followUpTask.id);

                followUpTask.subject = 
                FollowUpTaskHelper.getFollowUpSubect(followUpTask.subject);
                if (followUpTask.ActivityDate != null) {
                    followUpTask.ActivityDate =
                      followUpTask.ActivityDate + 1; //The day after
                }
                followUpTasks.add(followUpTask);
            }
        }
        FollowUpTaskHelper.setAlreadyCreatedFollowUpTasks();
        insert followUpTasks;
    }
}

The following code defines the test methods:

// This class includes the test methods for the
// AutoCreateFollowUpTasks trigger.

public class FollowUpTaskTester {
    private static integer NUMBER_TO_CREATE = 4;
    private static String UNIQUE_SUBJECT =
                                    'Testing follow-up tasks';

    static testMethod void testCreateFollowUpTasks() {
        List<Task> tasksToCreate = new List<Task>();
        for (Integer i = 0; i < NUMBER_TO_CREATE; i++) {
            Task newTask = new Task(subject = UNIQUE_SUBJECT,
                    ActivityDate = System.today(),
                    Create_Follow_Up_Task__c = true );
            System.assert(newTask.Create_Follow_Up_Task__c);
            tasksToCreate.add(newTask);
        }

        insert tasksToCreate;
        System.assertEquals(NUMBER_TO_CREATE,
                            [select count()
                             from Task
                             where subject = :UNIQUE_SUBJECT
                             and ActivityDate = :System.today()]);

        // Make sure there are follow-up tasks created
        System.assertEquals(NUMBER_TO_CREATE,
           [select count()
            from Task
            where subject = 
           :FollowUpTaskHelper.getFollowUpSubject(UNIQUE_SUBJECT)
           and ActivityDate = :System.today()+1]);
    }

    static testMethod void assertNormalTasksArentFollowedUp() {
        List<Task> tasksToCreate = new List<Task>();
        for (integer i = 0; i < NUMBER_TO_CREATE; i++) {
            Task newTask = new Task(subject=UNIQUE_SUBJECT,
                                  ActivityDate = System.today(),
                                  Create_Follow_Up_Task__c = false);
            tasksToCreate.add(newTask);
        }

        insert tasksToCreate;
        System.assertEquals(NUMBER_TO_CREATE,
                            [select count()
                            from Task
                            where subject=:UNIQUE_SUBJECT
                            and ActivityDate =:System.today()]);

        // There should be no follow-up tasks created
        System.assertEquals(0,
              [select count()
               from Task
               where subject=
               :FollowUpTaskHelper.getFollowUpSubject(UNIQUE_SUBJECT)
               and ActivityDate =:(System.today() +1)]);
    }

}

Share

Recipe Activity - Please Log in to write a comment

Worked fine, thanks!

voted as verified by Emilio Galicia  (2013-07-24)

Works like a charm for bulk DML for > 200 records

voted as verified by Brent Gossett  (2012-10-01)

To get around the problem stated by   jochen5478, I actually started using static Maps to track which records had already been processed and only processing them if they aren't in the Map(s). This is certainly not a completely safe solution but at least records in the "Subsequent" batches are being processed. The problem with this approach is that if you really have a lot of records, you can very quickly reach scripting and/or storage limits.

by LukeJFreeland  (2011-12-24)

I agree with jochen5478 this pattern is not bulk safe at all and by implication there does not appear to be a workable solution to handling recursive triggers for large amounts of data. You can demonstrate the problem using the sames code posted in this solution and using the dataloader to insert more than 200 tasks. In my case I tried adding in 300 tasks, expecting to see 300 follow up tasks created, but only 199 follow up tasks were created. This is due to the fact that the trigger processes records in batches of 200 so after the first 200 are processed the flag hasAlreadyCreatedFollowupTasks is set. Each subsequent batch of 200 records that the trigger processes is in the same process where the hasAlreadyCreatedFollowupTasks is still set to true and as a result no follow up tasks are created for these batches. This is a major weakness with platform development and should really be looked into resolving.

by Gareth Davies  (2011-10-28)

There is a problem with handling batches. If batch size is over 100 the batch is splited into parts of 100 or lower. For each subbatch trigger code is executed but stays in the same context (so governer limits count for both subbatches). But the static variable is not reset! By this behaviour you may have run your trigger code for the first 100 records but not for the last 100 records of the batch!
This problem is barely documented by salesforce but I think this is a big issue for developers need to cope with recrusive triggers!

by jochen5478  (2011-02-07)

 Sorry about that - I've fixed the repeating code.

by Jon Mountjoy  (2011-01-17)

actually this work around doesn't work properly in a test method context because the static survives across "DML contexts".   So if you do another DML Operation on the same object in the same test method it will think that the trigger has already been invoked.

Even worse trigger SOQL count is NOT reset by test.startTest as documented.

by david.mosher02192010  (2011-01-13)

I just noticed that as well too. All three code blocks are the same.

by a0930000007PDBM  (2011-01-06)

The trigger example code and the test example code are the same as the class example code.

by a0930000007NgNS  (2010-12-06)

X

Vote to Verify a Recipe

Verifying a recipe is a way to give feedback to others and broaden your own understanding of the capabilities on Force.com. When you verify a recipe, please make sure the code runs, and the functionality solves the articulated problem as expected.

Please make sure:
  • All the necessary pieces are mentioned
  • You have tested the recipe in practice
  • Have sent any suggestions for improvements to the author

Please Log in to verify a recipe

You have voted to verify this recipe.