Exceptions & Error Handling
Custom exception class names must end in Exception and extend Exception.
10.1 try / catch / finally
try {
Account a = [SELECT Id FROM Account WHERE Name = 'Does Not Exist']; // QueryException: no rows
} catch (QueryException e) {
System.debug(LoggingLevel.ERROR, 'Query failed: ' + e.getMessage());
} catch (Exception e) {
System.debug('Something else: ' + e.getTypeName() + ' ' + e.getStackTraceString());
} finally {
System.debug('Always runs');
}10.2 Common built-in exceptions
Dereferencing a null variable Assigning a zero- or multi-row query to a single sObject
Validation rule failure, required field missing, duplicate value
Index out of bounds, duplicate IDs in a DML list Invalid cast or conversion (Integer.valueOf('abc')) Division by zero Invalid ID string, bad string operation
Governor limit exceeded — cannot be caught HTTP callout failure, callout after uncommitted DML Accessing a field that was not queried
User-mode or WITH SECURITY_ENFORCED access violation Thrown to send a friendly message to LWC/Aura clients
10.3 Custom exceptions
Custom exception class names must end in Exception and extend Exception. They automatically get four constructors: (), (String message), (Exception cause), (String message, Exception cause).
Custom exceptions can carry extra data:
public class InsufficientFundsException extends Exception {}
public class BankAccount {
public Decimal balance { get; private set; }
public BankAccount(Decimal opening) { balance = opening; }
public void withdraw(Decimal amount) {
if (amount > balance) {
throw new InsufficientFundsException(
'Cannot withdraw ' + amount + '; balance is ' + balance);
}
balance -= amount;
}
}
// usage
try {
new BankAccount(100).withdraw(500);
} catch (InsufficientFundsException e) {
System.debug(e.getMessage());
}public class IntegrationException extends Exception {
public Integer statusCode;
public IntegrationException(String msg, Integer code) {
this(msg); // call the built-in message constructor
this.statusCode = code;
}
}10.4 Error-handling guidelines
Catch specific exceptions; avoid empty catch blocks that hide failures.
An unhandled exception rolls back the whole transaction (all DML) and shows an error to the user or sends an email to the developer (for async jobs).
In triggers, prefer record.addError('message') to block a single record with a friendly message rather than throwing.
LimitException can't be caught — prevent it by bulkifying and checking Limits (§21).
Log errors persistently (custom Log__c object, Platform Event-based logger, or an open-source framework such as Nebula Logger) rather than relying on debug logs.
🧪 Part I Exercises
Sign up for a Developer Edition org, install the Salesforce CLI and VS Code extensions, create a project, authorize the org, and deploy a HelloWorld class.
In Execute Anonymous, print the first 20 Fibonacci numbers using a Long and a while loop.
Write a static method isPalindrome(String s) that ignores case, spaces, and punctuation; test it with three inputs.
Given a List<Integer>, return a Map<String, List<Integer>> with keys 'even' and 'odd'.
Build a Set<String> of unique email domains from all Contacts in your org (substringAfter('@')), lowercased.
Write a method that returns the number of business days (Mon–Fri) between two Date values without using BusinessHours.
Create an enum Priority { LOW, MEDIUM, HIGH, CRITICAL } and a switch that maps each value to an SLA in hours.
Write Decimal applyDiscount(Decimal price, Decimal percent) that rounds to 2 places with HALF_UP and throws a custom InvalidDiscountException for percentages outside 0–100.
Demonstrate the difference between == and equals() for strings, and between ?. and normal dot access on a null sObject.
Write a test class with at least three test methods for exercises 3, 4, and 8 using the Assert class; run it from the CLI with code coverage.