Study guide

9.2 Queueable Apex

Salesforce Certified Platform Developer I Exam

Track your progress as you read

Finished sections get a check mark.

1 Introduction & Exam Overview

1.1 Current Exam Facts 1.2 Blueprint Weights & Study Strategy

2 Platform Fundamentals

2.1 Multi-Tenant Architecture & Shared Resources 2.2 MVC Mapping on the Lightning Platform 2.3 Declarative vs Programmatic Decisions 2.4 Agentforce for Developers: Use Cases & Limitations

3 Data Modeling & Schema

3.1 Objects, Fields & Schema Design 3.2 Relationship Types & Implications 3.3 Schema Design Impact on Apex 3.4 Data Import & Export for Development

4 Platform Data Features

4.1 Formula Fields & Roll-Up Summaries 4.2 Core CRM Objects & Standard Schema 4.3 AppExchange Extensions & Platform Boundaries

5 Declarative Process Automation

5.1 Record-Triggered Flows & Declarative Automation 5.2 Approval Processes & Business Rules 5.3 When to Use Apex vs Declarative Tools

6 Apex Language Basics

6.1 Variables, Constants & Apex Data Types 6.2 Lists, Sets & Maps 6.3 Control Flow & Exception Handling 6.4 Classes, Interfaces & OOP Patterns

7 SOQL, SOSL & DML

7.1 SOQL Query Fundamentals 7.2 SOSL Search Fundamentals 7.3 DML Operations & Bulkification 7.4 Governor Limits & Transaction Boundaries

8 Apex Triggers & Order of Execution

8.1 Trigger Structure & Context Variables 8.2 Bulk Trigger Patterns & Best Practices 8.3 Save Order of Execution 8.4 Recursion, Cascading & Transaction Control

9 Asynchronous Apex

9.1 @future Methods 9.2 Queueable Apex 9.3 Batch Apex 9.4 Schedulable Apex & Job Orchestration

10 Security, Events & Schema Access

10.1 Sharing, FLS, CRUD & Injection Prevention 10.2 Platform Events Publish & Subscribe 10.3 Programmatic Schema Access (Describe)

11 Visualforce

11.1 Visualforce Pages & Markup 11.2 Standard, Custom Controllers & Extensions 11.3 Visualforce in Lightning Experience

12 Lightning Components

12.1 Lightning Component Framework Benefits 12.2 Aura Component Structure & Resources 12.3 Lightning Web Components Fundamentals 12.4 LWC Data Access: Wire, Imperative & Events

13 UI Composition & Apex Integration Patterns

13.1 Composing UI in Lightning Apps 13.2 UI Security, Performance & Accessibility Patterns 13.3 Apex Actions for Flow, LWC, and Agentforce

14 Apex Testing

14.1 Testing Framework & Coverage Requirements 14.2 Writing Unit Tests for Triggers & Classes 14.3 Test Data Strategies & Isolation 14.4 Executing Tests & Execute Anonymous Differences

15 Debugging & Developer Tools

15.1 Debug Logs & Monitoring 15.2 Developer Console, CLI, VS Code & Workbench 15.3 Sandboxes, Scratch Orgs & Environment Strategy

16 Deployment & Release

16.1 Change Sets & Metadata Deployment 16.2 Unlocked Packages, Metadata API & Salesforce DX 16.3 Release Readiness & Credential Maintenance

9.2 Queueable Apex

Key Takeaways Explain This Topic AI

Implement the Queueable interface and enqueue with System.enqueueJob; execute(QueueableContext) holds the job logic

Unlike @future, Queueable can accept complex types including sObjects in the constructor and supports job Ids

You can chain another queueable from execute (with platform limits on depth/stacking); this enables multi-step async workflows

Implement Database.AllowsCallouts on the queueable class when the job performs HTTP callouts

Prefer Queueable over future for flexible state, chaining, and monitoring; prefer Batch when processing very large data volumes in chunks

Last updated: August 2026

9.2 Queueable Apex

Quick Answer: Queueable Apex lets you implement the Queueable interface, pass rich state (including sObjects) through a constructor, and run work asynchronously via System.enqueueJob . You get a job Id , optional callouts ( Database.AllowsCallouts ), and limited chaining —making Queueable the default upgrade path over @future for most deferred Apex.

Platform Developer I expects you to choose the right async tool. Queueable sits between simple @future methods and heavy Batch Apex: flexible state and chaining without the full start/execute/finish batch machinery.

  • The Queueable Interface public class ContactEnrichmentJob implements Queueable {
  • private List<Contact> contacts;
  • public ContactEnrichmentJob(List<Contact> contacts) {
  • this.contacts = contacts;
  • }
  • public void execute(QueueableContext context) {
  • // Optional: Id jobId = context.getJobId();
  • for (Contact c : contacts) {
  • c.Description = 'Enriched async';
  • }
  • update contacts;
  • }
  • }

Enqueue from a trigger, Flow-invoked Apex, controller, or another async finish method: Id jobId = System.enqueueJob(new ContactEnrichmentJob(Trigger.new));

// Persist jobId if you need user-visible status or custom monitoring

System.enqueueJob returns an AsyncApexJob Id (as an Id value). That alone is a major advantage over @future, which does not give you the same first-class enqueue handle in application code patterns emphasized on the exam.

Why sObject Parameters Matter

@future forbids sObject parameters. Queueable does not —you pass state through the constructor (or instance fields set before enqueue). That enables patterns such as:

Passing a filtered list of Trigger.new records (careful: still prefer Ids when records may change before the job runs)

  • Passing Maps, custom Apex types, and configuration flags
  • Building multi-step jobs with intermediate collections

Design caution: Enqueued jobs serialize their state. Passing huge heaps of records can hit async heap limits. For very large volumes, Batch Apex with a QueryLocator is safer than stuffing thousands of sObjects into one queueable.

When data freshness matters more than the in-memory snapshot, still pass Ids and re-query inside execute —same best practice as future methods, but now optional rather than forced by the language.

  • Chaining Queueable Jobs
  • From inside execute , you may enqueue another queueable: public class StepOneJob implements Queueable {
  • public void execute(QueueableContext context) {

// ... do step one work ...

  • System.enqueueJob(new StepTwoJob(/* state */));
  • }
  • }

Chaining supports multi-phase workflows: validate → callout → update → notify. Exam points:

Chaining is a supported Queueable capability that @future lacks (no future-from-future).

There are platform limits on how queueable jobs stack and how deep chains can go in a transaction path; do not design infinite recursive enqueue loops.

Each chained job is a new async transaction with its own governors.

Failures in step two do not automatically roll back step one (separate transactions)—design idempotency and compensating logic when business requires it.

Depth limit awareness: In a single synchronous transaction you also face limits on how many jobs you can enqueue. Prefer one job that processes a bulk collection over enqueuing one job per record.

Callouts with Queueable

Implement Database.AllowsCallouts (marker interface) on the class: public class CaseWebhookJob implements Queueable, Database.AllowsCallouts {

private Set<Id> caseIds;

  • public CaseWebhookJob(Set<Id> caseIds) {
  • this.caseIds = caseIds;
  • }
  • public void execute(QueueableContext context) {
  • List<Case> cases = [
  • SELECT Id, CaseNumber, Subject FROM Case WHERE Id IN :caseIds
  • ];
  • HttpRequest req = new HttpRequest();
  • req.setEndpoint('callout:Service_NC/hooks/cases');
  • req.setMethod('POST');
  • req.setHeader('Content-Type', 'application/json');
  • req.setBody(JSON.serialize(cases));
  • HttpResponse res = new Http().send(req);
  • if (res.getStatusCode() >= 400) {
  • // log, retry via chain, or throw to mark job failed
  • }
  • }
  • }

Compare to @future(callout=true) : Queueable’s interface-based approach is clearer when combined with non-primitive state and chaining (for example, callout job chains to a “persist response” job).

Job IDs and Monitoring Id jobId = System.enqueueJob(new ContactEnrichmentJob(contacts));

Use the job Id to:

  • Query AsyncApexJob for status ( Queued , Processing , Completed , Failed , Aborted )
  • Show admins or custom UIs that work was submitted
  • Correlate debug logs and error emails with a specific enqueue AsyncApexJob job = [
  • SELECT Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems
  • FROM AsyncApexJob
  • WHERE Id = :jobId
  • ];

Batch jobs expose more “items processed” fields because they run in chunks; queueable jobs are typically single-execute units, but AsyncApexJob still reports lifecycle status.

Comparison: Future vs Queueable vs Batch

Capability @future Queueable Batch

Implementation Annotation on static void method Queueable class + enqueueJob Database.Batchable + Database.executeBatch

  • sObject / complex state No Yes (constructor fields) Yes (instance state; Stateful optional)
  • Job Id to caller Not the primary pattern Yes ( enqueueJob ) Yes ( executeBatch )
  • Chaining No future→future Yes (enqueue from execute) Yes (often from finish )
  • Callouts @future(callout=true) Database.AllowsCallouts Database.AllowsCallouts
  • Best scale Small/medium deferred units Medium deferred units, workflows Large data volumes in scopes
  • Chunked processing No built-in Manual / chain Built-in start/execute/finish
  • Scheduled directly No No (schedule a Schedulable that enqueues) Often launched from Schedulable

Exam heuristics:

“Process millions of records nightly” → Batch (possibly scheduled).

“After trigger, call out with related records and maybe chain a follow-up” → Queueable .

“Static void, Set of Ids only, mixed DML split” → @future still acceptable; Queueable also works if options allow it.

  • Common Patterns for Post-Commit Async Work
  • Pattern A — Trigger enqueues one bulk job // Trigger handler after insert
  • if (!Trigger.new.isEmpty()) {
  • System.enqueueJob(new CaseWebhookJob(
  • new Map<Id, Case>(Trigger.new).keySet()
  • ));
  • }

One enqueue for the whole trigger batch keeps you inside async enqueue limits.

Pattern B — Service layer decides sync vs async

Business services expose sync methods for small paths and queueable wrappers for integrations. Controllers call the service; the service chooses Queueable when callouts or long work would risk the interactive transaction.

Pattern C — Queueable after partial DML success

When using Database.insert(records, false) , collect successful Ids and enqueue enrichment only for those Ids so failed rows are not sent to external systems.

Pattern D — Chained callout then DML

Some designs: Job1 performs callout only; Job2 performs DML based on stored results or re-query. Splitting can simplify transaction rules and retries.

Pattern E — Replace @future incrementally

Refactoring legacy @future methods into Queueable classes improves testability (injectable state), monitoring (job Id), and future chaining needs without changing when the work runs (still async post-commit from the caller’s perspective).

  • Testing Queueable Apex
  • Same start/stop discipline as future methods: @IsTest
  • static void enrichmentRuns() {
  • Contact c = new Contact(LastName = 'Async');
  • insert c;
  • Test.startTest();
  • System.enqueueJob(new ContactEnrichmentJob(
  • [SELECT Id, Description FROM Contact WHERE Id = :c.Id]
  • ));
  • Test.stopTest();
  • Contact result = [SELECT Description FROM Contact WHERE Id = :c.Id];
  • System.assertEquals('Enriched async', result.Description);
  • }

For callout queueables, register HttpCalloutMock before stopTest forces execution. Assert both DML side effects and that the mock received the expected request when relevant.

Choosing Queueable on Scenario Questions

Pick Queueable when the stem mentions any of: non-primitive/sObject inputs, System.enqueueJob , job Id monitoring, chaining steps, or “more flexible than future.” Pick Batch when volume, QueryLocator, or per-scope governors dominate. Pick @future when the stem is locked to annotation syntax, void static methods, or explicit future limits.

Queueable is the workhorse of modern async Apex on the exam—and in real orgs—once you outgrow Id-only future methods but do not yet need batch-scale chunking.