Proper exception handling is crucial for building robust, maintainable Apex code. This guide covers try/catch blocks, custom exceptions, and best practices.
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');
}
}
}
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);
}
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());
}
try {
String name = null;
Integer length = name.length(); // Null reference
} catch (NullPointerException e) {
System.debug('Null value encountered');
}
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());
}
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());
}
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);
}
}
// ❌ 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
}
// ❌ 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);
}
public static void processLargeDataset() {
try {
// Expensive operation
} catch (Exception e) {
System.debug('Error: ' + e.getMessage());
} finally {
// Always runs - cleanup here
cleanupResources();
}
}
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');
}
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 { }