An Introduction to Exception Handling
Abstract
When writing software, we spend a lot of time thinking about cases that we know will happen. But according to McConnell's Code Complete, studies have shown that up to 90% of programming is designed to deal with exceptions—error states where the program doesn't flow as expected. If those studies are right, error handling is perhaps the most important thing we should be doing as programmers. The book contains little further mention of exception states and dealing with them. Why is error handling such an ignored topic?
I've always had a nagging feeling that I wasn't following best practices in my exception handling in Apex. In most cases I wasn't doing any exception handling at all. I decided to write up what I have learned about error handling on the Force.com platform so that others could jump into it full-bore, and make their programs more robust and fault tolerant.
What is an exception?
An exception is a special condition that changes the normal flow of program execution. That is, it's when something bad happens that the program can't deal with during execution. Exceptions are the language's way of throwing up its hands and saying, "I can't deal with this, you need to."
So what kinds of conditions can cause Apex to raise, or throw, an exception? Here are the most common examples:
- Your code expects a value from something that is currently null
- An insert or update statement fails to pass a custom validation rule you have set
- Assigning a query that returns no records or more than one record to a singleton sObject variable
- Accessing a list index that is out of bounds
In all these instances we're trying something that the language deems impossible, and an exception is thrown. Apex has 20 different kinds of exceptions--that's a lot of different kinds of exceptions, but since they're all subclassed from a generic exception class they are very similar to deal with. All the exceptions support standard methods for accessing the error message and the exception type.
What happens when an exception occurs?
When an exception occurs, and you haven't written any code to deal with it, it's called 'unhandled.' First, an unhandled exception brings processing to a halt. If the code that has processed so far contained any Database Manipulation Language (DML) statements, those statements will be rolled back completely.
The system then notifies the running User of the problem. If you run into an exception in Apex code while using the standard user interface, a red text message will appear at the top of the screen showing you the text of the unhandled exception. If the exception was caused by a custom field validation error and you've set that to show next to the field that caused the error, a red message will show up there.
The system will then try to notify the developer of the code in question that there has been an unhandled exception. An email will get sent to the developer with the Organization Id and User Id of the running user, as well as the exception message.
How do I catch exceptions?
Uncaught exceptions are less than ideal. They're kind of like the Windows "blue screen of death"--not the most graceful end to a program's functioning you could imagine. The error message the end user sees might not make much sense to them, and who knows what kind of state the data is in. This section looks at how to catch the exceptions, so that you can insert some code to handle it in some way. The following section will look at techniques for handling the exception.
The good news is that Apex allows for you to handle your exceptions, and write code to gracefully recover from an error. Apex uses the Try, Catch, Finally construct common to many other programming languages. You "try" to run your code. If there is an exception, you "catch" it and can run some code, then you can "finally" run some code whether you had an exception or not.
You can have multiple Catch blocks to catch any of the 20 different kinds of exceptions. If you use a generic exception catcher, it must be the last Catch block.
Here's what a try-catch-finally block looks like:
try{
//Your code here
} catch (ListException e) {
//Optional catch of a specific exception type
//Specific exception handling code here
} catch (Exception e) {
//Generic exception handling code here
} finally {
//optional finally block
//code to run whether there is an exception or not
}
Here's a try-catch example for an Apex callout:
try{
HttpResponse res = http.send(req);
} catch (System.CalloutException e){
System.debug('ERROR:' + e);
}
How do I handle exceptions that I've caught?
As I mentioned above, the developer of the code gets notified of uncaught exceptions. The running user gets a notice as well, either in the standard user interface, or if they are using a Web Services API application, they'll get the notice if the developer has chosen to expose the message.
When you catch exceptions, you lose the default notification mechanisms that exist with uncaught exceptions—you'll need to build your own. Here are some ways in which you can handle exceptions that you catch, and notify folks of the problem.
DML
Exceptions can occur in DML statements. For example, you may try to insert a record without a required field. The addError() method can be called on a record or a field and will prevent the DML operation from committing.
Here's an example:
try{
update accounts;
} catch (DMLException e){
for (Account account : accounts) {
account.addError('There was a problem updating the accounts');
}
} finally {
inProgress = false;
}
In this example we're adding a message to the first objects in the list of Accounts. If this trigger exception came about by a user interacting with data through the standard user interface, then the message we've added will show up in red at the top of the screen. If this trigger exception came about through use of the Web Services API, the developer of the application can show the user these messages. In the Dataloader, for example, the addError() message will show up in a new column in the returned CSV file.
If you use addError() this way, you will bring to a halt all post-commit processing, like the sending of emails and callouts.
That works. But there is another way to catch exceptions in DMLs. The DML statement above is all or nothing. If there is an exception in any of the objects in the array, the whole DML transaction will rollback. We can write an update that allows for success of all records that update correctly, and will record errors for those that don't—it's called partial processing. It looks like this:
Database.insert(contacts, false);
Let's say I have a trigger that fires on Account update and allows for partial processing. If an exception occurs in that trigger, the following code will catch it and then use addError to notify the user:
Database.update(accounts, false);
The thing about partial processing is that even if individual records in the update cause exceptions, an exception won't be thrown. To deal with individual exceptions in this manner, we can do something like this:
Database.SaveResult[] lsr = Database.update(accounts, false);
for(Database.SaveResult sr : lsr){
if (!sr.isSuccess()) {
myMethodToaddToErrorObjectList(sr);
}
}
Here we try to update a list of Accounts. The Database.update won't throw an exception, because we're allowing for partial processing. We then loop through the save result looking for errors. We can pass the save result to a method that will do some processing of the error records. And because we're doing partial processing, we won't stop the post-commit processing like the sending of emails.
One thing to realize about the partial processing method--automatic rollback of DMLs is disabled when you use it. If you switch from all or nothing to partial processing, you may need to change your code to handle your own rollback using Apex transaction control.
Visualforce
If you have a custom controller or controller extension for a Visualforce page, you can handle exceptions just as in the examples above. To show the error on a Visualforce page, you can use the ApexPages.message class to create a message for display. Here's an example of a message created in a controller:
ApexPages.Message myMsg = new ApexPages.Message(ApexPages.Severity.FATAL,'my error msg'); ApexPages.addMessage(myMsg);
The message will display on the Visualforce page if you include the Message component somewhere on the page:
<apex:pageMessages/>
Sending an Email on exception
In addition to displaying the message to the user via page messages, you can also notify the developer by email if you like. Here's an example:
try{
update account;
} catch (DMLException e){
ApexPages.addMessages(e);
Messaging.SingleEmailMessagemail=new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {'developer@acme.com'};
mail.setToAddresses(toAddresses);
mail.setReplyTo('developer@acme.com');
mail.setSenderDisplayName('Apex error message');
mail.setSubject('Error from Org : ' + UserInfo.getOrganizationName());
mail.setPlainTextBody(e.getMessage());
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
You could use this technique to ensure that error messages are sent to an error mailing list for example.
Logging in a custom object
If you get enough emails already, another option would be to use a future method to write a custom object that could catch the error details. Your try-catch would look something like this:
try{
throw new MyException('something bad happened!');
} catch (MyException e){
ApexPages.Message myMsg = new ApexPages.Message(ApexPages.Severity.FATAL,'my error msg');
futureCreateErrorLog.createErrorRecord(e.getMessage());
}
Your future class would look something like this.
global class futureCreateErrorLog {
@future
public static void createErrorRecord(string exceptionMessage){
myErrorObj__c newErrorRecord = new myErrorObj__c();
newErrorRecord.details__c = exceptionMessage;
Database.insert(newErrorRecord,false);
}
}
You could then set up workflow for email notifications off the record, and use Force.com's powerful analytics to dig into the exceptions you're getting.
How do I use custom exceptions?
You may decide that you want to interrupt your program flow even when a system exception does not occur. In that case you can throw your own custom exception. It's very easy to do - simply create a new exception class by extending the Exception class. Here's an example of a custom exception, and some code that throws the exception:
//define your custom exception
public class MyException extends Exception{}
//try, throw and catch
try {
Integer i;
//Your code here
if ( i < 5 ) throw new MyException();
} catch ( MyException e ) {
//Your MyException handling code here
}
Custom exceptions give you the flexibility to throw exceptions under whatever conditions make sense to you.
What about exceptions I can't catch?
You can't catch all exceptions. Tripping governor limits causes a halt of all processing, so no graceful recovery is possible. There are Limit methods that you can use to see the wall of governor limits coming up on you. Limits.getDMLRows() and Limits.getDMLStatements() will tell you how many rows you've touched and how many individual DML statements you've made. You can compare these numbers with the absolute limits which are returned by Limits.getLimitDMLRows() and Limits.getLimitDMLStatements(), respectively.
Governor limits are best understood by thoroughly reading the documentation: Understanding Execution Governors and Limits
Summary
Exception handling is a very important aspect of your application, and one that is often overlooked. The good news is Apex makes it easy to get going on handling your own exceptions. Use try-catch-finally statements to catch system exceptions as they occur. Use custom exceptions to throw your own and gracefully adapt even when the system doesn't object to program flow. These techniques will give your users a better experience.
You can keep the user aware of any errors you run into via Apex page messages in Visualforce and object .addError() messages in Apex triggers. Should you want to be notified as the developer, you can write Apex email notifications or write your error to a custom object so you know when your code is misbehaving.
Apex has a robust framework for exception handling, start using it today in your triggers and classes—your programs will be better for it!
About the Author
Steve Andersen is a Solutions Architect at the Salesforce.com Foundation where he builds the Nonprofit Starter Pack--a set of Force.com customizations in use by thousands of nonprofits around the world. He actively blogs about Salesforce.com and nonprofits - and is going to Rwanda in February 2010 to start learning about the possibilities for cloud computing in rural Africa.