Skip to content

Configuration Safety

The rule

Mistakes in Apex throw. Mistakes in Custom Metadata degrade and warn.

Async Lib refuses to enqueue a job that is wrong in code. It never refuses to enqueue a job because a Custom Metadata record is wrong.

Why

Wrong ApexWrong QueueableJobSetting__mdt
Changed bya developeran admin
Needs a deployyesno
Runs your tests firstyesno
Reachesthe one job just writtenevery job in the org, via the All record

A typo in the All record would otherwise stop every async job in production, with no test run and no deploy to catch it first. Losing a retry costs you a behaviour. Refusing to enqueue stops the business.

What happens instead

Each case degrades, records the reason on the job, and writes it to the debug log at ERROR. Nothing is thrown and the job runs.

Configuration mistakeResult
MaxRetries__c set for a job that declares no resetretry not applied, job runs once
BackoffStrategy__c is not a known strategyno backoff, retries run without delay
MaxRetries__c above the framework capclamped to the cap
LoggerClass__c cannot be resolvedno logger, jobs run normally
JobSerializerClass__c cannot be resolved, or is not an Async.JobSerializerno payload stored, the result records NotSerializable, job runs normally

Every fallback degrades toward doing less, never toward doing something the developer did not ask for. Skipping retry is safe, because the job then runs exactly once, which is what its code was written and tested against. Silently enabling retry on a job that never declared how its state resets would not be.

Warnings append to the job's retry history, so they reach AsyncResult__c.RetryHistory__c when result creation is enabled. Each one names the record, the job, what was skipped, and the fix.

What still throws

Anything a developer wrote, because it cannot escape their own test run:

  • retry(n) or Async.chunk(...) without a declared reset, see Job State Between Runs
  • retry(-1), or a retry count above the cap passed in Apex
  • delay() combined with asyncOptions()
  • dependsOn(Async.afterPrevious()) with no previous job
  • Async.requeue(...) asked for more payload than it can hold in one call

These throw at .enqueue() or .chain(), synchronously, in the caller's transaction.

Consequence worth knowing

Setting MaxRetries__c on the All record enables retry only for jobs that have declared how their state resets. The rest keep running as before and say why in their history.

That is intentional. The alternative is an admin silently enabling state-carrying retries across an entire org, which is the bug Job State Between Runs exists to prevent.