Exception Handling in Apex – try/catch, Custom Exceptions

Proper exception handling is crucial for building robust, maintainable Apex code. This guide covers try/catch blocks, custom exceptions, and best practices.

1. Basic try/catch Structure


public class AccountService {
    public static void updateAccounts(List<Account> accounts) {
        try {
            // Code that might throw an exception
            update accounts;
            System.debug('Accounts updated successfully');
        } catch (DmlException e) {
            // Handle the specific exception
            System.debug('DML Error: ' + e.getMessage());
            System.debug('Failure message: ' + e.getDmlMessage(0));
            System.debug('Failed record ID: ' + e.getDmlId(0));
        } catch (Exception e) {
            // Catch-all for other exceptions
            System.debug('Unexpected error: ' + e.getMessage());
        } finally {
            // Cleanup code - runs regardless of exception
            System.debug('Operation complete');
        }
    }
}
  

2. Common Exception Types

DmlException (Database Exceptions)


try {
    List<Account> accounts = new List<Account>{
        new Account(Name = 'Test')  // Missing required field
    };
    insert accounts;
} catch (DmlException e) {
    // Specific DML errors
    System.debug('Error type: ' + e.getDmlType(0));  // REQUIRED_FIELD_MISSING
    System.debug('Error message: ' + e.getDmlMessage(0));
    
    // Which record failed?
    String failedId = e.getDmlId(0);
}
  

QueryException


try {
    Account acc = [SELECT Id FROM Account WHERE Name = 'NonExistent'];
} catch (QueryException e) {
    // Thrown when query returns no rows or too many rows
    System.debug('Query failed: ' + e.getMessage());
}
  

NullPointerException


try {
    String name = null;
    Integer length = name.length();  // Null reference
} catch (NullPointerException e) {
    System.debug('Null value encountered');
}
  

CalloutException (API Calls)


try {
    Http http = new Http();
    HttpRequest request = new HttpRequest();
    request.setEndpoint('https://api.example.com');
    request.setMethod('GET');
    request.setTimeout(120000);
    
    HttpResponse response = http.send(request);
} catch (CalloutException e) {
    // Timeout or other callout errors
    System.debug('Callout failed: ' + e.getMessage());
}
  

3. Creating Custom Exceptions

Create custom exceptions for application-specific errors:


// Define custom exception
public class InsufficientFundsException extends Exception { }

public class PaymentProcessor {
    public static void processPayment(Account acc, Decimal amount) {
        if (amount > acc.Balance__c) {
            throw new InsufficientFundsException('Account balance too low');
        }
        
        // Process payment
        acc.Balance__c -= amount;
        update acc;
    }
}

// Handle custom exception
try {
    PaymentProcessor.processPayment(myAccount, 5000);
} catch (InsufficientFundsException e) {
    System.debug('Payment rejected: ' + e.getMessage());
    ShowUserMessage('Your account balance is insufficient');
} catch (Exception e) {
    System.debug('Unexpected error: ' + e.getMessage());
}
  

4. Custom Exception with Additional Context


public class ValidationException extends Exception {
    public List<String> errors { get; set; }
    
    public ValidationException(List<String> errorList) {
        this.errors = errorList;
        this.setMessage('Validation failed: ' + String.join(errorList, ', '));
    }
}

public class AccountValidator {
    public static void validateAccount(Account acc) {
        List<String> errors = new List<String>();
        
        if (String.isBlank(acc.Name)) {
            errors.add('Account name is required');
        }
        if (acc.Revenue__c < 0) {
            errors.add('Revenue cannot be negative');
        }
        if (String.isBlank(acc.Industry)) {
            errors.add('Industry is required');
        }
        
        if (!errors.isEmpty()) {
            throw new ValidationException(errors);
        }
    }
}

// Usage
try {
    AccountValidator.validateAccount(myAccount);
} catch (ValidationException e) {
    System.debug('Validation errors:');
    for (String err : e.errors) {
        System.debug('  - ' + err);
    }
}
  

5. Exception Handling Best Practices

Don't Silently Ignore Exceptions


// ❌ BAD - Swallows all errors
try {
    update accounts;
} catch (Exception e) {
    // Silent catch - error is lost!
}

// ✅ GOOD - Log or handle properly
try {
    update accounts;
} catch (DmlException e) {
    System.debug('DML Error: ' + e.getMessage());
    logErrorToCustomObject(e);
    throw e;  // Or re-throw if critical
}
  

Be Specific About Exception Types


// ❌ BAD - Catch-all hides real problems
try {
    insert accounts;
    update opportunities;
} catch (Exception e) {
    System.debug('Error: ' + e.getMessage());
}

// ✅ GOOD - Specific handling
try {
    insert accounts;
} catch (DmlException e) {
    handleDmlError(e);
}

try {
    update opportunities;
} catch (DmlException e) {
    handleDmlError(e);
}
  

Use finally for Cleanup


public static void processLargeDataset() {
    try {
        // Expensive operation
    } catch (Exception e) {
        System.debug('Error: ' + e.getMessage());
    } finally {
        // Always runs - cleanup here
        cleanupResources();
    }
}
  

6. Logging Exceptions

Always log exceptions for debugging and monitoring:


public class ErrorLogger {
    public static void logException(Exception e, String context) {
        Error_Log__c errorLog = new Error_Log__c(
            Exception_Type__c = e.getTypeName(),
            Exception_Message__c = e.getMessage(),
            Stack_Trace__c = e.getStackTraceString(),
            Context__c = context,
            User__c = UserInfo.getUserId(),
            Timestamp__c = Datetime.now()
        );
        insert errorLog;
    }
}

// Usage
try {
    update accounts;
} catch (DmlException e) {
    ErrorLogger.logException(e, 'AccountService.updateAccounts');
}
  

7. Re-throwing Exceptions


public static void processOrder(Order order) {
    try {
        validateOrder(order);
        update order;
    } catch (ValidationException e) {
        // Handle validation specifically
        System.debug('Validation failed: ' + e.getMessage());
        throw e;  // Re-throw to caller
    } catch (Exception e) {
        // Log unexpected errors
        System.debug('Unexpected error: ' + e.getMessage());
        throw new ProcessingException('Order processing failed');
    }
}

public class ProcessingException extends Exception { }
  

Key Takeaways