Wednesday 29 August 2012

Built-In Exceptions and Common Methods - Apex

Apex provides a number of exception types that the runtime engine throws if errors are encountered during execution. You’ve seen the DmlException in the previous example. Here is a sample of some of the built-in exceptions:
DmlException
Any problem with a DML statement, such as an insert statement missing a required field on a record.
For an example that makes use of DmlException, see Lesson 2: Try, Catch, and Finally Statements.
ListException
Any problem with a list, such as attempting to access an index that is out of bounds.
Try out some code that does some things on purpose to cause this exception to be thrown. Execute the following:

try {
    List<Integer> li = new List<Integer>();
    li.add(15);
    // This list contains only one element, 
    
    // but we're attempting to access the second element 
    
    // from this zero-based list. 
    
    Integer i1 = li[0]; 
    Integer i2 = li[1]; // Causes a ListException 
    
} catch(ListException le) {
    System.debug('The following exception has occurred: ' + le.getMessage());
}
In the previous code snippet, we create a list and add one element to it. Then, we attempt to access two elements, one at index 0, which exists, and one at index 1, which causes a ListException because no element exists at this index. This exception is caught in the catch block. The System.debug statement in the catch block writes the following to the debug log: The following exception has occurred: List index out of bounds: 1.
NullPointerException
Any problem with dereferencing a null variable.
Try out some code that does some things on purpose to cause this exception to be thrown. Execute the following:

try {
    String s;
    Boolean b = s.contains('abc'); // Causes a NullPointerException 
    
} catch(NullPointerException npe) {
    System.debug('The following exception has occurred: ' + npe.getMessage());
}
In the previous example, we create a String variable named s but we don’t initialize it to a value, hence, it is null. Calling the contains method on our null variable causes a NullPointerException. The exception is caught in our catch block and this is what is written to the debug log: The following exception has occurred: Attempt to de-reference a null object.
QueryException
Any problem with SOQL queries, such as assigning a query that returns no records or more than one record to a singleton sObject variable.
Try out some code that does some things on purpose to cause this exception to be thrown. Execute the following:

try {
    // This statement doesn't cause an exception, even though  
    
    // we don't have a merchandise with name='XYZ'. 
    
    // The list will just be empty. 
    
    List<Merchandise__c> lm = [SELECT Name FROM Merchandise__c 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 Merchandise__c object 
    
    // but no Merchandise is returned. 
    
    Merchandise__c m = [SELECT Name FROM Merchandise__c WHERE Name='XYZ' LIMIT 1];
} catch(QueryException qe) {
    System.debug('The following exception has occurred: ' + qe.getMessage());    
}
The second query in the above code snippet causes a QueryException. We’re attempting to assign a Merchandise object to what is returned from the query. Note the use of LIMIT 1 in the query. This ensures that at most one object is returned from the database so we can assign it to a single object and not a list. However, in this case, we don’t have a Merchandise named XYZ, so nothing is returned, and the attempt to assign the return value to a single object results in a QueryException. The exception is caught in our catch block and this is what you’ll see in the debug log: The following exception has occurred: List has no rows for assignment to SObject.
SObjectException
Any problem with sObject records, such as attempting to change a field in an update statement that can only be changed during insert.
Try out some code that does some things on purpose to cause this exception to be thrown. Execute the following:

try {
    Merchandise__c m = [SELECT Name FROM Merchandise__c LIMIT 1];
    // Causes an SObjectException because we didn't retrieve 
    
    // the Total_Inventory__c field. 
    
    Double inventory = m.Total_Inventory__c;
} catch(SObjectException se) {
    System.debug('The following exception has occurred: ' + se.getMessage());    
}
Our code snippet queries any Merchandise object that is in the database. Note the use of LIMIT 1 in the query. Since we have sample merchandise items, the first object in the query will be returned and assigned to the Merchandise variable m. However, we retrieved only the Name field in the query and not Total_Inventory, so when we attempt to get the Total_Inventory value from the merchandise object, we get an SObjectException. This exception is caught in our catch block and this is what you’ll see in the debug log: The following exception has occurred: SObject row was retrieved via SOQL without querying the requested field: Merchandise__c.Total_Inventory__c.

Common Exception Methods

You can use common exception methods to get more information about an exception, such as the exception error message or the stack trace. The previous example calls the getMessage method, which returns the error message associated with the exception. There are other exception methods that are also available. Here are descriptions of some useful methods:
  • getCauseReturns the cause of the exception as an exception object.
  • getLineNumberReturns the line number from where the exception was thrown.
  • getMessageReturns the error message that displays for the user.
  • getStackTraceStringReturns the stack trace as a string.
  • getTypeNameReturns the type of exception, such as DmlException, ListException, MathException, and so on.
Try It Out
Let’s see what these methods return by running this simple example.

try {
    Merchandise__c m = [SELECT Name FROM Merchandise__c LIMIT 1];
    // Causes an SObjectException because we didn't retrieve 
    
    // the Total_Inventory__c field. 
    
    Double inventory = m.Total_Inventory__c;
} catch(Exception e) {
    System.debug('Exception type caught: ' + e.getTypeName());    
    System.debug('Message: ' + e.getMessage());    
    System.debug('Cause: ' + e.getCause());    // returns null 
    
    System.debug('Line number: ' + e.getLineNumber());    
    System.debug('Stack trace: ' + e.getStackTraceString());    
}
The output of all System.debug statements looks like the following:
17:38:04:149 USER_DEBUG [7]|DEBUG|Exception type caught: System.SObjectException
17:38:04:149 USER_DEBUG [8]|DEBUG|Message: SObject row was retrieved via SOQL without querying the requested field: Merchandise__c.Total_Inventory__c
17:38:04:150 USER_DEBUG [9]|DEBUG|Cause: null
17:38:04:150 USER_DEBUG [10]|DEBUG|Line number: 5
17:38:04:150 USER_DEBUG [11]|DEBUG|Stack trace: AnonymousBlock: line 5, column 1
The catch statement argument type is the generic Exception type. It caught the more specific SObjectException. You can verify that this is so by inspecting the return value ofe.getTypeName() in the debug output. The output also contains other properties of the SObjectException, like the error message, the line number where the exception occurred, and the stack trace. You might be wondering why getCause returned null. This is because in our sample there was no previous exception (inner exception) that caused this exception. In Lesson 5: Creating Custom Exceptions, you’ll get to see an example where the return value of getCause is an actual exception.

More Exception Methods

Some exception types, such as DmlException, have specific exception methods that apply to only them:
  • getDmlFieldNames(Index of the failed record): Returns the names of the fields that caused the error for the specified failed record.
  • getDmlId(Index of the failed record): Returns the ID of the failed record that caused the error for the specified failed record.
  • getDmlMessage(Index of the failed record): Returns the error message for the specified failed record.
  • getNumDml: Returns the number of failed records.
Try It Out
This snippet makes use of the DmlException methods to get more information about the exceptions returned when inserting a list of Merchandise objects. The list of items to insert contains three items, the last two of which don’t have required fields and cause exceptions.

Merchandise__c m1 = new Merchandise__c(
    Name='Coffeemaker',
    Description__c='Kitchenware',
    Price__c=25,
    Total_Inventory__c=1000);
// Missing the Price and Total_Inventory fields 
    
Merchandise__c m2 = new Merchandise__c(
    Name='Coffeemaker B',
    Description__c='Kitchenware');
// Missing all required fields 
    
Merchandise__c m3 = new Merchandise__c();
Merchandise__c[] mList = new List<Merchandise__c>();
mList.add(m1);
mList.add(m2);
mList.add(m3);

try {
    insert mList;
} catch (DmlException de) {
    Integer numErrors = de.getNumDml();
    System.debug('getNumDml=' + numErrors);
    for(Integer i=0;i<numErrors;i++) {
        System.debug('getDmlFieldNames=' + de.getDmlFieldNames(i));
        System.debug('getDmlMessage=' + de.getDmlMessage(i));  
    }
}  
Note how the sample above didn’t include all the initial code in the try block. Only the portion of the code that could generate an exception is wrapped inside a try block, in this case the insert statement could return a DML exception in case the input data is not valid. The exception resulting from the insert operation is caught by the catchblock that follows it. After executing this sample, you’ll see an output of System.debug statements similar to the following:
14:01:24:939 USER_DEBUG [20]|DEBUG|getNumDml=2
14:01:24:941 USER_DEBUG [23]|DEBUG|getDmlFieldNames=(Price, Total Inventory)
14:01:24:941 USER_DEBUG [24]|DEBUG|getDmlMessage=Required fields are missing: [Price, Total Inventory]
14:01:24:942 USER_DEBUG [23]|DEBUG|getDmlFieldNames=(Description, Price, Total Inventory)
14:01:24:942 USER_DEBUG [24]|DEBUG|getDmlMessage=Required fields are missing: [Description, Price, Total Inventory]
The number of DML failures is correctly reported as two since two items in our list fail insertion. Also, the field names that caused the failure, and the error message for each failed record is written to the output.

1 comment: