Skip to content

AfterInsert.ContinueOnError

Logs and swallows a Writer's or Dispatcher's exception, so later handlers run and the insert goes on. A Writer also gets a private unit of work, so a failure discards only its own writes.

Interface

apex
public class AfterInsert {
    public interface ContinueOnError {
    }
}

AfterInsert.ContinueOnError has no methods: implementing it is the whole opt-in.

Example

apex
public with sharing class ContactWriter implements AfterInsert.Writer, AfterInsert.ContinueOnError {
    public Boolean writeOnAfterInsertWhen(TriggerHandler.InsertRecord record) {
        return record.isNotNull(Contact.AccountId);
    }

    public void writeOnAfterInsert(TriggerHandler.InsertRecord record, TriggerHandler.UnitOfWork unitOfWork) {
        unitOfWork.toInsert(new Task(WhatId = ((Contact) record.getNewSObject()).AccountId, Subject = 'Review contact'));
    }
}
cls
public with sharing class AccountWelcomeTaskWriter implements AfterInsert.Writer, AfterInsert.ParentQuery, AfterInsert.ContinueOnError {
    public Map<SObjectField, TriggerHandler.ParentFields> queryParentsOnAfterInsert() {
        return new Map<SObjectField, TriggerHandler.ParentFields>{ Account.OwnerId => TriggerHandler.ParentFields.with(User.Name, User.Email) };
    }

    public Boolean writeOnAfterInsertWhen(TriggerHandler.InsertRecord record) {
        return record.startsWith(Account.Type, 'Customer');
    }

    public void writeOnAfterInsert(TriggerHandler.InsertRecord record, TriggerHandler.UnitOfWork unitOfWork) {
        Account accountRecord = (Account) record.getNewSObject();
        User owner = (User) record.getNewParent('Owner');

        unitOfWork.toInsert(
            new Task(
                WhatId = record.getId(),
                OwnerId = accountRecord.OwnerId,
                Subject = 'Onboarding call - ' + accountRecord.Name,
                Description = 'New customer assigned to ' + owner?.Name + ' (' + owner?.Email + '). Confirm the billing address and the primary contact.',
                ActivityDate = Date.today().addDays(3),
                Priority = 'High',
                Status = 'Not Started'
            )
        );
    }
}

Good to Know

  • The rest of the handler is skipped. After an exception, its remaining records and its Finalizer do not run.
  • Add a Logger. Without a TriggerOrchestrator.Logger, a swallowed error leaves no trace. See Errors & Logging.
  • Some errors still fail the insert. TriggerOrchestratorException and TriggerHandler.TriggerHandlerException are logged and rethrown. System.LimitException and the FinalException from writing to the row cannot be caught.
  • Only the handler's own work. A failure in bypassOnAfterInsertWhen(), queryParentsOnAfterInsert() or the shared commit still fails the insert.
  • The private unit costs its own statements. It commits right after the Writer, before the shared unit, and is never merged with it.