So I am realizing that I should really put try/catch blocks around all the DML statements in my after triggers, but then I realized that maybe I can just write up a dynamic utility method that contains all the try/catch logic, and just call if from my trigger (or methods called within the after triggers).

Here is the method I came up with (first draft), I'm wondering if there are any fatal flaws...

public static void TryCatchDML(List<Sobject> recordsToDML, Map<Id, SObject> mapToTriggeredRecords, String operation) 
    // recordsToDML are the SObject records that are being updated
    // mapToTriggeredRecords: keys are IDs of the objects being updated, values are the SObject record in Trigger.new that should have an error added if caught
    // operation is insert, update, or delete
        if(operation == 'insert') insert recordsToDML;
        if(operation == 'update') update recordsToDML;
        if(operation == 'delete') delete recordsToDML;
    catch(DMLException e)
        for(Integer i = 0; i < e.getNumDML(); i++)
            SObject errorRecord = recordsToDML[e.getDMLIndex(i)]; 
            Sobject triggeredRecord = mapToTriggeredRecords.get(errorRecord.Id);
    } catch(Exception e) 
        System.debug('Exception type caught: ' + e.getTypeName());   
        System.debug('Message: ' + e.getMessage());   
        System.debug('Cause: ' + e.getCause());
        System.debug('Line number: ' + e.getLineNumber());   
        System.debug('Stack trace: ' + e.getStackTraceString());   

Since addError() on a DML Error has to be added to an SObject that is within the Trigger.new list, I made it so that one of the arguments is a map of the ids of records being updated back to the triggered record. So for example, here is how I'm calling the method in place of a simple insert statement:

(In this case I am updating a custom object abbreviated as ofq, within an Opportunity trigger)

if(ofqsToInsert.size() > 0)
    Map<Id, Opportunity> ofqIDtoOppMap = new Map<Id, Opportunity>();
    for(Opp_for_Quota__c ofq : ofqsToInsert) ofqIDtoOppMap.put(ofq.id, newOppMap.get(ofq.id));
    GHCustomServices.TryCatchDML(ofqsToInsert, ofqIDtoOppMap, 'insert');
  • 2
    You might find some inspiration in existing libraries with the same goal. – Adrian Larson Sep 8 '16 at 21:19
  • I really highly recommend dropping the empty catch(Exception e) block. – Adrian Larson Sep 8 '16 at 22:02
  • @AdrianLarson Do you have any samples or blog post for your DML library? – o-lexi Sep 8 '16 at 22:49
  • @Oleksiy Not currently. I've been meaning to set up a blog. I hadn't even realized I didn't set up a README. About time to do so, methinks. – Adrian Larson Sep 8 '16 at 22:50
  • 1
    @AdrianLarson This would be great! It's sad that I see many times in SF community people are 'inventing the bicycle' for missing common utilities that can easily be produced and maintained as open source projects, can spread DRY and Clean Code practices, save tons of time for new and experienced devs. – o-lexi Sep 8 '16 at 22:59