8

How should I handle multiple DmlException messages when using AuraHandledException inside an Apex Controller.

If I do this:

try {
    upsert value;
} catch(DmlException e) {
    throw new AuraHandledException(e.getMessage());
} catch(Exception e) {
    throw new AuraHandledException(e.getMessage());
}

I get an unfriendly error message, that the users won't like.

System.DmlException: Update failed. First exception on row 0 with id a0220000005vt34AAA; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION: bla bla bla

If I do this:

try {
    upsert value;
} catch(DmlException e) {
    throw e;
} catch(Exception e) {
    throw new AuraHandledException(e.getMessage());
}

I get an unfriendly error message which is even worse:

Unknown Error

If I do this:

try {
    upsert value;
} catch(DmlException e) {
    throw new AuraHandledException(e.getDmlMessage(0));
} catch(Exception e) {
    throw new AuraHandledException(e.getMessage());
}

I only get 1 of the DML errors and there could be many.

On the client-side I am using this JavaScript code to call controller actions and handle errors:

callAction: function (cmp, methodName, params, callback) {
    var action = cmp.get(methodName);
    action.setParams(params);
    action.setCallback(this, function (response) {
        var state = response.getState();
        if (cmp.isValid() && state === 'SUCCESS') {
            var result = response.getReturnValue();
            if (callback) callback(result);
        } else if (state === 'ERROR') {
            this.handleErrors(cmp, response.getError());
        }
    });
    $A.getCallback(function () {
        $A.enqueueAction(action);
    })();
},
handleErrors: function (cmp, errors) {
    let toastParams = {
        title: 'Error',
        message: 'Unknown error',
        type: 'error'
    };
    if (errors) {
        if (errors[0] && errors[0].message) {
            console.log(errors[0].message);
            toastParams.message = errors[0].message;
        }
    }
    let toastEvent = $A.get('e.force:showToast');
    toastEvent.setParams(toastParams);
    toastEvent.fire();
},

I cannot find the defacto solution or framework best practice for handling what must be a common scenario.

Ideally, I'd like a solution which can be used in the Apex controller.

Questions

  1. Is there a capability built into the Lightning framework to handle multiple DmlException's?
  2. Should the JavaScript handle the DmlException rethrow differently?
  3. Do I need to write some custom Apex to handle multiple DmlException's?

--

NOTE: this is not a duplicate of this question because he never got a satisfactory answer.

  • Could you not iterate over e.getDmlMessage to throw all errors in a loop or concatenate them? – Raul Apr 4 '19 at 9:34
  • @Raul obviously I could. I am enquiring to see if there are is a standard platform feature or a high quality solution out there. – Robs Apr 4 '19 at 9:53
  • I kind of ran into same issue and resorted to regular expression route. salesforce.stackexchange.com/questions/226338/… If the exception is thrown from deep inside helper class or somewhere then AuraHandledException doesn't work as expected and spits out ugly exception. – javanoob Apr 4 '19 at 13:29
  • 1
    Maybe we can do this all on the client side(JavaScript). – itzmukeshy7 Apr 4 '19 at 13:51
3

I would think of using Database.upsert() method which returns Database.UpsertResult[] where error messages can be collected and pass that to AuraHandledException.

You can capture all the errors together which you are facing issues above.

Database methods provide more flexibility that using straight DML statements.

try {
    List<String> lstErrorMsg = new List<String>();
    Database.UpsertResult[] results = Database.upsert(value,false);
    if (results != null){
        for (Database.UpsertResult result : results) {
            if (!result.isSuccess()) {
                Database.Error[] errs = result.getErrors();
                for(Database.Error err : errs)
                {
                    lstErrorMsg.add(err.getMessage());
                }
            }
        }
        if(lstErrorMsg.size()>0)
            throw new AuraHandledException(lstErrorMsg);
    }

} catch (Exception e) {
     throw new AuraHandledException(e.getMessage());
}

You can also think of using custom error message comparing the DMLException related errors and show it to the users, because system thrown DMLExceptions are unfriendly user messages.

You could create own framework and Utility classes to handle exceptions and reusing the above script.

| improve this answer | |
  • Thanks for responding and providing a solution. But surely you're not suggesting that everyone include 15 lines of code in every method to handle this? Also, this solution doesn't work if the insert/update is inside business logic which gets used by another part of the system which is not a lightning component, which often is the case i.e. a Service class which is used by a Queuable and InvocableMethod and a Lightning Component – Robs Apr 4 '19 at 9:00
  • I don't suggest to write 15 lines of code, rather I would prepare to create utility class to use this script as a method. Can you elaborate this line "Also, this solution doesn't work if the insert/update is inside business logic which gets consumed something than a lightning component, which often is the case"? You need to create your own framework to handle relevant exceptions. – Santanu Boral Apr 4 '19 at 9:03
  • I've updated my previous comment. In-addition: it's not always the case that you are doing the DML action in a controller, so throwing an AuraHandledException deep in your business logic would be bad practice. No offence, but I find this to be a low quality solution/answer to what must be a common problem, so there must be more standardise solutions... I could have whipped together some code the puts the errors into a list... – Robs Apr 4 '19 at 9:27
  • Also, is the answer a utility class or a custom exception based on DmlException or is there a platform solution or is it something else. If you are suggesting a utility class, why don't you provide one rather than a block of hacked together code which has very little use to the community in the future. Whereas, providing a utility class would help everyone who visits which page in the future... – Robs Apr 4 '19 at 9:30
2

These errors we can handle in the JavaScript part, and also for the Apex side as well we can do write some utility methods.

So inside the callback, we can parse the error we got from the server. For the same, sharing what I do use for the same, feel free to share your thoughts.

AuraBase lightning component and an apex class to handle all the background things parsing the response/errors and also logging errors/exceptions as needed in database/email.

handleErrors: function (c, errors) {
  var h = this;

  if (errors && Array.isArray(errors)) {
    var errorMessages = [];
    errors.forEach(function (error) {
      if (error.pageErrors && Array.isArray(error.pageErrors)) {
        error.pageErrors.forEach(function (pageError) {
          errorMessages.push(pageError.message);
        });

        if (errorMessages.length > 0) {
          h.warning(errorMessages.join(', '), { mode: 'sticky', title: 'Fix the errors.' });
          errorMessages = [];
        }
      }

      if (error.fieldErrors && Array.isArray(error.fieldErrors)) {
        error.fieldErrors.forEach(function (field) {
          error.fieldErrors[field].forEach(function (errorList) {
            errorMessages.push(errorList.message);
          });
        });

        if (errorMessages.length > 0) {
          h.warning(errorMessages.join(', '), { mode: 'sticky', title: 'Fix the errors.' });
          errorMessages = [];
        }
      }

      if (error.fieldErrors && Object.keys(error.fieldErrors)) {
        Object.keys(error.fieldErrors).forEach(function (field) {
          error.fieldErrors[field].forEach(function (errorList) {
            errorMessages.push(errorList.message);
          });
        });

        if (errorMessages.length > 0) {
          h.warning(errorMessages.join(', '), { mode: 'sticky', title: 'Fix the errors.' });
          errorMessages = [];
        }
      }


      var message = error.message || '';
      /* Add more statuses here that you want to parse, if you provide the value then it will show that value directly, instead of parsing the error, else it will format the error and will show to the user. */
      var statusCodes = {
        'CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY': '',
        'ENTITY_IS_DELETED': ''
      };

      var statusKeys = Object.keys(statusCodes);
      for (var statusCode = 0, totalCodes = statusKeys.length; statusCode < totalCodes; statusCode++) {
        try {
          if (message.indexOf(statusKeys[statusCode]) > -1) {
            if (!statusCodes[statusKeys[statusCode]]) {
              message = message.split(statusKeys[statusCode] + ', ')[1];
              var messageParts = message.split(': ');
              if (messageParts.length > 1) {
                messageParts.pop();
              }
              message = messageParts.join(': ');
            } else {
              message = statusCodes[statusKeys[statusCode]];
            }
            break;
          }
        } catch (statusCodeException) {
          break;
        }
      }
      errorMessages.push(message);
    });

    if (errorMessages.length > 0) {
      h.warning(errorMessages.join(', '), { mode: 'sticky' });
    }
  } else {
    h.warning('Something went wrong.', { mode: 'sticky', title: 'Contact System Administrator!' });
  }
}

And on the Apex side, we can use try{...}catch(){} to handle the exceptions and the JS part will handle all the errors 0/1/N.

try{
  Integer anError = 1/0;
}catch(Exception e){
  throw new AuraHandledException(e);
}
| improve this answer | |
1

A purely Apex solution would be to have a AuraHandledExceptionFactory which creates the AuraHandledException with a correctly formatted error message based on the Exception type it was caught.

Apex Controller

Simple one line usage

public with sharing class ApexController {

    @AuraEnabled
    public static String getAction() {

        try {
           // do something
        } catch (Exception cause) {
            throw AuraHandledExceptionFactory.create(cause);
        }
    }
}

AuraHandledExceptionFactory

Factory pattern used to generate the aproprate ExceptionHandler based on the type of Exception that has been provided.

public class AuraHandledExceptionFactory {

    public static AuraHandledException create(Exception cause) {
        Type handlerType = getType(cause);
        ExceptionHandler handler = newInstance(handlerType);
        return new AuraHandledException(handler.getMessage(cause));
    }

    private static ExceptionHandler newInstance(Type handlerType) {   
        try {
            return (ExceptionHandler) handlerType.newInstance();
        } catch (Exception ignore) {
            return new ExceptionHandler();
        }
    }

    private static Type getType(Exception cause) {
        return Type.forName(getTypeName(cause));
    }

    private static String getTypeName(Exception cause) {
        return cause.getTypeName() + 'Handler';
    }
}

ExceptionHandler

This virtual class can extended for different Exception types

public virtual class ExceptionHandler {
    public virtual String getMessage(Exception cause) {
        return cause.getMessage();
    }
}

DmlExceptionHandler

A custom implementation to handle the DmlException type.

public class DmlExceptionHandler extends ExceptionHandler {

    public override String getMessage(Exception cause) {

        DmlException dml = (DmlException) cause;

        String message = '';

        for(integer index = 0; index < dml.getNumDML(); index++) {
            // simple implementation
            message += dml.getDmlMessage(index);
        }
        return message;
    }
}

This approach follows the Single Responsibility Principle and the Open-Closed Principle by keeping each class clean and simple, while still enabling you to extend it to handle your own custom exception types.

Single Responsibility Principle

The single responsibility principle is that states that every class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.

Open-Closed Principle

The open-closed principle states classes should be open for extension, but closed for modification; that is, such an entity can allow its behaviour to be extended without modifying its source code.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.