My question was previously asked but the answer in that case was unrelated. In my case, I have a class that can raise custom exceptions, and the class may be called from trigger context. When that happens, the calling code doesn't get the custom exception, but instead gets a DMLException. Per the docs, Exception.getCause(), "Returns the cause of the exception as an exception object," but it's always returning null. In the debug log, I can see the original exception, for example:

System.DmlException: Update failed. First exception on row 0 with id a56W0000000UCWMIA4; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, FMZ_DisbursementTrigger: execution of AfterUpdate

caused by: FMZ_Loan.InvalidTransactionDateException: New transaction would occur before latest transaction date of: 6/28/2017

FMZ_Loan.InvalidTransactionDateException is the custom exception I'm trying to detect. For example, from a unit test:

boolean caught = false;
try {
    update disb;
catch(FMZ_Loan.InvalidTransactionDateException tdEx) {
    caught = true;  // THIS LINE NEVER REACHED - DMLException instead
catch(DMLException dmlEx) {
    Exception cause = dmlEx.getCause();
    System.assertNotEquals(null, cause);  // THIS FAILS
    System.assert(cause instanceof FMZ_Loan.InvalidTransactionDateException);
    caught = true;

This isn't just a testing issue; other code needs to understand why an update may have failed. How do I get the root cause of a DMLException?

  • 1
    I think it's by design, but I'll need to test this.
    – sfdcfox
    Commented Feb 26, 2018 at 20:35
  • I was messing around with writing a custom log handler and found that when the transaction fired a trigger, the stack trace wouldn't show anything before entering the trigger. It would display everything normally from there, unless it entered another trigger, in which case it would start over again from there. I'm wondering whether what you're seeing is related. @sfdcfox, did you ever do any research on this? Very curious about the reason, but I haven't found anything telling. Commented Oct 12, 2018 at 13:50
  • @ShaneSteinfeld It's a little disappointing... give me a few minutes, I'll gather my findings.
    – sfdcfox
    Commented Oct 12, 2018 at 14:06
  • @sfdcfox This isn't a duplicate... my question was asked in Feb; the linked question was asked in September. Commented Oct 15, 2018 at 14:39
  • @JasonClark While it's true your question is much older, the other question was answered earlier. It doesn't really matter (you'll still get rep points for upvotes), it tends to be more important which answer was first. Older Question is a Duplicate of a Newer Question? from one of our parent sites kind of goes over this. True, I did answer your question, but the linked question actually provides the same data (and more, actually), and was answered about a month ago. Changing the duplicate flag to the other question doesn't change much.
    – sfdcfox
    Commented Oct 15, 2018 at 14:46

1 Answer 1


Each DML operation (including nested operations) constitutes an entire transaction, including a rollback point. Part of the magic of DML statements is that when an exception is thrown and not caught, it is converted to a DmlException.

This is done by necessity so that API clients are able to understand the error. During the Summer '18 release, a bug appeared briefly where an uncaught exception in a trigger was also cancelling the entire transaction without explanation, despite an outer try-catch block, but this appears to be fixed in Winter '19.

Long story short, you must use the only approved mechanism for communicating errors: the SObject addError method. It should also be noted that a try-catch DML operation is not bulk-friendly. Using exceptions to try to communicate messages may drastically harm performance in certain situations, and will make it harder to diagnose problems, since you'll have no idea which row caused the problem.

As for figuring out why an operation failed? Read the error messages. That's what they are there for. A proper error message will include the field that failed as well as the message. This is all you should really need. Use custom labels if you need to support multiple languages.

Not the answer you're looking for? Browse other questions tagged .