No. Salesforce locks any Apex class that is part of an active scheduled job. Trying to edit or deploy it gives the error:
“This Apex class is currently in use by a scheduled job. Aborted job must finish or be removed before the class can be saved.”
The lock extends to any class referenced by the scheduled class — directly or transitively. If your Schedulable calls a service class, that service class is also locked. Update either one, and the deploy fails.
What’s actually locked
- The Schedulable class itself.
- Any class directly referenced (called methods, static usage, constructor args).
- Any class transitively referenced through that dependency chain.
A scheduled MainJob that calls ServiceA which calls ServiceB which calls UtilityC: all four are locked.
The workaround
Three-step deploy:
// 1. Find and abort the scheduled job(s)
List<CronTrigger> jobs = [
SELECT Id, CronJobDetail.Name FROM CronTrigger
WHERE CronJobDetail.Name = 'Nightly Cleanup'
];
for (CronTrigger ct : jobs) {
System.abortJob(ct.Id);
}
// 2. Deploy the new class versions
// 3. Re-schedule
System.schedule('Nightly Cleanup', '0 0 2 * * ?', new NightlyCleanupSchedulable());
Automating this in CI/CD
Many teams script the abort/deploy/reschedule cycle in their pipeline:
- Pre-deploy hook: query and abort all scheduled jobs.
- Deploy: standard
sf project deploy. - Post-deploy hook: run a script that re-creates the schedules.
The re-create script is just a series of System.schedule calls in anonymous Apex.
Why Batch and Queueable are different
Database.Batchable and Queueable classes can usually be edited even while jobs of that type are running. Why? Because running jobs use the deployed-at-enqueue-time code, and updates only affect future invocations. Salesforce only locks scheduled Apex because the schedule persists indefinitely and refers to the class by name.
But there’s a related restriction: if a Batch class is currently Processing, you can’t redeploy it (active job execution holds a lock briefly).
What “active” means
A Schedulable is “active” if a CronTrigger record references it. The job doesn’t need to be currently executing — just scheduled.
Hot-fix without aborting?
If you absolutely cannot disrupt the schedule:
- Modify a helper class the scheduled job does not directly reference. This works because the lock only follows direct references.
- Add new fields/objects that don’t change Apex.
- Override behavior via configuration (Custom Metadata, Custom Settings) that the scheduled code reads at runtime.
For genuine code changes to the Schedulable or its referenced classes, you must abort + deploy + reschedule.
Sandbox refresh
After a sandbox refresh, CronTrigger rows don’t transfer. All schedules are gone. Re-running your “schedule everything” script is part of standard post-refresh setup.
Common interview follow-ups
- What happens if I deploy and the script’s still running? — The deploy fails or hangs waiting for the lock.
- Can I update the cron expression? — No, you abort and re-schedule. There’s no “update schedule” API.
- Why is this so strict? — The platform can’t safely re-resolve class references mid-fire. Locking is safer than crashing mid-job.
Verified against: Apex Developer Guide — Scheduled Apex Limitations. Last reviewed 2026-05-17.