What You'll Learn
How Scheduled Apex and Scheduled Flows each work under the hood, where they share a queue and where they don't, which option wins for different use cases, and how to manage the scheduled job limits that bite enterprise orgs.
Two Mechanisms, One Shared Constraint
Salesforce offers two ways to schedule background work: Scheduled Apex (the Schedulable interface) and Scheduled Flows (the Scheduled trigger type). Both appear in the Scheduled Jobs list under Setup, and both compete for the same org-wide limit of 100 total scheduled jobs.
This shared limit is the most important fact for architects managing complex orgs. Salesforce AppExchange packages often consume scheduled jobs without visibility. An org with 15 installed packages can exhaust the 100-job limit before custom development has even started.
SELECT Id, JobType, CronJobDetail.Name FROM CronTrigger to inventory existing jobs.
Scheduled Apex: Mechanics and Usage
A class implementing Schedulable can be registered for execution at specific times using a cron expression. The execute(SchedulableContext sc) method runs in its own transaction with full governor limits.
Scheduled Apex almost always delegates work to a Batch Apex class — the schedulable class itself processes zero records but calls Database.executeBatch() to spawn the batch job. The schedulable class is a trigger; the batch class is the worker.
// Schedulable class — triggers a batch job
public class DailyLeadScoreJob implements Schedulable {
public void execute(SchedulableContext sc) {
Database.executeBatch(new LeadScoreBatch(), 200);
}
}
// Scheduling programmatically
String cron = '0 0 2 * * ?'; // 2 AM daily
System.schedule('Daily Lead Score', cron, new DailyLeadScoreJob());
// Scheduling via Setup > Apex Classes > Schedule Apex (no code needed)
// SOQL to check scheduled jobs
List<CronTrigger> jobs = [
SELECT Id, CronJobDetail.Name, NextFireTime, State
FROM CronTrigger
WHERE CronJobDetail.JobType = '7' // 7 = Schedulable
];Scheduled Flows: When They Make Sense
A Scheduled-Trigger Flow runs on a schedule against a batch of records matching specified criteria. Configure it from Flow Builder: choose "Schedule-Triggered Flow," set the frequency (once, daily, weekly), and the record filter criteria. Salesforce handles the scheduling and batching automatically.
Scheduled Flows shine for simple record-update automations that business users need to configure or modify without developer involvement. A nightly flow that updates Account tier based on last 90 days opportunity value is a good fit. No code, no deployment, visible to admins in Flow Builder.
The trade-offs versus Scheduled Apex:
- Volume limit: Scheduled Flows have a practical upper limit of ~250,000 records per scheduled batch. For larger populations, Batch Apex handles them more reliably.
- Error handling: Flow errors surface as fault paths or email notifications. Apex provides structured try/catch logging and custom error handling.
- Callouts: Scheduled Flows cannot make external HTTP callouts. If your scheduled job needs to call an external API, Scheduled Apex is required.
- Observability: Scheduled Apex job history is available in Setup > Apex Jobs with status, error messages, and duration. Scheduled Flow execution history is visible in Flow Interviews but less detailed.
Job Limit Management at Enterprise Scale
Enterprise orgs with 20+ managed packages routinely approach the 100-job ceiling. Management strategies:
- Consolidate jobs: Combine multiple low-volume Scheduled Apex jobs into a single dispatcher class that runs multiple operations sequentially. One job slot, multiple operations.
- Use Batch Apex self-chaining: Instead of scheduling a batch to run every 15 minutes, use the
finish()method to re-schedule itself. One job slot that runs continuously. - Replace old jobs: Audit and retire scheduled jobs from inactive managed packages or obsolete automation before adding new ones.
- Review package jobs: Contact AppExchange vendors about scheduled job consumption. Some vendors allow disabling package-internal scheduled jobs when not needed.
// Batch Apex self-chaining pattern (avoids scheduled job slot)
public class ContinuousLeadScoreBatch implements Database.Batchable<SObject>,
Database.Stateful {
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator([SELECT Id FROM Lead WHERE IsConverted = false]);
}
public void execute(Database.BatchableContext bc, List<Lead> scope) {
// process scope
}
public void finish(Database.BatchableContext bc) {
// Re-queue itself to run again in 15 minutes
System.schedule('LeadScore-' + System.now().getTime(),
buildCron(15), new DailyLeadScoreJob());
}
}System.abortJob(previousJobId) in the finish() method before scheduling the next run. Neglecting this creates ghost jobs that count against the 100-job limit.
Decision Framework: Apex or Flow?
Use Scheduled Flow when: the operation is simple record updates, the record population is under 250K, no external callouts are needed, and business users need to configure or modify the schedule.
Use Scheduled Apex when: the operation requires callouts, complex Apex logic, error handling with retry, volumes exceed 250K records, or the job must chain into Batch Apex for processing.
Key Takeaways
- Both Scheduled Apex and Scheduled Flows share the 100-job org limit — managed packages consume this limit invisibly.
- Scheduled Apex always delegates bulk work to Batch Apex — the Schedulable class is just a trigger.
- Scheduled Flows cannot make external callouts — use Apex for any job that hits a remote API.
- Job scheduling is not precise — Salesforce delivers jobs approximately at the scheduled time, not exactly.
- Self-chaining batch jobs must clean up previous scheduled job entries to avoid exhausting the job limit.
- Audit your org's job inventory via SOQL on CronTrigger before designing new scheduled automation.
Check Your Understanding
1. What is the org-wide limit for total scheduled jobs, shared across Scheduled Apex, Scheduled Flows, and packages?
2. A scheduled automation needs to call an external pricing API every night. Which tool must you use?
3. When using a self-chaining Batch Apex pattern to avoid scheduled job slots, what must happen in the finish() method?
Discussion & Feedback