---
url: https://async.beyondthecloud.dev/getting-started.md
---

# Getting Started

Async Lib is a powerful Salesforce Apex framework that provides an elegant
solution for managing asynchronous processes. It eliminates common limitations
like "Too many queueable jobs" errors and offers a unified API for queueable,
batchable, and schedulable jobs.

## Why Async Lib?

### Salesforce Limits

### Key Benefits

* **🚀 Eliminates Queueable Limits**: Automatically handles "Too many queueable
  jobs" by intelligent chaining and batch overflow
* **🎯 Unified API**: Single, consistent interface for all async job types
  (Queueable, Batchable, Schedulable)
* **⚡ Smart Prioritization**: Jobs execute based on priority with automatic
  sorting
* **🛡️ Advanced Error Handling**: Built-in error recovery, rollback options, and
  continuation strategies
* **📊 Job Tracking**: Comprehensive tracking with custom job IDs and result
  records
* **⚙️ Configuration-Driven**: Control job behavior through custom metadata
  without code changes
* **🔗 Support Finalizers**: Execute cleanup logic after job completion with
  full context

## Core Concepts

### 1. QueueableJob Base Class

All queueable jobs extend the `QueueableJob` abstract class:

```apex
public class MyQueueableJob extends QueueableJob {
  public override void work() {
    // Your business logic here
    System.debug(
      'Processing job: ' + Async.getQueueableJobContext().currentJob.customJobId
    );
  }
}
```

### 2. Builder Pattern API

All job types use a fluent builder pattern:

```apex
// Queueable Job
Async.queueable(new MyQueueableJob())
    .priority(10)
    .delay(5)
    .enqueue();

// Batch Job
Async.batchable(new MyBatchJob())
    .scopeSize(100)
    .execute();

// Schedulable Job
Async.schedulable(new MySchedulableJob())
    .name('Daily Cleanup')
    .cronExpression('0 0 2 * * ? *')
    .skipWhenAlreadyScheduled()
    .schedule();
```

### 3. Automatic Job Chaining

When queueable limits are reached, Async Lib automatically switches to
scheduled-batch-based execution, ensuring your jobs always run without hitting
Queueable platform limits.

## Your First Queueable Job

Let's create a simple job that processes accounts:

### Step 1: Create Your Job Class

```apex
public class AccountProcessorJob extends QueueableJob {
  private List<Id> accountIds;

  public AccountProcessorJob(List<Id> accountIds) {
    this.accountIds = accountIds;
  }

  public override void work() {
    // Get job context
    Async.QueueableJobContext ctx = Async.getQueueableJobContext();
    System.debug('Processing job: ' + ctx.currentJob.customJobId);

    // Process accounts
    List<Account> accounts = [
      SELECT Id, Name
      FROM Account
      WHERE Id IN :accountIds
    ];
    for (Account acc : accounts) {
      acc.Description = 'Processed by ' + ctx.currentJob.className;
    }
    update accounts;

    System.debug('Processed ' + accounts.size() + ' accounts');
  }
}
```

### Step 2: Enqueue the Job

```apex
// Get some account IDs
List<Id> accountIds = new List<Id>{
    '0013000000abcdef',
    '0013000000ghijkl'
};

// Enqueue the job
Async.Result result = Async.queueable(new AccountProcessorJob(accountIds))
    .priority(5)
    .enqueue();

System.debug('Job enqueued with ID: ' + result.customJobId);
```

## Processing a Large Data Set

One job per record blows the limits, and a `Database.Batchable` is a separate
lifecycle with none of the chain tracking. `Async.chunk(...)` sits in between: it
pages through a source and runs one job per page, with the same retry, backoff and
`AsyncResult__c` tracking as any other job.

```apex
public class AccountRecalcJob extends ChunkJob implements Async.ChunkResettable, Async.Retryable {
    public override void work(List<SObject> chunk) {
        List<Account> accounts = (List<Account>) chunk;
        for (Account acc : accounts) {
            acc.Description = 'Recalculated';
        }
        update accounts;
    }

    // This job keeps nothing between pages or attempts, and says so.
    public void resetBeforeNextChunk(Integer pageNumber) {
    }

    public void resetBeforeRetry(Integer attempt) {
    }
}
```

The same job object runs once per page, and again on every retry, so Async Lib
makes you state what happens to its state. Empty bodies are a valid answer when
there is nothing to clear. See
[Job State Between Runs](/explanations/job-state-between-runs).

```apex
// In-memory records or ids
Async.chunk(new AccountRecalcJob(), ChunkSource.of(accounts))
    .chunkSize(200)
    .retry(2)
    .enqueue();

// Too large to hold in memory? Page a SOQL cursor instead
Async.chunk(new AccountRecalcJob(), ChunkSource.query('SELECT Id FROM Account'))
    .chunkSize(200)
    .enqueue();
```

The run takes one slot in the chain, so everything chained after it waits for the
last page:

```apex
Async.queueable(new PrepareJob())
    .chunk(new AccountRecalcJob(), ChunkSource.of(accounts))
    .chunkSize(200)
    .chain(new NotifyJob())
    .enqueue();
```

See the [Chunk API](/api/chunk) for ordering, priority, failure handling and the
cursor limits.

## Error Handling

Async Lib provides sophisticated error handling options:

```apex
Async.queueable(new MyJob())
    .continueOnJobEnqueueFail()    // Don't fail if enqueue fails
    .continueOnJobExecuteFail()    // Continue processing other jobs if this fails
    .rollbackOnJobExecuteFail()    // Rollback any DML if job fails
    .enqueue();
```

## Using Finalizers

Finalizers run after job completion and have access to success/failure context:

```apex
public class MyJobFinalizer extends QueueableJob.Finalizer {
    public override void work() {
        Async.QueueableJobContext ctx = Async.getQueueableJobContext();
        FinalizerContext finalizerCtx = ctx.finalizerCtx;

        if (finalizerCtx.getResult() == ParentJobResult.SUCCESS) {
            System.debug('Job completed successfully!');
        } else {
            System.debug('Job failed: ' + finalizerCtx.getException().getMessage());
        }
    }
}

// Attach finalizer within a job
public class MyMainJob extends QueueableJob {
    public override void work() {
        // Do main work...

        // Attach finalizer
        Async.queueable(new MyJobFinalizer())
            .attachFinalizer();
    }
}
```

## Configuration

Control job behavior using Custom Metadata (`QueueableJobSetting__mdt`):

1. Go to **Setup → Custom Metadata Types → QueueableJobSetting → Manage
   Records**
2. Create or edit settings:
   * **All**: Global settings for all jobs
   * **Specific Class Name**: Settings for specific job classes

Available settings:

* **QueueableJobName\_\_c**: The name of the QueueableJob.
* **IsDisabled\_\_c**: Disable job execution
* **CreateResult\_\_c**: Create `AsyncResult__c` records for tracking

## Async Result Records

When enabled in `QueueableJobSetting__mdt` (**CreateResult\_\_c** = true), Async
Lib automatically creates an `AsyncResult__c` record for every job in a chain —
including jobs that were **skipped** — so you can see exactly what happened, and
why. Query by **ChainId\_\_c** to see a whole chain run as a timeline.

### Key Fields

* **ChainId\_\_c**: Correlation id shared by every job in the same chain run
* **Status\_\_c**: Lifecycle outcome — `COMPLETED`, `FAILED`,
  `SKIPPED_DEPENDENCY`, `SKIPPED_CHAIN_STOPPED`, `SKIPPED_CHUNK_STOPPED`,
  `SKIPPED_EXPLICIT`, `SKIPPED_DISABLED`, `FRAMEWORK_ERROR`
* **CustomJobId\_\_c**: Unique job ID generated by Async Lib
* **SalesforceJobId\_\_c**: Underlying Salesforce job ID (Queueable, Batch, or
  Scheduled)
* **ClassName\_\_c**: Apex class name of the job
* **Result\_\_c**: Platform job result for jobs that ran (`SUCCESS`,
  `UNHANDLED_EXCEPTION`)
* **ExceptionType\_\_c** / **ExceptionMessage\_\_c**: Failure detail when
  `Status__c` is `FAILED`
* **SkipReason\_\_c**: Why a skipped job was skipped (e.g. *"Dependency
  'extract' required SUCCESS but was FAILURE"*)
* **DependsOnResult\_\_c**: Self-lookup to the result of the dependency that
  decided this job's fate, with **RequiredOutcome\_\_c** /
  **ActualOutcome\_\_c**
* **RetryAttempts\_\_c**: How many retries the job went through
* **RetryHistory\_\_c**: Per-attempt retry log

These records are never deleted automatically. See
[AsyncResult Cleanup](/features/asyncresult-cleanup) for the scheduled
cleanup batch with separate retention for failed results and the rest.

## What's Next?

Now that you understand the basics:

1. **[Standard Apex vs Async Lib](/introduction/standard-apex-vs-async-lib)** -
   See each plain-Apex async pattern next to its Async Lib equivalent.
2. **Explore the API** - Learn about all available methods and options:
   1. **[Queueable API](/api/queueable.md)** - Detailed information on using
      Queueable jobs
   2. **[Chunk API](/api/chunk.md)** - Paging a large data set across chained
      Queueables
   3. **[Batchable API](/api/batchable.md)** - Detailed information on using
      Batchable jobs
   4. **[Schedulable API](/api/schedulable.md)** - Detailed information on using
      Schedulable jobs
3. **Read the Blog Post** - Check out the detailed explanation:
   [Apex Queueable Processing Framework](https://blog.beyondthecloud.dev/blog/apex-queueable-processing-framework)
4. **[Initial Queueable Chain Schedulable Explanation](/explanations/initial-scheduled-queuable-batch-job.md)** -
   Learn why this job is important for framework to function properly.

## Quick Tips

* **Job Naming**: Jobs get unique names with timestamps:
  `MyJob::2024-01-15T10:30:45.123Z::1`
* **Custom Job IDs**: Every job gets a UUID for tracking independent of
  Salesforce Job IDs
* **Priority Matters**: Lower numbers = higher priority. Finalizers always run
  first.
* **Test Friendly**: Framework handles test context automatically
* **Callouts Supported**: add `implements Database.AllowsCallouts` to any job
