This document outlines the basics of the Apex language. You may find it useful to review this Sample Script before or in conjunction with your reading of this document.

Contents

Local Variables and Expressions

Local variables can be defined at any point in a block and assigned a scope from that point forward. Sub-blocks cannot redefine the same variable name (hiding the variable in the parent block), since Java does not allow this, but parallel blocks can reuse a variable name. For example:

Integer i;
{
  // Integer i;   Not allowed
}

for (Integer j = 0; j < 10; j++);
for (Integer = 0; j < 10; j++);

All variables and expressions have a type that is:

  • A primitive type
  • The special type Null for the constant null
  • A concrete SObject type (e.g. Account, Contact, MyCustom__c)
  • An exception
  • An array of one of the following:
    • A primitive
    • A concrete SObject type

The special value null has a Null type and can be assigned to any variable.

Methods that return no value have the void type.

Type checking is strict and enforced at compile time. For example, an SObject field of type Integer cannot be assigned a String value — this error is caught by the parser.

Note. Compile-time exceptions are returned as specific API fault codes, with the line number and column of the error.

Declaration

Variables are declared with Java-style syntax. For example:

Integer i = 0;
String s;
Account a;
Account[] accs;

As with Java, multiple variables can be declared and initialized in a single statement using comma separation:

Integer i = 0, j, k = 1;

Primitives

The primitive data types are:

Integer, Double, Date, Datetime, String, Id, Boolean

All variables allow null as a value and are initialized to null, including Boolean.

The initial value can be overridden when the variable is defined, by providing an expression following the equality operator (like in Java).

  • The Boolean literals are true and false.
  • Integer and Double literals are distinguished by the decimal point.
  • Strings have the following features:
    • They must be surrounded by single quotes.
    • The same parsing rules apply as SOQL strings.
    • String field values can never be empty (only null).
    • String field values are always trimmed (the same semantics as Oracle and the AppExchange API). However, script-level String values can be empty or have leading or trailing spaces.
    • The same escape sequences are currently used as for SOQL strings.
  • Date and Datetime literals are also specified in the same format as in SOQL.
  • Ids are special strings that can only take valid AppExchange API 18-character Id values. Note that setting an Id to a 15-character value will actually set it to its 18-character representation. Any invalid Id will be rejected with a runtime exception.

The type AnyType cannot be used in a variable declaration, but AnyType fields in the AppExchange data model (such as EntityHistory.NewValue) are of this type – they must be converted at runtime using the valueOf() static methods.

SObject Abstract Type

An SObject type represents a row of data and can be declared using the API name of the SObject. For custom objects, use the usual __c syntax. For example:

Account a1;
Account a2 = new Account();
Account a3 = new Account(name='xxx');
CustomNoParent__c c;


Note. SObject is an abstract variable type. Variables cannot be defined with this type, instead they are declared with the specific SObject type name as above.

The Apex parser tracks the dependent custom objects used in a script (both at the script syntax level as well as embedded in SOQL statements) when the script is parsed and validated. The parser throws an exception when an invalid name is used. Note that read/write access and field-level security are honored based on the context user.

SObject fields can be accessed or changed with simple dot notation. For example:

Account a = new Account();
Contact c = new Contact();
System.debug(a.name);
a.name = 'xxx';
System.debug(c.account.name);

All SObject variables are initialized to null, but can be assigned a valid object reference with the new operator, as illustrated in the previous examples. The syntax allows the specification of initial field values (validated against the schema) using name=value tuples.

All field references are validated against actual field names, including __c for custom fields. A parse-time exception is thrown when an invalid field name is used.

Note. Apex tracks the dependencies of scripts on custom fields.

In the example above, we're following a relationship from Contact to Account to get to the actual field value. The relationship value is null, so the overall expression evaluates to null. This expression does not yield a null pointer exception because you'll typically want to navigate multiple foreign keys and Apex doesn't check null values along the way, for efficiency. This syntax is useful when accessing fields of an SObject populated with a SOQL query that uses foreign key relationships.

SObject variables support the following operators:

// Clear the map so that the SObject has no field values
s.clear();


// Clone the SObject so that two variables can point at
// the same data values, but not share the same internal
// memory
a.clone();

Arrays

Arrays are defined by following the basic type name with the [] characters. These characters must be the terminal portion of the type — arrays of arrays are not allowed. Arrays can be defined for any of the other types, but not Exception:

Account[] accs1;
Account[] accs2 = new Account[]{};
Account[] accs3 = new Account[]{new Account()};
Account[] accs4 = new Account[4];
Integer[] ints1;
Integer[] ints2 = new int[]{1, 2, 3};
Integer[] ints3 = new int[6];


All arrays are initialized to null but can be assigned a value with the array literal notation above, in either form.

Array variables support the following operators:

// Clear the list to have 0 elements
a.clear();

// Add one element m to a. m must be of the 
// appropriate type
a.add(<scalar mem>); 

// Add all element of a2 to a. a2 must be of the 
// appropriate type
a.addAll(<array a2>); 

// Return the size of the array as an int
a.size();

// Remove the entry at the given index
a.remove(<integer index>)

// Remove the entry with the given id (SObject array only)
a.remove(<id>)

// Make a shallow copy of the list
a.clone();

// Make a deep copy of the list of SObjects – the objects
// themselves are also cloned
a.deepClone();

For more information, see Methods.

Arrays are zero-based as in Java. Array values can be accessed by position, with strong typing (the result is a primitive or SObject value of the Array member type). In this example, we assign to an array position, overwriting the value at that position:

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

This will have the following output:

'xxx'
null

Arrays as Maps

Since the Id of an SObject is so important we want to make it easy to find rows by their Id in the script context.

For this reason, arrays of SObjects, in addition to acting as lists, also act as maps from Id to SObject. Whenever an SObject is added to an array (in a constructor, via add() or addAll(), or via positional setting), if that row has an Id, then an internal pointer is kept with the Id-to-SObject mapping. Also, when insert is called on an array, then, in addition to setting the new Ids on the rows themselves, the map is also updated. For example:

Account acc = [select Id from account where name = 'xxx'];
Account[] accs = new Account[]{acc};
System.assert(accs[acc.id] != null);

This feature allows easy "script hash joins" against small lookup tables.

Note that if a new SObject row happens to be a member of multiple arrays, and insert is called on one of those arrays (which sets the Id value on the row), then only the inserted array's mapping will be updated. For example:

Account acc = new Account(name='xxx');
Account[] accs1 = new Account[]{acc};
Account[] accs2 = new Account[]{acc};
insert accs1;
System.assert(!accs2[acc.id]);

Also note that it is illegal to add the same row (with a non-null Id) to the same array more than once. For example:

Account acc = new Account(name='xxx');
insert acc;
Account acc2 = [select id from account where id = :acc.id];
Account[] accs = new Account[]{acc};
// This would yield an error
// accs.add(acc2);

Finally, it is illegal for the same exact row to be present in an array more than once (even with null id), since that would lead to a violation of the mapping invariant when the array was inserted. For example:

Account a = new Account();
Account[] accs = new Account[]{a, a}; // Runtime exception

Case Sensitivity

Apex itself is case insensitive because SOQL is case insensitive, as are other scripting languages. Therefore, the variable and method names of Apex are also case insensitive:

Integer I;
// This would be an error 
// Integer i;

In addition, any references to object and field names are case insensitive:

Account a1;
      ACCOUNT a2;

And, of course, the SOQL statements themselves are fully case insensitive as well:

Account[] accs = [sELect ID From ACCouNT 
                 where nAme = 'fred']

Also, the SOQL WHERE clause always uses the same filtering semantics as the online application (internationalization case-insensitive comparison of strings using collation). Script-level comparisons will obey these same semantics as SOQL, so the following expressions are all true:

'a' == 'A'
null < 'a'
!(null > 'a')

Script-level methods exist for binary string comparisons (for example, s1.equals(s2)) as well. For more information, see Methods).

Expressions

The most basic expression types are similar to Java:

  • Literal
  • New SObject or Array using the new operator. For example:
    • new Account(field_initializations)
    • new int[n]
    • new Account[]{elements}
  • LValues:
    • Variable
    • Field reference of an SObject variable
    • A positional reference of an Array variable
  • Non-assignable field references:
    • An Id reference of an SObject array (cannot be assigned)
    • An aggregate field of an SObject (for SOQL queries that use aggregate relationships). This type of expression yields a query result, much like an inline query. For more information about inline queries, see Inline Queries.
  • Inline queries. For more information, see Inline Queries.
  • Static methods. For an example, see now.

Expression Composition and Operators

SObject and Array expressions can additionally be followed by a chain of either method references or array position references, respectively, to form new expressions. This follows Java-style object method invocation syntax. For example:


  • new Account[]{new Account(name='xxx')}[0].name.length()
  • [select id from account][0].name.toLowerCase()

Additionally, Apex supports compound expressions based on the usual operators, with standard precedence. For example:

  • The assignment and combinated assignment operators: =, +=, *=. -=, /=, |=, &=, which return the result of the assignment (right associative)
  • The ternary operator: ?: (right associative)
  • The Boolean operators: &&, || (left associative)
    • && binds tighter than ||
    • Short-circuiting applies
  • The comparison operators: ==, <, >, <=, >=, != (non associative)
    • Comparison operators are not tri-state: even though null can be an argument to various comparison operators below, the resulting Boolean expression is never null in Apex.
    • SOQL uses = for equality, but we want to mimic Java's ==
    • == and != compare object value equality, not reference equality:
      • In particular, they do not correspond to == and != in Java. They currently use SOQL equality semantics. This means that for strings, these operators use collation according to the current user’s locale. This is because in Java == doesn’t even work for strings at all.
      • Testing for null and not null is done using the == and != operators.
      • There is no IS NULL operator, as there is none in Java either.
      • Inequalities also behave the same as in SOQL:
      • For numbers and dates, if either argument is Null then the expression is false.
      • Null is always less than any String value.
    • Boolean expressions cannot be used in inequalities, and neither can Ids.
  • Only the 4 basic arithmetic operators are implemented (though unary negation is also allowed), and they come next in precedence order:
    • Integers and Doubles can be intermingled. If at least one value is a Double, the result is Double.
    • Dates can be the left side of an expression with Integers on the right hand side. The operation will return a new Date with the given number of days added or subtracted.
    • Datetimes can be the left side of an expression with Integers or Doubles on the right hand side. The operation will return a new Datetime with the given number of days added or subtracted, with the fractional portion corresponding to a fractional date.
    • String concatenation is supported using the + operator with any type of argument on the right-hand side.
  • The Boolean negation operator !, unary negation - (and no-op +), unary pre-fix, and post-fix increment using ++ and --
  • Of course, parentheses can be used to change the normal operator precedence. For example:
X += (y == 2) ? i++ * 7 : (z = w = 4);

Inline Queries

An inline query is a SOQL statement surrounded by square brackets ([query]) that allows on-the-fly SOQL evaluation in Apex. It evaluates (by running the query) to an array of SObjects, or to an Integer for count() queries. For example:

Account[] accs = [select id from Account 
                 where name = 'xxx'];
Integer i = [select count() from contact 
        where lastName = 'x'];

These types of inline query expressions can be used wherever Arrays (or Integers, in the case of count() queries) are used.

Inline queries can also be used to assign a single SObject value. When the LValue of an expression is a singleton SObject type, then Apex automatically assigns the one row from the query result to that LValue. A runtime exception results if zero rows are found or more than one row is found. For example:

Integer i = [select count() from account];
Integer i2 = 5 * [select count() from account];
// This only works if one row is returned
Account acc = [select id from account];
Account[] accs = [select id from account];

Note. The last example (returning multiple rows) can only be used if the query results are returned in a single batch. If the query requires cursoring, because the results are very large, then a FOR loop must be used. The syntax above, if the results are too large, will cause a runtime exception.

The select statement can, in general, be any valid SOQL statement, including foreign key and aggregate joins. If foreign key joins are included, the resulting SObjects that are returned can be referenced using normal field notation For example:

System.debug([select account.name from contact
              where firstname = 'xxx'][0].account.name);

Additionally, aggregate relationships in SObjects act as inline queries as well (in AppExchange WSDL the type of these fields is QueryResult, so they really are just like outer queries). For example:

for (Account a : [select id, name, 
                        (select lastname from contacts) 
                 from account 
                 where name = 'testAgg']) {
Contact[] cons = a.contacts;
}
// This example works because we limit to only 1 contact
for (Account a : [select id, name, 
                        (select lastname from 
                         contacts limit 1) 
                 from account 
                 where name = 'testAgg']) {
Contact c = a.contacts;
}

Finally and importantly, SOQL statements in Apex may reference script variables and expressions inline, preceded by a colon — these will be evaluated in script context before executing the SOQL statement. These expressions can be used as the filter literals in WHERE clauses as well as the numeric value in the LIMIT clause. For example:

Account A = new Account(name='xxx');
insert A;
Account B;
B = [select id from account
            where id = :A.id];
B = [select id from account 
    where name = :('x' + 'xx')];

String s = 'XXX';
B = [select id from account 
    where name = :s.toLowerCase()];
B = [select id from account 
    where name = :'XXXX'.substring(0,3)];
B = [select id from account 
    where name = :[select name from account 
                   where id = :A.id].name];
Contact C = new Contact(lastName='xxx', 
                       accountid=A.id);
insert new Contact[]{C, new Contact(lastName='yyy', 
                                   accountId=A.id)};
B = [select id, (select id from contacts 
                where id = :C.id) 
    from account 
    where id = :A.id];
// One contact returned
Contact D = B.contacts;

Integer i = 1;
B = [select id from account limit :i];