A catch (Exception e) block catches every kind of error Apex knows about, but most of the time only one kind can actually happen at the place you are guarding. Catching the specific type makes the code say exactly what went wrong, and it stops a different, unexpected failure from being swallowed by the wrong fallback.
A few built-in types come up over and over:
NullPointerException - you dereferenced a variable that held nullListException - the index you used is past the end of the list, or negativeDmlException - a insert, update, or delete failed for one or more recordsQueryException - a SOQL query found no rows when one was required, or the query itself was malformedMathException - a calculation went wrong, like dividing an Integer by zeroTypeException - a cast or valueOf call did not match the destination typeAll of them extend Exception, so a more specific catch always runs before a generic one in the same try:
try {
Account a = [SELECT Id FROM Account WHERE Id = :someId LIMIT 1];
update a;
} catch (QueryException e) {
System.debug('No account matched that id');
} catch (DmlException e) {
System.debug('Update failed: ' + e.getMessage());
}
Even when you catch a specific type, the variable still has useful methods on it. e.getTypeName() returns the name of the actual exception class that was thrown, which is handy when a catch (Exception e) covers several possible failures and you want to react differently to each:
try {
doSomethingRisky();
} catch (Exception e) {
if (e.getTypeName() == 'ListException') {
return Collections.emptyList();
}
throw e;
}
Catching the specific type you expect is clearer to read, and it lets unexpected exceptions keep propagating instead of being silently turned into a wrong answer. The built-in type names are also the names you will see in stack traces and debug logs, so recognising them shortens the time between a bug report and a fix.