Apex Built-In Exceptions and Common Methods

Salesforce Apex is a powerful programming language specifically designed for building robust and scalable applications on the Salesforce platform. One key aspect of Apex development is handling exceptions effectively to ensure the reliability and stability of your code. In this article, we will delve into Salesforce Apex built-in exceptions and common methods used for exception handling.

Understanding Exceptions in Apex

In the realm of programming, an exception is an unexpected event or error that disrupts the normal flow of code execution. Exceptions can occur for various reasons, such as invalid user input, database connectivity issues, or unexpected system behavior. In Apex, exceptions are represented as objects of the Exception class or its subclasses.

Apex Exception Class Hierarchy

The Exception class serves as the base class for all exceptions in Apex. Salesforce provides a hierarchy of exception classes that extend the Exception class, each addressing specific types of errors or scenarios. Some notable exception classes include:

  1. DmlException: This exception is thrown for errors related to Database Manipulation Language (DML) operations, such as inserts, updates, and deletes.
  2. LimitException: Thrown when a governor limit is exceeded. Apex enforces various limits to ensure efficient resource usage, and exceeding these limits results in a LimitException.
  3. QueryException: Raised when issues occur during database queries.
  4. MathException: Used for mathematical operation errors, such as divide-by-zero.
  5. ListException and NullPointerException: These exceptions handle errors related to lists and null references, respectively.
  6. SObjectException: An SObjectException is thrown when there’s an issue with an SObject, such as creating, updating, or deleting records.
  7. AuraHandledException: Specific to Lightning components, this exception is used for handling errors in the Aura framework.

1. DmlException:

The DmlException is thrown when an error occurs during a Database Manipulation Language (DML) operation. DML operations include inserting, updating, deleting, and merging records using the Database or System classes.

Example: Let’s consider a scenario where a developer attempts to insert a record with a duplicate value in a unique field, resulting in a DmlException. The catch block will handle the exception and log relevant information.

public class AccountHandler {
    public static void createAccount(String accountName) {
        try {
            // Check if an account with the same name already exists
            List<Account> existingAccounts = [SELECT Id FROM Account WHERE Name = :accountName LIMIT 1];

            if (!existingAccounts.isEmpty()) {
                // Attempting to insert a record with a duplicate name, which will result in a DmlException
                throw new DmlException('Account with the name "' + accountName + '" already exists.');
            }

            // Create a new account
            Account newAccount = new Account(Name = accountName);
            insert newAccount;
        } catch (DmlException de) {
            // Log the DmlException
            System.debug('DmlException: ' + de.getMessage());
        }
    }
}

In this example, the createAccount method first queries the existing accounts with the specified name. If an account with the same name is found, a DmlException is thrown during the attempt to insert a new account. The catch block handles the exception, logging a message indicating that an account with the same name already exists. This illustrates how DmlException can be used to handle scenarios where DML operations encounter issues, such as violating unique constraints.

2. LimitException:

Description: The LimitException is thrown when a governor limit is exceeded. Governor limits are in place to ensure efficient resource usage on the Salesforce platform, and exceeding these limits can lead to a LimitException.

Example: Consider a situation where a Salesforce Apex batch job processes a large volume of records, and the cumulative execution time exceeds the maximum limit allowed. This could result in a LimitException.

global class MyBatch implements Database.Batchable<sObject> {
    global Database.QueryLocator start(Database.BatchableContext BC) {
        // Query large set of records
        return Database.getQueryLocator('SELECT Id FROM MyObject__c');
    }

    global void execute(Database.BatchableContext BC, List<MyObject__c> scope) {
        // Process records
        // ...

        // Simulate a long-running operation that exceeds the execution time limit
        for (Integer i = 0; i < 100000; i++) {
            // Some processing logic
        }
    }

    global void finish(Database.BatchableContext BC) {
        // Finalize batch processing
    }
}

In this example, the lengthy processing logic within the execute method could lead to a LimitException if the cumulative execution time exceeds the governor limit.

3. QueryException:

A QueryException in Salesforce Apex is thrown when there’s an issue with a SOQL query. One common scenario is when a query designed to return a single record, such as using the LIMIT 1 clause, actually returns more than one record or no records at all. Let’s look at an example:

try {
    // This statement doesn't cause an exception, even though 
    // we don't have a Contact with name='XYZ'.
    // The list will just be empty.
    List<Contact> lm = [SELECT Name FROM Contact WHERE Name = 'XYZ'];
    // lm.size() is 0 
    System.debug(lm.size());
    
    // However, this statement causes a QueryException because 
    // we're assiging the return value to a Contact object
    // but no Contact is returned.
    Contact c = [SELECT Name FROM Contact WHERE Name = 'XYZ' LIMIT 1];
} catch(QueryException qe) {
    System.debug('The following exception has occurred: ' + qe.getMessage());    
}

4. MathException:

The MathException is thrown for mathematical operation errors, such as attempting to divide by zero.

Example: Consider a scenario where a developer performs a division operation and encounters a situation where the denominator is zero.

public Decimal divideNumbers(Decimal numerator, Decimal denominator) {
    try {
        // Attempt to divide by zero
        return numerator / denominator;
    } catch (MathException me) {
        // Log the math exception
        System.debug('Math Exception: ' + me.getMessage());
        return null;
    }
}

In this example, if the denominator parameter is zero, a MathException will be thrown, and the catch block allows for handling the exception and logging relevant information.

5. ListException / NullPointerException

The ListException is thrown for errors related to lists in Salesforce Apex. This can include scenarios such as trying to access an index that is out of bounds.

Example: Consider a scenario where a developer attempts to access an element at an index that does not exist in a list.

public Integer getElementAtIndex(List<Integer> integerList, Integer index) {
    try {
        // Access an element at the specified index
        return integerList[index];
    } catch (ListException le) {
        // Log the list exception
        System.debug('List Exception: ' + le.getMessage());
        return null;
    }
}

In this example, if the index parameter is greater than or equal to the size of the integerList, a ListException will be thrown and caught in the catch block.

A NullPointerException occurs when a developer tries to access or manipulate the attributes or methods of an object that is null, meaning it doesn’t reference any memory location. In Apex, this commonly happens when attempting to perform operations on variables that have not been initialized or are explicitly set to null.

public class NullPointerExceptionExample {
    public static void main() {
        String myString = null;

        try {
            // This line will throw a NullPointerException
            Integer length = myString.length();
        } catch (NullPointerException e) {
            System.debug('NullPointerException: ' + e.getMessage());
        }
    }
}

In this example, trying to get the length of myString when it is null will result in a NullPointerException.

6. SObjectException:

Description: An SObjectException is thrown when there’s an issue with an SObject, such as creating, updating, or deleting records. This exception can be raised for various reasons, including validation rule failures, missing required fields, or attempting to perform unsupported operations on SObjects.

Example: In this example, we’ll query an Invoice_Statement__c object, selecting only its Name field. Then, we’ll attempt to access the Description__c field, which was not queried, resulting in an SObjectException.

public class SObjectExceptionExample {
    public static void main() {
        try {
            // Query an Invoice_Statement__c object, selecting only the Name field
            List<Invoice_Statement__c> invoices = [SELECT Name FROM Invoice_Statement__c LIMIT 1];

            // Attempt to access the Description__c field, which wasn't queried
            String description = invoices[0].Description__c;
        } catch (SObjectException e) {
            System.debug('SObjectException: ' + e.getMessage());
        }
    }
}

The catch block logs the SObjectException message, indicating that the Description__c field was attempted to be accessed without being queried. This exception provides valuable information for developers to identify and rectify issues related to querying fields in Salesforce Apex.

7. AuraHandledException:

The AuraHandledException is specific to Lightning components in Salesforce. It is used for handling errors within the Aura framework.

Example: Consider a scenario where a Lightning component makes a server-side call and encounters an error that needs to be communicated to the client-side component for user feedback.

public class MyLightningController {
    @AuraEnabled
    public static String performAction() {
        try {
            // Some logic that might throw an exception
            throw new AuraHandledException('An error occurred while performing the action.');
        } catch (AuraHandledException ae) {
            // Log the AuraHandledException
            System.debug('AuraHandledException: ' + ae.getMessage());
            // Rethrow the exception to propagate it to the client-side component
            throw ae;
        }
    }
}

In this example, if an error occurs during the execution of the server-side action, an AuraHandledException is thrown and caught. The catch block logs the exception, and the exception is rethrown to communicate the error to the client-side Lightning component.

Common Methods for Exception Handling

Now, let’s explore some commonly used methods for handling exceptions in Salesforce Apex.

1. getMessage() Method

The getMessage() method, inherited from the Exception class, is used to retrieve the error message associated with an exception. This method returns a string representing the error message that can be logged, displayed to users, or used for further diagnostics.

try {
    // Code that may throw an exception
} catch (Exception e) {
    String errorMessage = e.getMessage();
    System.debug('Error Message: ' + errorMessage);
}

2. getStackTraceString() Method

The getStackTraceString() method returns a string representation of the stack trace associated with an exception. The stack trace provides information about the sequence of method calls that led to the exception, aiding developers in identifying the root cause of the error.

try {
    // Code that may throw an exception
} catch (Exception e) {
    String stackTrace = e.getStackTraceString();
    System.debug('Stack Trace: ' + stackTrace);
}

3. getCause() Method

The getCause() method returns the cause of the exception. If an exception is the result of another exception, this method allows developers to access the underlying cause.

try {
    // Code that may throw an exception
} catch (Exception e) {
    Exception causeException = e.getCause();
    if (causeException != null) {
        System.debug('Cause: ' + causeException.getMessage());
    }
}

4. getLineNumber() Method

The getLineNumber() method returns the line number in the Apex code where the exception was thrown. This information is particularly helpful for pinpointing the location of an issue within your code.

public class ExceptionExample {
    public static void throwError() {
        try {
            Integer result = 10 / 0; // This will result in a MathException
        } catch (Exception e) {
            // Log the line number where the exception occurred
            System.debug('Exception occurred at line: ' + e.getLineNumber());
        }
    }
}

5. getTypeName() Method

The getTypeName() method returns the name of the exception type. This can be useful when you want to identify the specific type of exception that occurred.

public class ExceptionTypeExample {
    public static void handleException() {
        try {
            String s = null;
            Integer length = s.length(); // This will result in a NullPointerException
        } catch (Exception e) {
            // Log the type name of the exception
            System.debug('Exception Type: ' + e.getTypeName());
        }
    }
}

6. getDmlFieldNames(Index of the failed record) Method

The getDmlFieldNames(Integer index) method is specific to DmlException and returns the names of the fields that caused the error for the specified failed record.

public class DmlExceptionExample {
    public static void insertAccounts(List<Account> accounts) {
        try {
            // Attempt to insert a list of accounts, some of which might have missing required fields
            insert accounts;
        } catch (DmlException de) {
            // Log the field names for the first failed record
            System.debug('Field Names for the First Failed Record: ' + de.getDmlFieldNames(0));
        }
    }
}

In this example, if the insert operation fails for the first record in the accounts list, the getDmlFieldNames(0) method is used to log the names of the fields that caused the error for that specific record.

7. getDmlId(Index of the failed record) Method

The getDmlId(Integer index) method, specific to DmlException, returns the ID of the failed record that caused the error for the specified failed record.

public class DmlExceptionExample {
    public static void updateContacts(List<Contact> contacts) {
        try {
            // Attempt to update a list of contacts, some of which might not exist
            update contacts;
        } catch (DmlException de) {
            // Log the ID for the first failed record
            System.debug('ID of the First Failed Record: ' + de.getDmlId(0));
        }
    }
}

In this example, if the update operation fails for the first record in the contacts list, the getDmlId(0) method is used to log the ID of the record that caused the error.

8. getDmlMessage(Index of the failed record) Method

The getDmlMessage(Integer index) method, specific to DmlException, returns the error message for the specified failed record.

public class DmlExceptionExample {
    public static void deleteOpportunities(List<Opportunity> opportunities) {
        try {
            // Attempt to delete a list of opportunities, some of which might be referenced elsewhere
            delete opportunities;
        } catch (DmlException de) {
            // Log the error message for the first failed record
            System.debug('Error Message for the First Failed Record: ' + de.getDmlMessage(0));
        }
    }
}

In this example, if the delete operation fails for the first record in the opportunities list, the getDmlMessage(0) method is used to log the error message for that specific record.

9. getNumDml() Method

The getNumDml() method, specific to DmlException, returns the number of failed records.

public class DmlExceptionExample {
    public static void upsertLeads(List<Lead> leads) {
        try {
            // Attempt to upsert a list of leads, some of which might have duplicate values
            Database.upsert(leads, Lead.Fields.Email, true);
        } catch (DmlException de) {
            // Log the total number of failed records
            System.debug('Number of Failed Records: ' + de.getNumDml());
        }
    }
}

In this example, if the upsert operation encounters issues for multiple records in the leads list, the getNumDml() method is used to log the total number of failed records.

These specific exception methods provide detailed information about failed DML operations in Salesforce Apex, aiding developers in identifying and resolving issues with individual records during bulk operations.

Best Practices for Exception Handling in Apex

  1. Specificity in Exception Handling: Catch exceptions based on their specific types rather than using a generic Exception catch block. This allows for more granular control over error handling.
  2. Logging and Debugging: Utilize the System.debug() method or logging framework to log relevant information, including error messages and stack traces, for effective debugging and troubleshooting.
  3. Transaction Management: Be mindful of DML operations within transactions. Consider using savepoints to handle partial commits and rollbacks in complex transactions.
  4. Bulk Data Processing: When dealing with bulk data processing, bulkify your code to handle large datasets efficiently and to avoid hitting governor limits.
  5. Governor Limits Awareness: Understand and monitor Salesforce governor limits, and design your code to stay within these limits to ensure optimal performance.
  6. Testing for Exceptions: Write test cases that cover both expected and unexpected scenarios, including scenarios where exceptions are likely to occur. This ensures robust exception handling.

Conclusion

Exception handling is a critical aspect of Salesforce Apex development, and understanding built-in exceptions and common methods is essential for writing reliable and maintainable code. By leveraging the exception classes provided by Salesforce, along with common methods for exception handling, developers can build applications that gracefully handle errors and provide a better user experience. Remember to follow best practices, log relevant information, and test thoroughly to create robust and resilient Apex code on the Salesforce platform.

About the blog

SFDCLessons is a blog where you can find various Salesforce tutorials and tips that we have written to help beginners and experienced developers alike. we also share my experience and knowledge on Salesforce best practices, troubleshooting, and optimization. Don’t forget to follow us on:

Newsletter

Subscribe to our email newsletter to be notified when a new post is published.

Arun Kumar

Arun Kumar is a Salesforce Certified Platform Developer I with over 7+ years of experience working on the Salesforce platform. He specializes in developing custom applications, integrations, and reports to help customers streamline their business processes. Arun is passionate about helping businesses leverage the power of Salesforce to achieve their goals.

Leave a Reply