[SalesForce] the correct way to return validation errors from a lightning Apex controller

What is the correct approach to return validation errors from lightning controller?

Suppose we have an aura method that takes some parameters. We need to validate these parameters in order to make sure that required values are provided, values are correct etc. When everything is correct we are fine. But what if some validations fail, how do we report them back to client?

As I see there are basically 3 options:

  1. Return a string with an error message.
  2. Return a serialized object that could hold either success or error flag + error message
  3. Throw an AuraHandledException.

Option #1 – Return a string with an error message##

    @AuraEnabled
    public static String start(String firstname, String lastname, String email) {
        if (String.isBlank(firstname)) {
            return 'First name is required';
        }
        // lastname and email validations skipped for brevity
        
        // When validations are done we want to return some value
        // that will be used in the lightning component later
        String someId = '';
        return someId;
    }

It is simple. The only drawback of this is in case you really need to return something when an action completes successfully, you should figure out something to differentiate error message from success message since in both cases the client will receive a String.

Option #2 – Return a serialized object##

public class ActionResult {
        
        @AuraEnabled
        public String someId { get; set; }

        @AuraEnabled
        public String errorMessage { get; set; }

        @AuraEnabled
        public Boolean isSuccess { get; set; }
    }
    
    @AuraEnabled
    public static ActionResult start(String firstname, String lastname, String email) {
        ActionResult result = new ActionResult();
        
        if (String.isBlank(firstname)) {
            result.isSuccess = false;
            result.errorMessage = 'First name is required';
            return result;
        }
        // lastname and email validations skipped for brevity

        // When validations are done we want to return some value
        // that will be used in the lightning component later
        String someId = '';
        
        result.isSuccess = false;
        result.someId = someId;
        return result;
    }

This is more verbose comparing to Option 1, yet it makes it easier to separate errors from success results.

Option #3 – Throw an AuraHandledException##

This is a different story and there is a whole blog post Error Handling Best Practices for Lightning and Apex

@AuraEnabled
public static String start(String firstname, String lastname, String email) {
    ActionResult result = new ActionResult();

    if (String.isBlank(firstname)) {
        throw new AuraHandledException('First name is required');
    }
    // lastname and email validations skipped for brevity

    // When validations are done we want to return some value
    // that will be used in the lightning component later
    String someId = '';

    return someId;
}

While this approach works, it doesn't feel right to use exceptions to something that is not exceptional in nature. While it might be good to throw exceptions when a dml operation failed or something unusual happend, with user input we expect bad values. Also when throwing AuraHandledException exceptions writing unit tests becomes a 'funny exercise':

@IsTest
    static void startTest() {
        
        // Test for null
        try {
            UPST_SelfRegisterFormController.initSelfRegistration(null, 'lastName', 'email');
        } catch (Exception ex) {
            System.assertEquals('First name is required', ex.getMessage());
        }

        // Test for an empty string
        try {
            UPST_SelfRegisterFormController.initSelfRegistration('', 'lastName', 'email');
        } catch (Exception ex) {
            System.assertEquals('First name is required', ex.getMessage());
        }
        
        // Same tests for last name and email
        // ...
    }

At yet one more thing, in order to actually see the exception message in unit test it should be set up a bit differently (Testing AuraHandledExceptions):

if (String.isBlank(firstname)) {
    String message = 'First name is required';
    AuraHandledException e = new AuraHandledException(message);
    e.setMessage(message);
    throw e;
}

Best Answer

Option #3 (AuraHandledException) is correct. Use this whenever you wish to signal to the client that the program flow was not successful. This makes your client code much simpler. This applies especially to LWC, because you end up using then/catch Promises to control program flow. There's no difference from an error generated because of a DML operation and an error because a user screwed up their input. In both cases, the system did not reach a successful conclusion, so an exception should be generated. The more usual way to write exceptions, by the way, is as follows:

throw new AuraHandledException('First Name is required.');
Related Topic