With the following code, I'm getting an Apex CPU time limit exceeded error:

List<Lead> untouchedLeads = [SELECT id 
                                    FROM Lead 
                                    WHERE Days_Since_Last_Touch__c > 2
                                        AND CreatedDate = LAST_N_WEEKS:6
                                        AND Status = 'Attempting'
                                        AND Owner.Type = 'User'
                                        ORDER BY CreatedDate DESC NULLS LAST LIMIT 508];
    if(untouchedLeads.size() > 0) { 
        // find ID of distribution queue
            List<Group> distQueues = [SELECT Id
                                        FROM Group 
                                        WHERE DeveloperName = 'Distribution_Queue'
                                        AND Type = 'Queue'
                                        ORDER BY CreatedDate DESC NULLS LAST LIMIT 1];
            if(distQueues.size() > 0) {
                //assign untouched leads to distribution queue
                Id distQueueId = distQueues[0].id;
                for(Lead untouchedLead : untouchedLeads){
                    untouchedLead.Ownerid = distQueueId;

       update untouchedLeads;

Should I not be updating a list like that?

Here is our only update lead trigger code that's not part of a managed package:

/* after update */
if (trigger.isAfter && trigger.isUpdate && triggerContextUtility.isFirstRun()) {

    // indicates this ran
    system.debug ('***** enter leadTrigger after update *****');

    // if lead is updated by API or System User
    if(user=='API User' || user=='System User') {

        for(Lead l: trigger.new) {
          Lead oldLead = Trigger.oldMap.get(l.Id);
          Boolean oldLeadOwnedByUser  = oldLead.Owner.Type.equals('User');
          Boolean newLeadOwnedByQueue = l.Owner.Type.equals('Queue');
           if (oldLeadOwnedByUser && newLeadOwnedByQueue){  
            Date four_days_ago = Date.today().addDays(-4);
            Date seven_days_ago = Date.today().addDays(-7);
            String owneridstring = l.OwnerId;
            system.debug ('leadTrigger after update activeOrg running user: ' + user);
            system.debug ('leadTrigger after update activeOrg lead id: ' +l.Id);
            system.debug ('leadTrigger after update activeOrg org id: ' +l.Org_ID__c);
            system.debug ('leadTrigger after update activeOrg IsConverted: ' +l.IsConverted);
            system.debug ('leadTrigger after update activeOrg IsDeleted: ' +l.IsDeleted);
            system.debug ('leadTrigger after update activeOrg Scammer__c: ' +l.Scammer__c);
            system.debug ('leadTrigger after update activeOrg CustomerStatus__c: ' +l.CustomerStatus__c);
            system.debug ('leadTrigger after update activeOrg Hiring_Plan__c: ' +l.Hiring_Plan__c);
            system.debug ('leadTrigger after update activeOrg ZipHR_Demo_Request_Time__c: ' +l.ZipHR_Demo_Request_Time__c);
            system.debug ('leadTrigger after update activeOrg ZipHR_Benefits_Setup_Request_Time__c: ' +l.ZipHR_Benefits_Setup_Request_Time__c);
            system.debug ('leadTrigger after update activeOrg ZipHR_Self_Service_Benefits_Start_Time__c: ' +l.ZipHR_Self_Service_Benefits_Start_Time__c);
            system.debug ('leadTrigger after update activeOrg Free_Trial_Start_Time__c: ' +l.Free_Trial_Start_Time__c);
            system.debug ('leadTrigger after update activeOrg LastActivityDate: ' +l.LastActivityDate);
            system.debug ('leadTrigger after update activeOrg seven_days_ago: ' +seven_days_ago);
            system.debug ('leadTrigger after update activeOrg OwnerId: ' +l.OwnerId);

            // lead qualifies for auto conversion if
            // lead is a paying org 
            // lead is not on a free trial
                //trigger.oldMap.get(l.Id).Hiring_Plan__c != l.Hiring_Plan__c &&
                l.Org_ID__c != null
                && l.IsConverted == false
                && l.IsDeleted == false
                && l.Scammer__c != 'scammer'
                && l.CustomerStatus__c == 'paying'
                && l.Hiring_Plan__c != null
                && !l.Hiring_Plan__c.contains('free')
                && !l.Hiring_Plan__c.contains('trial')
                && (l.Free_Trial_Start_Time__c < four_days_ago || l.Free_Trial_Start_Time__c == null)
                && ( (l.LastActivityDate == null 
                    || l.LastActivityDate < seven_days_ago) 
                        || owneridstring.startsWith('00GG') 
                && l.ZipHR_Demo_Request_Time__c == null
                && l.ZipHR_Benefits_Setup_Request_Time__c == null
                && l.ZipHR_Self_Service_Benefits_Start_Time__c == null
                && (l.Free_Trial_Start_Time__c < seven_days_ago 
                    || l.Free_Trial_Start_Time__c == null)
                ) {

        system.debug ('***** leadTrigger after update activeOrgMap size: '+activeOrgMap.size());


    // prevent multiple recursions
    system.debug('**** leadTrigger after update number of runs: ' + triggerContextUtility.getNumOfRuns());

    // if trigger already ran, setFirstRunFalse
    system.debug('**** leadTrigger after update is first run: ' + triggerContextUtility.isFirstRun());

    // return number of soql queries in this execution context so far 
    system.debug('leadTrigger after update, queries used in this apex code so far: ' + limits.getQueries());

}   /**** end after update ****/

Could something in there be the problem?

  • 1
    Are you using before update/insert trigger to update the leads ? Can you put your full code ? Just this code doesn't seem to throw the CPU limit error.
    – Ruchi
    Oct 12, 2016 at 5:55
  • 1
    You can put System.debug(Limits.getCPUTime()); before your update DML and see how much time it is taking.Put same debug statement after DML also.See if that come.Review the debug log what other process and flows are running after updating.
    – Mr.Frodo
    Oct 12, 2016 at 6:03
  • I've now posted my update lead trigger code above. Oct 12, 2016 at 6:08

2 Answers 2


The first piece of code you posted will use an amount of CPU time that depends on how many records are matched and so is inherently at risk of hitting CPU and heap limits no matter how much optimisation you do.

A mechanism the platform provides to handle this problem is batchable Apex where you can place the query in the start method and have the work done in multiple transactions in the execute method where each transaction is passed a limited number of records - you can pick how many and so stay well below the governor limits. The processing is done asynchronously and for example can be scheduled to run every night (which makes sense for logic like this based on days).


To start the batch processing (where the second parameter is how many records to process at once and can be from 1 to 2,000):

Database.executeBatch(new MyBatchable(), 1000);

with the batchable class:

public class ContactFieldsBatchable implements Database.Batchable<SObject> {

    public Database.QueryLocator start(Database.BatchableContext context) {
        // Can return up to 50 million records
        return Database.getQueryLocator([
                SELECT id
                FROM Lead 
                WHERE Days_Since_Last_Touch__c > 2
                AND CreatedDate = LAST_N_WEEKS:6
                AND Status = 'Attempting'
                AND Owner.Type = 'User'
                ORDER BY CreatedDate DESC NULLS LAST

    public void execute(Database.BatchableContext context, List<Lead> scope) {
        List<Group> distQueues = [
                SELECT Id
                FROM Group 
                WHERE DeveloperName = 'Distribution_Queue'
                AND Type = 'Queue'
                ORDER BY CreatedDate DESC NULLS LAST LIMIT 1
        if (distQueues.size() > 0) {
            Id distQueueId = distQueues[0].id;
            for (Lead l : scope) l.Ownerid = distQueueId;
            update scope;

    public void finish(Database.BatchableContext context) {

General information: The Maximum CPU time on the salesforce servers - 10,000 milliseconds, or 10 seconds and that includes everything : Apex code, Workflows, Process builder, Validation rules, even calculating Formula fields when you are trying to access them.

So the problem with Apex CPU time limit is that code that gets the blame for over exceeding the Limit can potentially have nothing to do with it, it just represents the place where 10 seconds are over. It could be that previous process took 9.99 seconds and left only 0.01 for everything else.

For example Process Builder can not even fail with CPU timeout and exception will be always thrown on after update trigger (if it exist).

So in general the answer is NO, there is no way to catch that exception or anyhow avoid it, this exception is inevitable. What you need to do - is reduce CPU time consumed by inefficient processes.

You can start with placing few System.debug(Limits.getCPUTime()); around the code. I see that this part of code is updating a lead so this is probably connected to after update trigger. I suggest you to put System.debug(Limits.getCPUTime()); to the end of before update and to start of after update trigger. This way you will know how much CPU time eats before insert trigger (1st debug) and how much time consume validations, workflows etc (2nd debug). From where you can do more granular search.

looked into full code : Debugs eat CPU time , for example all lead information can be debugged in 1 statement instead of 15, that will give you some time profit, but I think your problem is out of code posted here.

Update: Looked into updated code. Please put System.debug(Limits.getCPUTime()); before untouchedLeads Select and see how much CPU time left for that process OR try to execute whole thing posted with test data in execute anonymous and see how much processing time it needs. This way it will be clear if problem is in this particular 508 leads loop and insert or somewhere else entirely.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.