100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Exception Handling in Apex

Learn how to use try/catch/finally, built-in and custom exception types, and partial-success DML patterns to write resilient Apex code.

Advanced ApexBeginner8 min readJul 10, 2026
Analogies

Try, Catch, Finally, and Exception Types

Apex exception handling follows the familiar try/catch/finally structure: code in the try block executes normally, a matching catch block intercepts a thrown exception of a specific type (or its supertype), and the optional finally block always executes whether or not an exception occurred, making it the right place for cleanup logic. Apex provides built-in exception types like DmlException, QueryException, NullPointerException, ListException, and CalloutException, each thrown automatically by the runtime for the corresponding failure category, and every custom exception class you define by extending Exception (e.g., class InsufficientInventoryException extends Exception {}) automatically inherits getMessage(), getStackTraceString(), getTypeName(), and getCause().

🏏

Cricket analogy: It's like a batsman having a pre-planned response for specific dismissal types — a review for a marginal LBW versus simply walking off for a clean bowled — rather than reacting the same way to every wicket.

DML Exception Handling and Partial Success

For bulk DML operations, wrapping insert/update statements in try/catch stops processing entirely on the first failed record within that statement by default; to allow some records to succeed while others fail, use Database.insert(records, false) or Database.update(records, false), which returns a list of Database.SaveResult objects that you inspect individually with .isSuccess() and .getErrors() instead of throwing. This partial-success pattern is essential in batch and bulk contexts where a single bad record — say, a duplicate external ID — shouldn't cause every other valid record in the same chunk to roll back.

🏏

Cricket analogy: It's like a bowling attack continuing the innings even after one bowler has a disastrous over — the team doesn't forfeit the whole match, they just note that over's damage and carry on with the rest of the spell.

apex
List<Account> accountsToInsert = buildAccountsFromImport();
Database.SaveResult[] results = Database.insert(accountsToInsert, false);

List<String> failureLog = new List<String>();
for (Integer i = 0; i < results.size(); i++) {
    if (!results[i].isSuccess()) {
        for (Database.Error err : results[i].getErrors()) {
            failureLog.add('Row ' + i + ': ' + err.getStatusCode() + ' - ' + err.getMessage());
        }
    }
}
if (!failureLog.isEmpty()) {
    System.debug(LoggingLevel.ERROR, String.join(failureLog, '\n'));
}

Custom Exceptions and Rethrowing

Defining domain-specific custom exceptions — like InsufficientInventoryException or InvalidDiscountException — makes calling code's catch blocks far more expressive than catching a generic Exception, and you can chain the original cause into a custom exception via the two-argument constructor pattern (new MyException('context message', originalException)) so getCause() preserves the full root failure for logging. In trigger and service-layer code, it's a best practice to catch narrow, expected exceptions close to where they occur and only let truly unexpected exceptions propagate up, since an uncaught exception in a trigger rolls back the entire DML operation that fired it, including any partial work already done in that transaction.

🏏

Cricket analogy: It's like a scorer recording not just 'out' but the precise dismissal type — caught behind, run out, stumped — preserving the specific cause rather than a vague generic entry in the scorebook.

Catching System.Exception broadly and swallowing it silently (an empty catch block) is a serious anti-pattern — it hides bugs like NullPointerExceptions in production and makes support tickets nearly impossible to diagnose; always log or rethrow.

Use Test.startTest()/Test.stopTest() combined with a try/catch and System.assert on the exception's getMessage() to unit-test that your code throws the right custom exception under the right failure condition.

  • try/catch/finally lets you intercept specific exception types and always run cleanup logic in finally.
  • Apex provides built-in exceptions like DmlException, QueryException, NullPointerException, and CalloutException.
  • Custom exceptions extend Exception and inherit getMessage(), getStackTraceString(), getTypeName(), and getCause().
  • Database.insert/update(records, false) enables partial success, returning SaveResult objects instead of throwing.
  • SaveResult.isSuccess() and getErrors() let you inspect per-record DML outcomes in bulk operations.
  • Chain an original exception as the cause of a custom exception to preserve root-cause information.
  • Never swallow exceptions silently with an empty catch block — always log or rethrow.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ApexSalesforceStudyNotes#ExceptionHandlingInApex#Exception#Handling#Apex#Try#ErrorHandling#StudyNotes#SkillVeris