Only classes that implement Schedulable can be passed directly to System.schedule. Batch Apex, Queueable, and future methods cannot be scheduled on their own — but they can run from inside a Schedulable’s execute method.
Directly schedulable
| Apex type | Implements Schedulable? | Schedulable directly? |
|---|---|---|
| Schedulable class | Yes | Yes |
| Batch Apex (Database.Batchable) | No (unless also implements Schedulable) | No (unless dual-implements) |
| Queueable | No | No |
@future method | No | No |
| Trigger | N/A | No |
The pattern: Schedulable wraps the work
The almost-universal real-world pattern is:
- Write a Schedulable class.
- In its
execute(SchedulableContext), kick off Batch / Queueable / future / direct work. - Schedule the Schedulable.
public class NightlyJob implements Schedulable {
public void execute(SchedulableContext ctx) {
// Choice 1: kick off a Batch
Database.executeBatch(new MyBigBatch(), 200);
// Choice 2: enqueue a Queueable
// System.enqueueJob(new MyQueueable());
// Choice 3: do work directly (small jobs only)
// List<Lead> leads = [...];
}
}
System.schedule('Nightly', '0 0 2 * * ?', new NightlyJob());
Convenience: System.scheduleBatch for Batch one-shots
For “run this batch once after N minutes”:
System.scheduleBatch(new MyBigBatch(), 'Retry MyBatch', 30, 200);
Salesforce auto-generates a Schedulable wrapper. The batch runs once and the schedule self-terminates. Useful for retries.
Dual-implementing Schedulable + Batchable
One class can be both:
public class NightlyCleanup implements
Database.Batchable<sObject>,
Schedulable
{
// Schedulable.execute — fires from the schedule
public void execute(SchedulableContext ctx) {
Database.executeBatch(this, 200);
}
// Batchable.execute — fires per chunk
public void execute(Database.BatchableContext ctx, List<sObject> scope) {
// ... process chunk
}
public Database.QueryLocator start(Database.BatchableContext ctx) { /* ... */ }
public void finish(Database.BatchableContext ctx) {}
}
System.schedule('Nightly', '0 0 2 * * ?', new NightlyCleanup());
Apex distinguishes the two execute methods by their parameter types. The Schedulable execute calls Database.executeBatch(this, ...) to kick off the batch lifecycle.
What you can call from a Schedulable’s execute
| Action | Allowed from Schedulable.execute? |
|---|---|
Database.executeBatch | Yes |
System.enqueueJob (Queueable) | Yes |
@future method call | Yes (Schedulable counts as sync for this rule) |
| HTTP callout directly | No — must go through Queueable or @future(callout=true) |
| Heavy DML / SOQL | Yes, but bounded by sync governor limits |
| Yes |
The callout caveat
Schedulable.execute itself does not support callouts. If your scheduled work needs a callout:
public class CalloutSchedule implements Schedulable {
public void execute(SchedulableContext ctx) {
System.enqueueJob(new CalloutQueueable());
}
}
public class CalloutQueueable implements Queueable, Database.AllowsCallouts {
public void execute(QueueableContext ctx) {
// ... HTTP callouts here
}
}
Limits to know
| Limit | Value |
|---|---|
| Scheduled jobs in an org | 100 |
System.schedule calls per transaction | 250 |
| One scheduled job can fire multiple times | Yes (recurring schedules) |
| Each fire counts against daily async limit | Yes |
Common interview follow-ups
- Can I schedule a Queueable? — Not directly. Wrap it in a Schedulable and enqueue from
execute. - Can I schedule a method instead of a class? — No. Schedule the class; the method called is always
execute. - Can a Schedulable schedule itself? — Yes — it can call
System.schedulefor a new instance. Useful for one-shot patterns, but watch for runaway loops.
Verified against: Apex Developer Guide — Scheduled Apex. Last reviewed 2026-05-17.