Stop, Drop, and Rollback
Smokey The Bear Says — Care will prevent 9 out of 10 Apex errors!
Exceptions note errors that disrupt the normal flow of code execution. Try/catch blocks provide a strong defense in finding resolutions for these exceptions.
When an exception occurs, code execution stops and all Data Manipulation Language (DML) operations processed before the exception are rolled back without being committed to the database. The Salesforce user may see an error message in the Salesforce user interface when exceptions remain unhandled.
Saving partially processed code data in Salesforce
There are a few assumptions when coding in Salesforce with regard to protecting the developer from saving partially processed data in Salesforce.
Any exceptions that happen in code (within the same entire execution) will roll back the entire execution so all database changes leading up to the failure will not be committed to the database. This is true for triggered code, page controllers, asynchronous code, etc. This was a GREAT thing as I started developing in Salesforce and really protected the data for clients.
The above is only true if you are:
Not using try/catch blocks around DML operations, which results in undesirable errors on top of standard pages or white-screen error pages from custom pages.
Using try/catch blocks and are properly handling the error inside the 'catch.’
My experience with using try/catch blocks in Salesforce coding
The more custom work I did, the more I realized I was using try/catch all over the place but didn't immediately understand the implications of using it. I've talked about this topic many times with other developers to explain my experiences, and it can be a complex topic to discuss.
Here are some hypothetical situations to explain what I mean.
Hypothetical use cases for the try/catch blocks:
You want to use try/catch in a page controller so you can show a friendly error on the screen for the user. There are two DML operations in the 'button click' action, and the error happens on the second DML. Tell the user there is an issue by adding a pagemessage. Did you do anything to undo the first DML, which was a success? Salesforce will not rollback that DML for you because you've essentially "caught" the error that would have done that. Result: Bad data. You need to roll back.
You've got a trigger on a case that will roll-up custom information to the account. This important logic will keep the data in check. But, sometimes there is an issue when you run the DML Update to the accounts, so you wrap the DML in a try/catch and perform a system.debug to figure things out a bit better. You've deployed things like this to production since it is very rare. The only handling you've done is a debug and you have caught the error, so you have told Salesforce not to roll-back the changes to the original cases that fired the trigger. You'll end up with cases (newer changes) out of sync with the account (failed update) in your database. The solution is that you need to attach a ".adderror()" to the appropriate case records in the trigger, which will tell Salesforce to roll back changes and will show your custom, more descriptive error to the user (or to the calling code / external system). Result: Bad data. You need to .addError
Apex Controller Examples (Lightning, Aura, LWC, Visualforce)
Good Example no try/catch
Standard exception handling, Salesforce handles database protection.
KEY: Do nothing extra (Good)
Bad Example with try/catch
Account will insert without the case.
Since we are using try/catch below, we are able to show the user a nicer error message with the ApexPages.addMessage method. BUT, we are now taking over the standard Salesforce pattern of "error and rollback.” We are also forgetting to roll back ourselves.
Good Example with try/catch
If Case fails, Account insert will roll back.
KEY: Using Database Rollback (Best Practice)
Apex Trigger Code Examples
Good Example no try/catch
Standard exception handling, Salesforce handles database protection.
KEY: Do nothing extra (Good)
Bad example with try/catch
Account update could fail, but we didn't stop the Cases from being inserted.
Good example with try/catch
If Account update fails, we flag the Cases with an error which will show to user and prevent the Case inserts.
KEY: Use .addError() method (Best Practice)
Best Practices for Using Try/Catch Blocks in Apex
Generic exception class
When using the generic exception class, it will catch any type of error besides limits and some other situations. Be careful not to put too much inside the same try block because it'll be more difficult to figure out what caused the error. The best practice would be to use specific exception classes like dmlexception and only put a single DML in that try block.
Testing your catch blocks
Make sure to test your catch blocks. In your manual tests you can configure a dummy "requirement" on an object or a validation rule you know will fail, or omit a required field. Then, attempt the custom button, standard button, or whatever it is that starts the code that should fail, and see how your catch block operates.
This could be more difficult for unit tests, but you could employ some special logic to conditionally fill in data or fail to fill in data in your code.
Say your controller would be looking for "Unit Test *** Fail DML Required Field." It could know to purposely clear a field on a record before the DML, which would then fire your catch block. Or, you have a unique field setup in an object, and you let your unit test try to insert two records with the same values in that field. There are many other ways to test, but it’s up to you!
Contact CRM Science Salesforce Consultants
At CRM Science, we use our Salesforce expertise to transform your enterprise. Partnering with our clients throughout the Salesforce journey, we strategize and optimize business processes, and develop solutions across every Salesforce cloud. CRM Science is an award-winning Salesforce Gold Consulting Partner and a Salesforce.org Registered Partner.
Mike Katulka Technical Architect CRM Science