4

I thought I was being clever by putting my Scheduled job classes inside a wrapper class:

global class Scheduler {
    global class Foo implements System.Schedulable {
        global void execute(SchedulableContext sc) {
            //Foo stuff
        }
    }

    global class Bar implements System.Schedulable {
        global void execute(SchedulableContext sc) {
            //Bar stuff
        }
    }
}

However once scheduled, any code inside the execute() block either silently fails or does absolutely nothing. Not even logging occurs.

The jobs look completely normal. Everything is scheduled correctly and says that my jobs are firing and logs a PreviousFireTime when monitoring with this SOQL query:

SELECT CreatedDate,CreatedById,CreatedBy.Name,CronExpression,EndTime,TimesTriggered,State,NextFireTime,PreviousFireTime,StartTime,CronJobDetail.Name,CronJobDetail.JobType,Id
FROM CronTrigger

I can't think of any namespace collisions that could be happening and the System.schedule() method is using dependency injection--so I wouldn't think using an inner class would matter. At the very least, would assume the compiler would prevent me from saving if this was an issue.

I can't find any documentation that states a Scheduled class should be top-level. So I'm at a lost if this is a bug or an implementation issue?

  • Can you add a little more detail around how you're so sure the execute block does not fire? Have you tried throwing exceptions therein to be extra sure? – Adrian Larson Dec 21 '16 at 18:12
6

The compiler allowed it (?), but the system doesn't support it. This is because the jobs are scheduled by ApexClassId, and only top-level classes have an ApexClassId. This is probably a bug in a recent release, as prior releases would prohibit implementing most system and database interfaces in a non-top-level class, including execute anonymous blocks.

However, you can work around it by being clever:

public class ScheduleWrapper implements Schedulable {
  // Local variable
  executeLogic logic;

  // Interface for executing logic
  interface executeLogic {
    void execute();
  }

  // Implementations
  class Foo implements executeLogic {
    public void execute() {
      // Do stuff here
    }
  }
  class Bar implements executeLogic {
    public void execute() {
      // Do stuff here
    }
  }

  // Factory methods
  public static ScheduleWrapper getFooScheduler() {
    return new ScheduleWrapper(new Foo());
  }
  public static ScheduleWrapper getBarSchduler() {
    return new ScheduleWrapper(new Bar());
  }

  // Private constructor
  ScheduleWrapper(executeLogic logic) {
    this.logic = logic;
  }

  // Schedulable Interface logic
  public void execute(SchedulableContext context) {
    logic.execute();
  }
}

The only down side is that in the Scheduled Apex logs, the top-level class name will be displayed (here, ScheduleWrapper), so you won't be able to determine which of the two methods are being executed. Of course, the trick is to make sure you name your jobs appropriately so you can determine which is which.

  • Works great @sfdcfox, thanks! My only tweak was to make the interface and construct public so I could call the scheduled jobs from other classes. Also, thanks for the explanation on the ApexClassIds! Makes much more sense. – Matthew Mitchener Dec 21 '16 at 22:47
  • @MatthewMitchener That's what the factory methods are for: System.schedule(jobName, cronString, ScheduleWrapper.getFooScheduler()). It's really just my personal preference so I don't have to worry about typing more code than necessary in my other classes, but you can do it any way that you like. – sfdcfox Dec 21 '16 at 23:41
  • 2
    So frustrating! I've wasted a whole day trying to figure out why an inner class that implements Schedulable looked like it was working but also not working. Took a while to finally uncover this post, but thanks for the answer @sfdcfox! – Scott McClung Jun 8 '18 at 19:30
  • @ScottMcClung I'm glad you found this answer useful. – sfdcfox Jun 8 '18 at 21:46

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.