Contents

Statements

A block contains a series of statements. The outer script itself is considered a block.

Internal blocks are defined with curly braces as in Java. An internal block can be used wherever a statement is used. This allows the usual syntax for an if statement. For example:

if (true) 
    System.debug(1);
else 
    System.debug(2);

Or,

if (true) {
    System.debug(1);
    System.debug(2);
} else {
    System.debug(3);

    System.debug(4);
}

All non-block statements end with semicolon.

Assignment Statement

Apex supports the following assignment operators:

  • =
  • ++
  • //
  • +=
  • -=
  • *=
  • /*
  • &=
  • |=

Assignment statements are specified as in Java:

LValue = new_Value_expression | [query];

Where LValue can be:

  • A simple variable
  • An SObject reference, with the following limits:
    • Only fields in the main object.
    • Field-level security is honored based on the context user.
    • The Id field cannot be assigned or changed via Apex.
    • It is not legal to change field values through a relationship such as Contact.account.name. For example:
      • Only Contact.accountId can be assigned for a Contact.
      • After saving this change, re-query to get other fields from the new Account.
    • Examples of an SObject reference include:
      • acc.Id = <some id>; // Parser exception
      • acc.name = 'xxx';
      • acc.createdDate = <some date> // Parser exception, field not writable
      • con.accountId = acc.id;
      • con.account.name = 'xxx'; // Parser exception
  • An array de-reference (optionally followed by an SObject de-reference). For example:
    • a[0] = 1;
    • accs[0].name = 'xxx';

It is important to note that assignment is done by reference. For example:

Account a = new Account();
Account b;
Account[] c = new Account[]{};
a.name = 'xxx';
b = a;
c.add(a);

System.debug(b.name);
System.debug(c[0].name);

The following is the output from the assignment by reference:

'xxx'
'xxx'

Similarly, two arrays can point at the same value:

Account[] a = new Account[]{new Account()};
Account[] b = a;
a[0].name = 'xxx';
System.debug(b[0].name);

The following output is from this assignment:

'xxx'

Conditional Statement

The conditional statement is like the Java conditional statement:

if (<Boolean expression>) 
    <truestatement> 
(else 
    <falsestatement>)?

Conditional statements behave as in Java. There is no elsif statement as it is not needed.

Loops

There are five types of procedural loops, analogous to Java procedural loops:

do statement while (expression);
while (expression) statement;

for (initial ; (condition); (inc)) statement;

for (type var : array) statement;
for (type var : [query]) statement;

The do and while loops function as in Java. The expression must be of type boolean.

The first for loop corresponds to the traditional Java loop of the form:

for (int i=0, j=0; i < 100; i++);

Note: Multiple variables can be declared and initialized.

Apex first executes initial, which is typically an assignment statement, and then continues to perform the following tasks:

  1. Tests the condition, and quitting when false.
  2. Executes the statement.
  3. Executes inc, which typically increments the variable set during initial.

The second for loop assigns the variable var, which must be of the array type of array, to each element in array successfully, and then runs statement for each value.

The third type of loop corresponds to a SELECT statement and will be described in Query Assignment Statement and for Query Loops.

All loops allow these loop control structures:

break;
continue;

break exits the entire loop, and continue skips to the next iteration.

In order to prevent infinite loops or very inefficient iterative database calls, the script execution framework throws a runtime exception if too many script statements are executed without any database statements. For more information, see Execution Model and Governors.

Query Assignment Statement and for Query Loops

There are two ways to pull data from SOQL into the scripting environment:

  • Assignment:

LValue = [query] // We've seen this already

  • Looping:

for (SObject_type var : [query]) statement; for (SObject_array_type var : [query]) statement;

Notice that the SOQL statements can refer to script expressions in a WHERE clause using the :expression syntax. For example:

String s = 'xxx';
for (Account a : [select id, name from account 
                  where name like :(s+'%')]) {
    
}

The queries used in both formats can reference host script variables in the WHERE clause.

The first query format we have seen already. It can retrieve either the count() of a query or a small number of result rows (one batch) into a host array or single SObject.

The second and third formats retrieve all SObjects, using efficient chunking via the API calls query and queryMore.

The difference between the two formats is whether the script sees one row at a time, using an SObject variable, or batches of rows at one time using an SObject array variable. The single-row format is easily understood, but if DML statements are used inside the loop, this could lead to inefficient writing operations. The array format is more complicated, but it can be more efficient, because it facilitates the use of bulk DML calls. In this array format, the loop statement is executed once per chunk and not once per row.

break and continue can be used for both formats. When using the array form, continue skips to the next chunk.

Notice that, as with inline queries, aggregate relationship values act as query results and can be used wherever an inline SOQL statement is used.

Examples:

// Find one account and count its contacts, demonstrating
// both an outer level FOR loop as well as one on an 
// aggregate relationship
for (Account a : [select id, name, 
                         (select lastname from contacts)
                  from account where name = 'testAgg']) {
    Integer i = 0;
    for (Contact c : a.contacts) 
        i++;
}

DML Statements

The DML statements are of the form:

insert SObject;
insert SObject_array;
update SObject;
update SObject_array;
upsert SObject;
upsert SObject_array;
       optional_matching_field_name; // Id-field is 
                                       // used by default
delete SObject;
delete SObject_array;

All statements perform the same logic as the underlying API bulk calls, with all workflow and other processing done in Java. This is why Apex is important as a wrapper for the API.

This also means that API fault codes can be thrown, such as a missing required field or field integrity exception. Save errors that result from DML faults can be trapped and handled. For more information, see Try and Throw Statements (Exception Handling).

The insert statement writes the Ids of the inserted rows back into the SObjects being saved. For arrays, Ids are written back to the script array, so you can reference those Ids. Inserting records that already have Ids produces an error. The contents of the SObjects are not otherwise altered by any of the statements.

While SELECT statements can use foreign key joins to retrieve data from parent SObjects, the insert, upsert, or update statements can only set the foreign key Ids for the relationships in the root object. Scalar fields in the parent records themselves cannot be changed by a DML call.

Transaction Control Statements

The results of all DML operations are held until a transaction control statement is issued:

commit; 
rollback;

If a top-level script invocation has pending rows when it completes (that is, uncommitted and not rolled back), then a runtime exception is generated.

Procedure Statements

Void functions that have side effects are typically executed as stand-alone procedure statements in script. Functions that return values can also be run as a statement. The return value is ignored, as in Java.

Try and Throw Statements (Exception Handling)

Script authors can catch some runtime exceptions and handle them using try...catch syntax:

try {
 <statement>
  
 <statement>
 } catch (<exception type> <exception variable name>) {
    <handler statement>
    
    <handler statement>
}
    <other optional catches>

} finally { // Optional
    
}

Exception variables (typically named e) are of the script type Exception and support the following methods:

// Returns a general type of the exception as a String. 
// Examples values are: ARRAY, MATH, DML, QUERY e.getTypeName()

// Returns the textual message of the exception e.getMessage()

The limit exceptions caused by the execution governor cannot be caught. To raise the exception again:

throw e;

Specific types of system exceptions can be caught using standardized exception names include: ArrayException, DmlException, NullPointerException, MathException. The general name Exception can also be used, but it must be the last catch block:

try {
    
} catch (System.ArrayException e) {
    
} catch (Exception e) {
    
}

User-Defined Exceptions

Script authors can define their own exceptions in Apex . These can be thrown and caught as expected. The following syntax is for an anonymous block defining an exception:

defineException MyException;
try {
    
    if (i > 5) {
        throw new MyException('This is bad');
    }
catch (MyException e)
    
}

Packages can define an exception as well, in which case the exception name can be referenced outside the package using dot notation.

Variables can be declared of type Exception, as in Java:

Exception e;
if (<condition>) {
    e = new MyException();
}else {
    e = new OtherException();
}
throw e;

Handling DML Exceptions

Bulk DML exceptions can be handled by getting information about the failed rows from the Exception object. The following methods are available for this purpose:

// Returns the number of failed rows from the last DML // operation e.getNumDml()

// Returns the original row number of the ith DML exception e.getDmlIndex(int)

// Returns the API fault code of the ith DML exception e.getDmlStatusCode(int)

// Returns the textual message of the ith DML exception e.getDmlMessage(int)

Example:

try {

   insert accs;

} catch (System.DmlException e) {

   for (Integer i = 0; i < e.getNumDml(); i++) {
       // process exception here maybe using
       // accs[e.getIndex(i)]
   }

}

Execution Model and Governors

Several governors are in place to prevent runaway scripts. The execution environment tracks statistics on the number and types of statements that have been executed and throws a runtime exception if one category exceeds the (contextual) limit. The statistics tracked are:

  • Total number of SOQL statements issued
  • Total number of SOQL rows retrieved. A query that produces a cursor counts as the total number, even if not all rows are retrieved into the script.
  • Total number of DML statements issued
  • Total number of DML rows processed
  • Total number of transaction control statements
  • The total number of uncommitted DML rows since the last transaction control statements
  • The total number of script statements that have been executed since the last database call. In this way, we prevent an infinite script-loop.

The Execution object allows specifying the ceiling for all of these limits. These limits should be set based on the context of the script, the organization in which it is running, the context user, functional testing, and other related areas of impact. The point is that you can easily prevent runaway resource utilization since you have full control over the actual runtime model.

The execution environment also has other limits in place: maximum stack depth for a single script when it calls procedures, including: maximum trigger stack depth when a stored trigger calls code that initiates a new trigger execution; maximum string size, and maximum array sizes.

In all cases in the list above, a runtime exception is thrown if the limit is exceeded, and the exception cannot be handled (like an Error in Java).

Other information available from the runtime model:

  • The variables and their values in scope at the end of the execution
  • The dependency information on schema objects and fields and packages
  • The debug log:
    • Logging level is specified in code and at script invocation. It takes one of four values: None, UserDebug Statements, Database Calls, or AllStatements. Higher levels of logging include all lower levels.
    • When AllStatement logging is turned on, the log will contain verbose information about every execution statement and expression, including actual values processed.
    • The System.debug() method writes to this log and is level dependent.
  • The stack trace

Loops

There are five types of procedural loops, analogous to Java procedural loops:

do statement while (expression); while (expression) statement;

for (initial ; (condition); (inc)) statement;

for (type var : array) statement; for (type var : [query]) statement;

The do and while loops function as in Java. The expression must be of type boolean.

The first for loop corresponds to the traditional Java loop of the form:

for (int i=0, j=0; i < 100; i++);

Note: Multiple variables can be declared and initialized.

Apex first executes initial, which is typically an assignment statement, and then continues to perform the following tasks:

  1. Tests the condition, and quitting when false.
  2. Executes the statement.
  3. Executes inc, which typically increments the variable set during initial.

The second for loop assigns the variable var, which must be of the array type of array, to each element in array successfully, and then runs statement for each value.

The third type of loop corresponds to a SELECT statement and will be described in Query Assignment Statement and for Query Loops.

All loops allow these loop control structures:

break; continue;

break exits the entire loop, and continue skips to the next iteration.

In order to prevent infinite loops or very inefficient iterative database calls, the script execution framework throws a runtime exception if too many script statements are executed without any database statements. For more information, see Execution Model and Governors.

Query Assignment Statement and for Query Loops

There are two ways to pull data from SOQL into the scripting environment:

  • Assignment:

LValue = [query] // We've seen this already

  • Looping:

for (SObject_type var : [query]) statement; for (SObject_array_type var : [query]) statement;

Notice that the SOQL statements can refer to script expressions in a WHERE clause using the :expression syntax. For example:

String s = 'xxx';
for (Account a : [select id, name from account 
                  where name like :(s+'%')]) {
    
}

The queries used in both formats can reference host script variables in the WHERE clause.

The first query format we have seen already. It can retrieve either the count() of a query or a small number of result rows (one batch) into a host array or single SObject.

The second and third formats retrieve all SObjects, using efficient chunking via the API calls query and queryMore.

The difference between the two formats is whether the script sees one row at a time, using an SObject variable, or batches of rows at one time using an SObject array variable. The single-row format is easily understood, but if DML statements are used inside the loop, this could lead to inefficient writing operations. The array format is more complicated, but it can be more efficient, because it facilitates the use of bulk DML calls. In this array format, the loop statement is executed once per chunk and not once per row.

break and continue can be used for both formats. When using the array form, continue skips to the next chunk.

Notice that, as with inline queries, aggregate relationship values act as query results and can be used wherever an inline SOQL statement is used. Examples:

// Find one account and count its contacts, demonstrating // both an outer level FOR loop as well as one on an // aggregate relationship for (Account a : [select id, name,

                        (select lastname from contacts)
                 from account where name = 'testAgg']) {
   Integer i = 0;
   for (Contact c : a.contacts) 
       i++;

}

DML Statements

The DML statements are of the form:

insert SObject;
insert SObject_array;
update SObject;
update SObject_array;
upsert SObject;
upsert SObject_array;
       optional_matching_field_name; // Id-field is 
                                       // used by default
delete SObject;
delete SObject_array;

All statements perform the same logic as the underlying API bulk calls, with all workflow and other processing done in Java. This is why Apex is important as a wrapper for the API.

This also means that API fault codes can be thrown, such as a missing required field or field integrity exception. Save errors that result from DML faults can be trapped and handled. For more information, see Try and Throw Statements (Exception Handling).

The insert statement writes the Ids of the inserted rows back into the SObjects being saved. For arrays, Ids are written back to the script array, so you can reference those Ids. Inserting records that already have Ids produces an error. The contents of the SObjects are not otherwise altered by any of the statements.

While SELECT statements can use foreign key joins to retrieve data from parent SObjects, the insert, upsert, or update statements can only set the foreign key Ids for the relationships in the root object. Scalar fields in the parent records themselves cannot be changed by a DML call.

Transaction Control Statements

The results of all DML operations are held until a transaction control statement is issued:

commit; 
rollback;

If a top-level script invocation has pending rows when it completes (that is, uncommitted and not rolled back), then a runtime exception is generated.

Procedure Statements

Void functions that have side effects are typically executed as stand-alone procedure statements in script. Functions that return values can also be run as a statement. The return value is ignored, as in Java.

Try and Throw Statements (Exception Handling)

Script authors can catch some runtime exceptions and handle them using try...catch syntax:

try {
 <statement>
  
 <statement>
 } catch (<exception type> <exception variable name>) {
    <handler statement>
    
    <handler statement>
}
    <other optional catches>

} finally { // Optional
    
}

Exception variables (typically named e) are of the script type Exception and support the following methods:

// Returns a general type of the exception as a String. 
// Examples values are: ARRAY, MATH, DML, QUERY e.getTypeName()

// Returns the textual message of the exception e.getMessage()

The limit exceptions caused by the execution governor cannot be caught. To raise the exception again:

throw e;

Specific types of system exceptions can be caught using standardized exception names include: ArrayException, DmlException, NullPointerException, MathException. The general name Exception can also be used, but it must be the last catch block:

try {
    
} catch (System.ArrayException e) {
    
} catch (Exception e) {
    
}

User-Defined Exceptions

Script authors can define their own exceptions in Apex . These can be thrown and caught as expected. The following syntax is for an anonymous block defining an exception:

defineException MyException;
try {
    
    if (i > 5) {
        throw new MyException('This is bad');
    }
catch (MyException e)
    
}

Packages can define an exception as well, in which case the exception name can be referenced outside the package using dot notation.

Variables can be declared of type Exception, as in Java:

Exception e;
if (<condition>) {
    e = new MyException();
}else {
    e = new OtherException();
}
throw e;

Handling DML Exceptions

Bulk DML exceptions can be handled by getting information about the failed rows from the Exception object. The following methods are available for this purpose:

// Returns the number of failed rows from the last DML
//  operation 
e.getNumDml()

// Returns the original row number of the ith DML exception
e.getDmlIndex(int)

// Returns the API fault code of the ith DML exception
e.getDmlStatusCode(int)

// Returns the textual message of the ith DML exception
e.getDmlMessage(int)

Example:

try {
    insert accs;
} catch (System.DmlException e) {
    for (Integer i = 0; i < e.getNumDml(); i++) {
        // process exception here maybe using
        // accs[e.getIndex(i)]
    }
}

Execution Model and Governors

Several governors are in place to prevent runaway scripts. The execution environment tracks statistics on the number and types of statements that have been executed and throws a runtime exception if one category exceeds the (contextual) limit. The statistics tracked are:

  • Total number of SOQL statements issued
  • Total number of SOQL rows retrieved. A query that produces a cursor counts as the total number, even if not all rows are retrieved into the script.
  • Total number of DML statements issued
  • Total number of DML rows processed
  • Total number of transaction control statements
  • The total number of uncommitted DML rows since the last transaction control statements
  • The total number of script statements that have been executed since the last database call. In this way, we prevent an infinite script-loop.

The Execution object allows specifying the ceiling for all of these limits. These limits should be set based on the context of the script, the organization in which it is running, the context user, functional testing, and other related areas of impact. The point is that you can easily prevent runaway resource utilization since you have full control over the actual runtime model.

The execution environment also has other limits in place: maximum stack depth for a single script when it calls procedures, including: maximum trigger stack depth when a stored trigger calls code that initiates a new trigger execution; maximum string size, and maximum array sizes.

In all cases in the list above, a runtime exception is thrown if the limit is exceeded, and the exception cannot be handled (like an Error in Java). Other information available from the runtime model:

  • The variables and their values in scope at the end of the execution
  • The dependency information on schema objects and fields and packages
  • The debug log:
    • Logging level is specified in code and at script invocation. It takes one of four values: None, UserDebug Statements, Database Calls, or AllStatements. Higher levels of logging include all lower levels.
    • When AllStatement logging is turned on, the log will contain verbose information about every execution statement and expression, including actual values processed.
    • The System.debug() method writes to this log and is level dependent.
  • The stack trace