Yes — you can call Database.executeBatch() from a trigger, and it compiles, deploys, and runs without complaint. But you almost never should, and the interview pivots quickly to why.
What works
trigger AccountTrigger on Account (after update) {
if (Trigger.isExecuting && Trigger.size > 1000) {
Database.executeBatch(new AccountRefreshBatch(Trigger.newMap.keySet()), 200);
}
}
The platform happily accepts this. Apex enqueues the batch job and the trigger transaction continues.
Why “yes, but” — three real problems
1. The 5 active batch jobs limit
Salesforce caps an org at 5 active or queued Apex batch jobs at any moment. A trigger that fires on bulk DML can easily breach this:
- A data loader pushes 50,000 records in 250 batches of 200.
- Each chunk of 200 enters the trigger.
- Each trigger calls
Database.executeBatch(). - After the 5th call, every subsequent one throws
System.AsyncException: You have exceeded the maximum number of 5 batch jobs allowed.
Result: the user’s bulk load fails partway through, with a confusing async error.
2. The 50 async-call ceiling per transaction
A single transaction can enqueue at most 50 async jobs total (across @future, Queueable, and Database.executeBatch). A trigger firing batch on each of 200 records hits the wall at record #51.
3. The batch starts whenever the scheduler decides
Batch jobs aren’t queued for “immediate” execution — they sit in the Apex Flex Queue and start when the platform has capacity. The trigger returns instantly; the batch might run 30 seconds or 30 minutes later. If users expect “the moment I save, the work happens” they’ll be confused.
The right async tool from a trigger
Most cases that look like “call batch from a trigger” should actually use Queueable Apex instead. Queueable:
- Has a higher cap — 50 jobs enqueued from a trigger context.
- Starts much faster — typically within seconds.
- Supports chaining one job to the next.
- Accepts complex object state through its constructor.
trigger AccountTrigger on Account (after update) {
System.enqueueJob(new AccountRefreshQueueable(Trigger.newMap.keySet()));
}
If the workload genuinely needs millions of records scanned, batch is the right answer — but launch it from a scheduled class or a button, not the OLTP trigger path.
What you cannot call from a trigger
| Async type | Callable from trigger? | Notes |
|---|---|---|
@future | Yes | Up to 50/transaction. Can’t pass SObjects — only primitives. |
Queueable | Yes | 50/transaction. Preferred for trigger-launched async. |
Database.executeBatch | Yes, with caveats | 5 active total org-wide. |
System.schedule | Yes (Apex 47+) | Schedules a Schedulable to run later. |
| Inbound callout | No | Callout from a trigger is not allowed — go async first. |
A safer pattern when you must call batch
If business rules truly require launching a batch from a trigger, gate it:
trigger AccountTrigger on Account (after update) {
if (!BatchLauncher.alreadyLaunchedThisTx) {
if (System.AsyncInfo.getCurrentQueueableStackDepth() == 0) {
// Only launch batch from the original transaction, not chained
try {
Database.executeBatch(new AccountRefreshBatch(), 200);
BatchLauncher.alreadyLaunchedThisTx = true;
} catch (System.AsyncException e) {
// Already 5 batches running. Defer to a queueable instead.
System.enqueueJob(new AccountRefreshQueueable(...));
}
}
}
}
The try/catch matters: in production you cannot assume the 5-job slot is free, so plan a fallback.
What interviewers are really looking for
The answer is “yes, but rarely the right tool.” Strong candidates name: (1) the 5 active batch jobs org limit, (2) the 50 async calls per transaction limit, (3) Queueable as the better default for trigger-launched async, (4) that you can’t do callouts directly from a trigger — you must go async first. Bonus signal: mention that scheduled classes can also be invoked from a trigger via System.schedule(), but you need a unique job name each time.
Verified against: Apex Developer Guide — Using Batch Apex, Async Apex Limits. Last reviewed 2026-05-17.