Apex follows Java-style try/catch/finally. The Exception hierarchy includes System.Exception (base), DmlException, QueryException, CalloutException, LimitException, NullPointerException, ListException, MathException, JSONException, etc.
apex try { insert someList; } catch (DmlException e) { System.debug('DML failed: ' + e.getMessage()); System.debug('Field errors: ' + e.getDmlFieldNames(0)); } catch (Exception e) { System.debug('Caught: ' + e.getTypeName() + ': ' + e.getMessage()); }
DmlException specifics: getNumDml(), getDmlMessage(i), getDmlFieldNames(i), getDmlId(i).
Custom exceptions: public class CustomerNotFoundException extends Exception {} then throw with custom message.
Critical caveats:
- `LimitException` is mostly UNCATCHABLE — when you hit a governor limit, the transaction terminates. Don't rely on catching it.
- Database.insert with allOrNone=false — errors return in
SaveResult[], not exceptions. - Async job exceptions — uncaught exceptions in Future/Queueable/Batch get logged and emailed; they don't propagate back.
In production, route exceptions to a custom Error_Log__c object for forensics.
