Apex Trigger Exceptions

Custom Apex Triggers execute on standard CRM objects (Account, Contact, Lead etc.) and custom objects in response to all data modifications applied via the Salesforce web application, API transactions and custom Apex script. As such it is imperative that trigger code adheres to patterns that promote performance and maintainability, guards against recursive behaviour and most critically protects data integrity.

Apex code outside of triggers (Batch Apex, Visualforce Controllers etc.) is completely isolated to the specific context, trigger code is not; the effect of errant code is felt across the standard web app, mobile apps, API-based integration flows, data loads and so on. As such triggers must be used judiciously and avoided wherever possible on the core standard objects. I often come across multiple complex triggers defined on Account, where the logic applied is non-time critical and could have been handled by Batch Apex, or indeed by declarative methods. In certain circumstances this idealistic view is impractical and triggers are a necessary friend. When writing triggers always stick to a proven pattern, good examples provided by community experts are easy to find, and read the relevant sections in the Apex Language Reference if you haven’t done so for a while.

This post aims to provide some practical insight related to exception behaviour within Apex Triggers that may not be obvious from the referenced resources.

Key Concepts
1. DML Processing Mode
All bulk DML transactions on the platform run in either allOrNothing or partial processing mode. In some cases the behaviour is implicit and fixed, in other cases flags can be set to override the default behaviour.

allOrNothing
– Apex Database statements (e.g. update contactsToUpdate;)

partial processing
– Apex Database methods (Database.update(contactsToUpdate);. The default behaviour can be overridden via DmlOptions.
– API (default behaviour – can be overridden)

2. .addError()
The .addError(custom message) method can be invoked at the record or field level within the trigger context to flag a record as invalid. This is particularly useful in asserting logical errors. A best practice in regard to the custom messages is to use Custom Labels with a defined Apex Trigger Error category.

3. Runtime Exceptions
If an unhandled runtime exception is thrown then the Apex Transaction will immediately rollback and Apex exception notifications will occur. Structured exception handling can be added to the trigger code to catch runtime exceptions and to apply custom handling logic.

Apex Trigger – Exception Test Cases
The following test cases highlight the impact of the .addError() method and runtime exceptions in the context of the 2 processing modes. Tests were conducted using Execute Anonymous and the Apex Data Loader at API version 30.0.

Test Case 1 – Does code continue after .addError()?
Result: Yes (this is necessary in both allOrNothing and partial processing cases to gather a complete set of errors).

Test Case 2.1 – all_or_none – Does .addError() rollback the transaction if any subset of records has .addError() applied?
Result: Yes – Full rollback.

Test Case 2.2 – all_or_none – Does .addError() rollback the transaction if all records have .addError() applied?
Result: Yes – Full rollback.

Test Case 2.3 – all_or_none – Does an un-handled runtime exception rollback the transaction if no records have .addError() applied?
Result: Yes – Full rollback.

Test Case 2.4 – all_or_none – Does a handled runtime exception rollback the transaction if no records have .addError() applied?
Result: No – Records are committed (complete trigger context – regardless of which record caused the runtime exception).

Test Case 3.1 – partial – Does .addError() rollback the transaction if any subset of records has .addError() applied?
Result: Yes – Full rollback of all uncommitted changes, the trigger fires again for the subset of records that have not had addError() applied. Up to 2 retries are performed, after which the “Too many batch retries in the presence of Apex triggers and partial failures.” exception is thrown.

Test Case 3.2 – partial – Does .addError() rollback the transaction if all records have .addError() applied?
Result: Yes – Full rollback.

Test Case 3.3 – partial – Does an un-handled runtime exception rollback the transaction if no records have .addError() applied?
Result: Yes – Full rollback.

Test Case 3.4 – partial – Does a handled runtime exception rollback the transaction if no records have .addError() applied?
Result: No – Records are committed (complete trigger context – regardless of which record caused the runtime exception).

Test Case 4 – partial – In a partial failure case are static variables set within the original trigger invocation reset before retry invocations?
Result No – Static variables are not reset between trigger invocations. This means that statics guard variables defined to protect against recursive behaviour will stop trigger logic being applied to records processed by retry invocations.

Conclusions
1) In all cases .addError() results in the full Apex Transaction being rolled back. Custom logging schemes such as writing exceptions to a Custom Object (including via @future) or sending notification emails are futile gestures when employed in conjunction with .addError().

2) Handled runtime exceptions result in the Apex Transaction being committed, unless .addError() is applied to at least one record.

3) In the partial processing case, governor limits are reset to their original state for each retry trigger invocation, static variables are not.

4) Where static guard variables are used to protect against re-execution of the trigger logic via field updates within update DML transactions, partial processing retry trigger invocations will also be blocked. Such invocations are consistent with those initiated by workflow field updates later in the order of execution.

In order to distinguish between a field update trigger execution and a partial processing retry it becomes difficult to use static variables – batch sizes can be equivalent, sequencing is unpredictable etc.

One possible approach is to use a custom field on the target object (Is Workflow Processing?), a field update action would be added to set this to “True” alongside the existing actions. In the trigger code we can now reliably test this field state to identify a workflow field update initiated trigger execution. The trigger code must always reset the field to “False” before returning, such that any partial processing retry executions safely proceed. Where trigger logic resides in an after update trigger, a before update trigger could be added simply for convenience of reseting the field – in this case a static could be set in the before context and referenced in the after context.

It’s possible there are more efficient ways to provide a robust solution to this. A complete Apex based solution would definitely be preferable to the custom field based solution.

Salesforce Spring ’14 Highlights

After a short period of blindly exploring a pre-release Spring ’14 org, the release notes are now available.

With Salesforce1 released recently it’s unsurprising that Spring ’14 isn’t a significant release in terms of new features and that a lot of the highlights are currently at Pilot status. I expected a low-key release with incremental improvements to Salesforce1, the usual evolution to Chatter and a number of older pre-release features becoming GA – this would seem to be the case. A few personal highlights below in no particular order, following a quick read of the release notes.

Skills and Endorsements (Pilot) – Skills reference data (ProfileSkill) can be added and associated with People (ProfileSkillUser). The skills profile for a User is displayed on the Chatter profile page and each skill can be endorsed by other users (ProfileSkillEndorsement). Skills can be managed via Chatter or via the Setup pages (Manage Users etc.). Interesting LinkedIn style concept.

Feed-Based Page Layouts – The Case Feed experience now available for Account, Contact, Lead, Opportunity and Custom Objects. The concept here is to split the record feed from the record details via tabs, enabling a more focused view with easy switching. For implementations where Chatter is used extensively at the record level, this provides a convenient efficiency.

Sharing Sets Enhancements (Pilot) – Sharing sets for HVPU now support indirect lookups to Account, Contact, Case, Service Contract, User and Custom Object records. Previously only directly related records to the Account and Contact were accessible, now a second level of related records can be accessed.

Launch Flows from Workflow (Flow Triggers) – A new Flow Trigger Workflow Action has been added which enables UI-less flows to be executed as per standard workflow actions (email, task etc.). This could provide a non-code alternative to Apex Triggers in some cases.

Orders / Place Order REST API – Basic standard objects and accompanying REST API to support the order management process (contract, orders and order products). There doesn’t appear to be any relationship (or automated synchronisation) with Opportunity or Quote. Definitely a feature intended for customisation.

API Limit App Quotas (Pilot) – Connected Apps can have 24 hour API quotas set for standard, bulk and streaming API consumption. I like where this is heading in terms of providing proactive API management tools – up to this point it’s all been reactive.

New User Interface for Monitoring Deployments – Clean new UI to view (and cancel) running deployments made via the metadata API.

Visualforce Remote Objects (Developer Preview) – Data proxies for standard objects that make SObject DML operations directly accessible in JavaScript code without the need for @RemoteAction methods. Another highly useful feature for developing in JavaScript which also avoids API limit consumption (as per JavaScript remoting).

Canvas App Accessible from the Salesforce1 Menu – Force.com Canvas apps can now be placed directly into the Salesforce1 navigation menu. I think we’ll see Canvas becoming a more important technology in the future as integration at the application level becomes more widespread. Where some degree of application blending is achievable, UI-level integration can be significantly less expensive to implement than at the data-level.

Independent Auto-Number Sequence for Unit Tests – It’s always puzzled me as to why the auto-number sequence increments in response to the creation of test data records (which are ultimately rolled-back). Surely, the unit test context should be better isolated? Anyway, with Spring ’14 we can now set the flag below to achieve this.

Develop->Apex Test Execution->Options->Independent Auto-Number Sequence

Usage Metrics (Pilot) – ISVs can now access the following metrics (via API only) per subscriber org:

Record count for custom objects
Access count for Visualforce pages (over the last 24 hours)

This feature requires an Environment Hub setup, with one org designated as the reporting org where the metrics are delivered. This long awaited feature will be well received by the ISV community.

View and Manage User Sessions – View and invalidate (end) user sessions as required. A useful administrator feature.

Mass Assign Permission Sets – At long last users can be assigned-to and removed-from Permission Sets en-masse. Previously this was a painful user-at-a-time process.

Analytics API Available in Apex – All the capabilities of the REST Analytics API are now available directly to Apex, enabling reports to run etc. (..Reports.ReportManager.runReport(myReportId)..).