[object Object]

Async Everywhere

Move heavy work to Queueable, Batchable, Platform Events, or the new Functions runtime when scale demands it. Synchronous Apex should finish in milliseconds. Long synchronous operations risk governor limits, lock contention, and visible user-experience lag. The 2026 default for any cross-record processing or external callout is Queueable; Batchable for record counts above 50k; Platform Events for fire-and-forget cross-system signaling.

// Modern pattern: queue heavy work, return immediately
public with sharing class AccountSyncService {
    public static void enqueueSync(Set<Id> accountIds) {
        if (accountIds.isEmpty()) return;
        System.enqueueJob(new AccountSyncQueueable(accountIds));
    }
}

public class AccountSyncQueueable implements Queueable, Database.AllowsCallouts {
    private final Set<Id> ids;
    public AccountSyncQueueable(Set<Id> ids) { this.ids = ids; }
    public void execute(QueueableContext qc) {
        // ... callout + DML
    }
}

Structured Errors

Custom exception classes per domain (AccountSyncException, BillingValidationException). Error logging to a custom object or Platform Event for monitoring, never System.debug only. Graceful user messages that don’t leak implementation details, schema names, or stack traces. Old throw new DmlException('something broke') scattered through legacy classes needs systematic refactoring; tag the work as a project, not as drive-by cleanup.

public class BillingException extends Exception {}

try {
    BillingService.run(records);
} catch (BillingException e) {
    Logger.error('billing.run', e, recordIds);
    throw new AuraHandledException('We could not process your billing change. Reference: ' + Logger.getCorrelationId());
}

Testability

Dependency injection via Apex interfaces, not concrete classes. Mock HTTP callouts consistently with HttpCalloutMock plus a per-test factory. Stub patterns for database operations using a Selector layer that can be overridden in tests with Stub API. Fast test suites enable confident deploys; a test class that takes 30 seconds breaks the inner loop. Target sub-200ms per test method on average.

public interface IAccountSelector {
    List<Account> byIds(Set<Id> ids);
}

@TestVisible private static IAccountSelector selector = new AccountSelector();

@isTest static void usesStub() {
    selector = (IAccountSelector) Test.createStub(
        IAccountSelector.class, new MyStub());
    // ...
}

Observability

Custom log tables, Platform Events for monitoring, Event Monitoring for production traces, and the new Apex Performance Monitor (Spring ‘26) for hot-path inspection. Legacy Apex often has zero observability — modernize this first when debugging issues, because you can’t optimize what you can’t measure. Correlation IDs in every log entry tie a single user-visible action to every Apex, Flow, and integration touch.

Modernization sequence (recommended):
  1. Add structured logger + correlation IDs (baseline observability)
  2. Add Selector + Service layer (enables stubs and tests)
  3. Move heavy work to Queueable (relieves limits)
  4. Replace generic exceptions with domain-specific ones
  5. Migrate trivial triggers to Before-Save Flow
  6. Adopt WITH USER_MODE everywhere a user context exists
  7. Add SLA alerts on failure tables

What Changed in 2026

WITH USER_MODE is now the recommended default on all SOQL and DML where a user context exists, replacing the older WITH SECURITY_ENFORCED. The Apex Performance Monitor surfaces per-method CPU and SOQL counts in production. Functions for Scale (Heroku-backed) is GA for genuinely heavy workloads that don’t fit governor limits at all.

What to Do This Week

Pick the most-modified Apex class in your repo over the last 90 days and add a structured logger with correlation IDs. Measure cycle time on the next two production incidents in that class and compare.

[object Object]
Share