Professional Documents
Culture Documents
String A series of characters surrounded by single quotes. This can include any text as sh
Boolean Typically either true or false. In Apex, null (empty) is also a valid value. Boolean is c
checkboxes.
Collections
A collection is a type of variable that can store multiple items. In our tea example, we
have a sugar bowl with several sugar cubes inside it. The sugar bowl is considered a
collection and the sugar cubes are items that are stored in that collection.
Although there are three types of Apex collections (lists, sets, and maps), in this
module we focus on lists.
Lists
An Apex list is an ordered group of items of the same type, like a shopping list.
1. Tea
2. Sugar
3. Honey
4. Milk
Each item in the shopping list is a string, and each item has a position within the list.
To declare a list you need a few things. You will use the list reserved word, the data
type (all items must be the same data type) within the <> characters, and the
reserved word new. At the end of this statement there are required parentheses.
Note
There are different ways to declare lists in Apex. This example uses array notation in
order to set the list size.
Initialize a List
Initializing a list is assigning initial values to a list. There are two ways to add items to
a list:
1. Declare and initialize a list.
2. //Sets the first item in the list to 'Tea'
Copy
groceries .add('Tea');
groceries.add('Tea');
groceries.add('Sugar');
groceries.add('Honey');
groceries.add(2, 'Milk');
Line 11 demonstrates another way to add an item to a list, by inserting the item in a
specific index (position):
Demystifying Lists
Different ways of adding items to a list produce slightly different results; sometimes
unexpected results. Try this exercise.
5.
6. groceries.add('Tea');
7. groceries.add('Sugar');
9.
12.
Copy
13. Select the Open log checkbox and click Execute. The Execution Log opens,
displaying the result of running your code.
14. Select the Debug Only checkbox at the bottom of the window.
Interesting results:
When we added the two strings in lines 4 and 5, we didn’t specify an index, so Tea
and Sugar were added onto the end of the list, increasing the list size from four
strings to six strings. In line 8, we explicitly specified index 1 for Honey, so why isn’t
Honey first in the list? Remember when programming, we always start counting with
0 instead of 1. In line 11, the first index in the list is 0, which currently contains
a null value.
An expression is a snippet of code that, when evaluated, results in a single value. For
our purposes in this module, an expression might be a literal value (such as 12), or a
variable (such as numberOfTeaCups), or a mathematical operation (such as 7 x 4).
Comparison Operators
== Equal to 10 == 10
!= Not equal to 10 != 0
<> 10 <> 11
One equals sign ( = ) is an assignment operator. It assigns values. Two equals signs ( == ) is
a comparison operator. It compares two values to determine whether they are equal (true) or
not equal (false).
Conditional Statements
If-Else Statements
if (condition is true) {
//do this
} else {
//do this
If-Else If Statements
To handle more than two possibilities, we use an if-else if statement. The if-else if
statement adds another if condition before the final else condition. Let’s look at an
example in action.
2.
3. if(waterLevel == 'empty') {
5. waterLevel = 'full';
8. waterLevel = 'full';
9. } else { /*This statement only runs if line 2 and line 5 result in
false.*/
Switch Statements
switch on expression {
when value1 {
//code block 1
when value2 {
//code block 2
//code block 3
In a switch statement, you can have one or more values after the when reserved
word.
switch on expression {
//code block 1
//code block 2
}
}
EXAMPLE
switch on waterLevel {
when 'empty' {
when 'half' {
when 'full' {
when else {
System.debug('Error!');
switch on waterLevel {
}
when 'full' {
when else {
System.debug('Error!');
Use Loops
A loop is a block of code that is repeated until a certain condition is met. In the case
of the Teatime code, let’s review the steps to be repeated
The differentiator is when they verify that the condition is met. While loops check the
condition before the loop starts, and do-while loops check it after the loop is
finished.
Seems like such a small detail, right? Actually, the impact is pretty big.
Think of it this way: the do-while loop always runs at least once. The while loop
might never run, depending on the condition.
What’s an sObject?
An sObject is an Apex data type that corresponds to a Salesforce object (sObject) in
an org. sObjects are complex data types that hold multiple values in one variable.
They hold a single record of data from a Salesforce object, such as an Account, a
Contact, or an Opportunity. Remember from Apex Basics for Admins
that variables are like containers. Most variables hold one piece of information.
sObjects are containers that hold other containers. The containers within the sObject
container may be of different data types, such as string, date, integer or Boolean.
Because resources are limited, you can use the insert statement in your code only 150 times.
One record at a time, you can insert a total of 150 records. But if each insert statement
inserts 10 records, that total increases to 1,500 records. Inserting multiple objects at once
helps with bulkification (combining repetitive tasks by writing efficient code). At first you may
be inserting as few as 10 records at a time. But as your application grows, you might begin to
insert 200 records at a time. Using bulkification from the start prepares your application for
growth.
EXAMPLE
list<Account>addAccounts=new list<Account>();
Integer counter=1;
while(counter<=N)
acc.AccountNumber='A000'+1;
addAccounts.add(acc);
counter = counter + 1;
insert addAccounts;
Sets
Until now, you’ve created one type of collection, a list. A set is an unordered set of
unique items of the same type. Similar to a list, a set is a group of items, called
elements, and all elements have the same data type, such as string, integer, or even
Account. Unlike a list, a set maintains no particular order for its elements. Because
the elements are unordered, a set can't have any duplicates. If you try to add an
element that’s already in the set, you don’t get an error, but the new value is not
added to the set. Remember that when you loop through a list, you always access its
items in the order that the items were added to the list. When you loop through a
set, because the elements are unordered, you may access elements in a random
order.
Declaring a set is like declaring a list, but you substitute the keyword Set for List .
Let’s create a set of strings.
Remember that sets don’t allow repeated values. Also, keep in mind you can’t index a set like
you can with a List. Sets are typically used for checking if a certain value is included.
Maps
A map is a more complex collection than either a list or a set. Each item in a map has
two parts: a key and a value, known as a key-value pair. Keys and values can be any
data type. Although each key is unique, values can be repeated within a map.
Imagine a map of telephone country codes. The country code is the key and the
name of the country is the value. Each country code is unique, but the countries can
be duplicated (because a country may have more than one country code).
To add key-value pairs to a map, use the put method, like this:
The put method expects two parameters: key and value. Let’s create a map of tea types
(keys) and their flavor profiles (values).
When provided with an existing key, the get method returns the value of the key. If
the provided key is not mapped to a value, the get method returns null. Remember
that the map declaration specifies the data type it expects for a return value. The
variable that returns a value must be the same data type that the map declaration
specifies.
Collection Type Description
List An ordered group of items of the same type.
Each item has an index number that represents its position in the list.
Set An unordered group of unique items of the same type.
Map A collection of key-value pairs. Each unique key maps to a single
value.
The syntax for an iteration for loop is a little different than the traditional for loop
syntax. When you declare an iteration for loop, the data type of the variable must
match the data type of the list or set. Here’s the syntax for an iteration for loop.
// Loop body
Copy
List <String> tea = new List<String>{'Black Tea', 'Green Tea', 'Chai Tea'};
Note that the tea list has the string data type. To declare the iteration for loop, we
use the list data type (string) and the list name (tea).
A collection, including:
o A set of primitives
Lists hold an ordered collection of objects. Lists in Apex are synonymous with arrays
and the two can be used interchangeably.
The following two declarations are equivalent. The colors variable is declared using
the List syntax.
Copy
Alternatively, the colors variable can be declared as an array but assigned to a list
rather than an array.
1. Open the Developer Console under Your Name or the quick access menu ( ).
3. Replace the default class body with the EmailManager class example.
The EmailManager class has a public method ( sendMail() ) that sends email and
uses built-in Messaging methods of the Apex class library. Also, this class has
a private helper method ( inspectResults() ), which can’t be called externally
because it is private but is used only within the class. This helper method
inspects the results of the email send call and is called by sendMail() .
// Public method
mail .setToAddresses(toAddresses);
mail .setSubject(subject);
mail .setPlainTextBody(body);
new Messaging.SingleEmailMessage[]
{ mail });
inspectResults(results);
// Helper method
if (res.isSuccess()) {
else {
sendResult = false;
}
return sendResult;
Copy
Let’s invoke the public method. We’ll use anonymous Apex execution to do so.
Anonymous Apex allows you to run lines of code on the fly and is a handy way to
invoke Apex, especially to test out functionality. Debug log results are generated, as
with any other Apex execution.
Note
There are other ways to invoke Apex, for example, through triggers. You’ll learn more
about triggers in another module.
Copy
5. Click Execute.
Now that this method has executed, you should have received an email in
your inbox. Check your email!
Use sObjects
Because Apex is tightly integrated with the database, you can access Salesforce records and
their fields directly from Apex. Every record in Salesforce is natively represented as
an sObject in Apex. For example, the Acme account record corresponds to an Account
sObject in Apex. The fields of the Acme record that you can view and modify in the user
interface can be read and modified directly on the sObject as well.
The following table lists some populated fields of the Acme account example record.
The Account sObject is an abstraction of the account record and holds the account
field information in memory as an object.
Id 001D000000JlfXe
Name Acme
Phone (415)555-1212
NumberOfEmployees 100
Standard and custom object records in Salesforce map to their sObject types in Apex.
Here are some common sObject type names in Apex used for standard objects.
Account
Contact
Lead
Opportunity
If you’ve added custom objects in your organization, use the API names of the
custom objects in Apex. For example, a custom object called Merchandise
corresponds to the Merchandise__c sObject in Apex.
Before you can insert a Salesforce record, you must create it in memory first as an
sObject. Like with any other object, sObjects are created with the new operator:
Account acct = new Account();
Copy
The API object name becomes the data type of the sObject variable in Apex. In this
example, Account is the data type of the acct variable.
The account referenced by the acct variable is empty because we haven’t populated
any of its fields yet. There are two ways to add fields: through the constructor or by
using dot notation.
The fastest way to add fields is to specify them as name-value pairs inside the
constructor. For example, this statement creates a new account sObject and
populates its Name field with a string value.
Copy
The Name field is the only required field for accounts, which means that it has to be
populated before being able to insert a new record. However, you can populate
other fields as well for the new record. This example adds also a phone number and
the number of employees.
Copy
Alternatively, you can use the dot notation to add fields to an sObject. The following
is equivalent to the previous example, although it takes a few more lines of code.
Variables that are declared with the generic sObject data type can reference any
Salesforce record, whether it is a standard or custom object record.
This example shows how the generic sObject variable can be assigned to any
Salesforce object: an account and a custom object called Book__c.
Copy
In contrast, variables that are declared with the specific sObject data type can
reference only the Salesforce records of the same type.
When you’re dealing with generic sObjects, you sometimes need to cast your sObject
variable to a specific sObject type. One of the benefits of doing so is to be able to
access fields using dot notation, which is not available on the generic sObject. Since
sObject is a parent type for all specific sObject types, you can cast a generic sObject
to a specific sObject. This example shows how to cast a generic sObject to Account.
// Now, you can use the dot notation to access fields on Account
DML Statements
insert
update
upsert
delete
undelete
merge
Each DML statement accepts either a single sObject or a list (or array) of sObjects.
Operating on a list of sObjects is a more efficient way for processing records.
The upsert DML operation creates new records and updates sObject records within a
single statement, using a specified field to determine the presence of existing
objects, or the ID field if no field is specified.
The merge statement merges up to three records of the same sObject type into one
of the records, deleting the others, and re-parenting any related records.
ID Field Auto-Assigned to New Records
When inserting records, the system assigns an ID for each record. In addition to
persisting the ID value in the database, the ID value is also autopopulated on the
sObject variable that you used as an argument in the DML call.
This example shows how to get the ID on the sObject that corresponds to the
inserted account.
insert acct ;
ID acctID = acct.Id;
// DEBUG|ID = 001D000000JmKkeIAF
You can retrieve a record from the database to obtain its fields, including the ID field, but
this can’t be done with DML. You’ll need to write a query by using SOQL. You’ll learn about
SOQL in another unit.
Bulk DML
You can perform DML operations either on a single sObject, or in bulk on a list of
sObjects. Performing bulk DML operations is the recommended way because it helps
avoid hitting governor limits, such as the DML limit of 150 statements per Apex
transaction. This limit is in place to ensure fair access to shared resources in the
Lightning Platform. Performing a DML operation on a list of sObjects counts as one
DML statement, not as one statement for each sObject.
This example inserts contacts in bulk by inserting a list of contacts in one call. The
sample then updates those contacts in bulk too.
1. Execute this snippet in the Developer Console using Anonymous Apex.
4. new
Contact(FirstName='Joe',LastName='Smith',Department='Finance'),
5. new
Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),
6. new
Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),
7. new
Contact(FirstName='Kim',LastName='Shain',Department='Education')};
8.
19. listToUpdate.add(con);
20. }
21. }
Two of the contacts who are in the Finance department should have their
titles populated with Financial analyst.
Upserting Records
If you have a list containing a mix of new and existing records, you can process
insertions and updates to all records in the list by using the upsert statement. Upsert
helps avoid the creation of duplicate records and can save you time as you don’t
have to determine which records exist first.
The upsert statement matches the sObjects with existing records by comparing
values of one field. If you don’t specify a field when calling this statement,
the upsert statement uses the sObject’s ID to match the sObject with existing
records in Salesforce. Alternatively, you can specify a field to use for matching. For
custom objects, specify a custom field marked as external ID. For standard objects,
you can specify any field that has the idLookup property set to true. For example, the
Email field of Contact or User has the idLookup property set. To check a field’s
property, see the Object Reference for Salesforce and Lightning Platform.
Upsert Syntax
The optional field is a field token. For example, to specify the MyExternalID field, the
statement is:
Copy
Upsert uses the sObject record's primary key (the ID), an idLookup field, or an
external ID field to determine whether it should create a new record or update an
existing one:
This example shows how upsert updates an existing contact record and inserts a new
contact in one call. This upsert call updates the existing Josh contact and inserts a
new contact, Kathy.
Note
The upsert call uses the ID to match the first contact. The josh variable is being
reused for the upsert call. This variable has already been populated with the record
ID from the previous insert call, so the ID doesn’t need to be set explicitly in this
example.
4. insert josh;
Copy
Your org will have only one Josh Kaplan record, not two, because the upsert
operation found the existing record and updated it instead of creating a new
contact record. One Kathy Brown contact record will be there too.
Alternatively, you can specify a field to be used for matching records. This example
uses the Email field on Contact because it has idLookup property set. The example
inserts the Jane Smith contact, and creates a second Contact sObject, populates it
with the same email, then calls upsert to update the contact by using the email field
for matching.
Alternatively, you can specify a field to be used for matching records. This example
uses the Email field on Contact because it has idLookup property set. The example
inserts the Jane Smith contact, and creates a second Contact sObject, populates it
with the same email, then calls upsert to update the contact by using the email field
for matching.
Note
If insert was used in this example instead of upsert , a duplicate Jane Smith contact
would have been inserted.
3. LastName='Smith',
4. Email='jane.smith@example.com',
6. insert jane;
11. LastName='Smith',
12. Email='jane.smith@example.com',
14. // Upsert the contact by using the idLookup field for matching.
Copy
Your org will have only one Jane Smith contact with the updated description.
Database Methods
Apex contains the built-in Database class, which provides methods that perform DML
operations and mirror the DML statement counterparts.
These Database methods are static and are called on the class name.
Database.insert()
Database.update()
Database.upsert()
Database.delete()
Database.undelete()
Database.merge()
This is how you call the insert method with the allOrNone set to false .
Copy
The Database methods return result objects containing success or failure information
for each record. For example, insert and update operations each return an array
of Database.SaveResult objects.
Copy
Note
Upsert returns Database.UpsertResult objects, and delete
returns Database.DeleteResult objects.
By default, the allOrNone parameter is true , which means that the Database method
behaves like its DML statement counterpart and will throw an exception if a failure is
encountered.
The following two statements are equivalent to the insert recordList; statement.
Database .insert(recordList);
Copy
And:
Copy
Beyond the Basics
In addition to these methods, the Database class contains methods that aren’t
provided as DML statements. For example, methods used for transaction control and
rollback, for emptying the Recycle Bin, and methods related to SOQL queries. You’ll
learn about SOQL in another unit.
Example: Inserting Records with Partial Success
Let’s take a look at an example that uses the Database methods. This example is
based on the bulk DML example, but replaces the DML statement with a Database
method. The Database.insert() method is called with the partial success option. One
contact in the list doesn’t have any fields on purpose and will cause an error because
the contact can’t be saved without the required LastName field. Three contacts are
committed and the contact without any fields generates an error. The last part of this
example iterates through the returned results and writes debug messages to the
debug log.
4. new
Contact(FirstName='Joe',LastName='Smith',Department='Finance'),
5. new
Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),
6. new
Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),
7. new Contact()};
8.
13. if (sr.isSuccess()) {
14. // Operation was successful, so get the ID of the record
that was processed
16. } else {
22. }
23. }
24. }
Copy
25. Verify the debug messages (use the DEBUG keyword for the filter).
One failure should be reported and three contacts should have been inserted.
Use DML statements if you want any error that occurs during bulk DML
processing to be thrown as an Apex exception that immediately interrupts
control flow (by using try. . .catch blocks). This behavior is similar to the way
exceptions are handled in most database procedural languages.
Use Database class methods if you want to allow partial success of a bulk DML
operation—if a record fails, the remainder of the DML operation can still
succeed. Your application can then inspect the rejected records and possibly
retry the operation. When using this form, you can write code that never
throws DML exception errors. Instead, your code can use the appropriate
results array to judge success or failure. Note that Database methods also
include a syntax that supports thrown exceptions, similar to DML statements.
Inserting Related Records
You can insert records related to existing records if a relationship has already been
defined between the two objects, such as a lookup or master-detail relationship. A
record is associated with a related record through a foreign key ID. For example, if
inserting a new contact, you can specify the contact's related account record by
setting the value of the AccountId field.
This example shows how to add a contact to an account (the related record) by
setting the AccountId field on the contact. Contact and Account are linked through a
lookup relationship.
3. insert acct;
7. ID acctID = acct.ID;
10. FirstName='Mario',
11. LastName='Ruiz',
12. Phone='415.555.1212',
13. AccountId=acctID);
Copy
A new account (SFDC Account) has been created and has the Mario Ruiz
contact in the account’s Contacts related list.
Updating Related Records
Fields on related records can't be updated with the same call to the DML operation and
require a separate DML call. For example, if inserting a new contact, you can specify the
contact's related account record by setting the value of the AccountId field. However, you
can't change the account's name without updating the account itself with a separate DML
call. Similarly, when updating a contact, if you also want to update the contact’s related
account, you must make two DML calls. The following example updates a contact and its
related account using two update statements.
// Query for the contact, which has been associated with an account.
FROM Contact
LIMIT 1];
update queriedContact ;
update queriedContact.Account;
Copy
The delete operation supports cascading deletions. If you delete a parent object, you
delete its children automatically, as long as each child record can be deleted.
For example, deleting the account you created earlier (SFDC Account) will delete its
related contact too.
1. Execute this snippet in the Anonymous Apex window of the Developer
Console.
delete queriedAccounts ;
Copy
You’ll see that both the account and its related contact were deleted.
Unlike other SQL languages, you can’t specify * for all fields. You must specify every
field you want to get explicitly. If you try to access a field you haven’t specified in the
SELECT clause, you’ll get an error because the field hasn’t been retrieved.
You don’t need to specify the Id field in the query as it is always returned in Apex
queries, whether it is specified in the query or not. For example: SELECT Id,Phone FROM
Account and SELECT Phone FROM Account are equivalent statements. The only time you
may want to specify the Id field if it is the only field you’re retrieving because you
have to list at least one field: SELECT Id FROM Account . You may want to specify the Id
field also when running a query in the Query Editor as the ID field won’t be displayed
unless specified.
Filtering Query Results with Conditions
If you have more than one account in the org, they will all be returned. If you want to
limit the accounts returned to accounts that fulfill a certain condition, you can add
this condition inside the WHERE clause. The following example retrieves only the
accounts whose names are SFDC Computing. Note that comparisons on strings are
case-insensitive.
Copy
The WHERE clause can contain multiple conditions that are grouped by using logical
operators (AND, OR) and parentheses. For example, this query returns all accounts
whose name is SFDC Computing that have more than 25 employees:
SOQL statements in Apex can reference Apex code variables and expressions if they
are preceded by a colon (:). The use of a local variable within a SOQL statement is
called a bind.
This example shows how to use the targetDepartment variable in the WHERE clause.
To get child records related to a parent record, add an inner query for the child
records. The FROM clause of the inner query runs against the relationship name,
rather than a Salesforce object name. This example contains an inner query to get all
contacts that are associated with each returned account. The FROM clause specifies
the Contacts relationship, which is a default relationship on Account that links
accounts and contacts.
SELECT Name, (SELECT LastName FROM Contacts) FROM Account WHERE Name =
'SFDC Computing'
Copy
This next example embeds the example SOQL query in Apex and shows how to get
the child records from the SOQL result by using the Contacts relationship name on
the sObject.
FROM Account
WHERE Name = 'SFDC Computing'];
Copy
You can traverse a relationship from a child object (contact) to a field on its parent
(Account.Name) by using dot notation. For example, the following Apex snippet
queries contact records whose first name is Carol and is able to retrieve the name of
Carol’s associated account by traversing the relationship between accounts and
contacts.
Copy
Note
The examples in this section are based on standard objects. Custom objects can also
be linked together by using custom relationships. Custom relationship names end
with the __r suffix. For example, invoice statements are linked to line items through
the Line_Items__r relationship on the Invoice_Statement__c custom object.
Querying Record in Batches By Using SOQL For Loops
With a SOQL for loop, you can include a SOQL query within a for loop. The results of
a SOQL query can be iterated over within the loop. SOQL for loops use a different
method for retrieving records—records are retrieved using efficient chunking with
calls to the query and queryMore methods of the SOAP API. By using SOQL for
loops, you can avoid hitting the heap size limit.
SOQL for loops iterate over all of the sObject records returned by a SOQL query. The syntax
of a SOQL for loop is either:
code_block
Copy
or
code_block
Copy
Both variable and variable_list must be of the same type as the sObjects that are
returned by the soql_query .
It is preferable to use the sObject list format of the SOQL for loop as the loop
executes once for each batch of 200 sObjects. Doing so enables you to work on
batches of records and perform DML operations in batch, which helps avoid reaching
governor limits.
// The sObject list format executes the for loop once per returned batch
// of records
Integer i=0;
Integer j=0;
for (Account[] tmp : [SELECT Id FROM Account WHERE Name LIKE 'for loop
_']) {
j = tmp.size();
i ++;
System .assertEquals(3, j); // The list should have contained the three
accounts
// named 'yyy'
Adding SOSL queries to Apex is simple—you can embed SOSL queries directly in
your Apex code. When SOSL is embedded in Apex, it is referred to as inline SOSL.
This is an example of a SOSL query that searches for accounts and contacts that have
any fields with the word 'SFDC'.
RETURNING Account(Name),
Contact(FirstName,LastName)];
Copy
Like SOQL, SOSL allows you to search your organization’s records for specific
information. Unlike SOQL, which can only query one standard or custom object at a
time, a single SOSL query can search all objects.
Another difference is that SOSL matches fields based on a word match while SOQL
performs an exact match by default (when not using wildcards). For example,
searching for 'Digital' in SOSL returns records whose field values are 'Digital' or 'The
Digital Company', but SOQL returns only records with field values of 'Digital'.
SOQL and SOSL are two separate languages with different syntax. Each language has
a distinct use case:
Use SOSL to search fields across multiple objects. SOSL queries can search
most text fields on an object.
Prerequisites
Some queries in this unit expect the org to have accounts and contacts. If you
haven’t created the sample data in the SOQL unit, create sample data in this unit.
Otherwise, you can skip creating the sample data in this section.
Name='SFDC Computing',
Phone='(415)555-1212',
NumberOfEmployees=50,
BillingCity='San Francisco');
insert acct ;
ID acctID = acct.ID;
LastName='Ruiz',
Phone='(415)555-1212',
Department='Wingo',
AccountId=acctID);
insert con ;
Phone='(310)555-1213',
NumberOfEmployees=50,
BillingCity='Los Angeles',
insert acct2 ;
Copy
The Developer Console provides the Query Editor console, which enables you to run
SOSL queries and view results. The Query Editor provides a quick way to inspect the
database. It is a good way to test your SOSL queries before adding them to your
Apex code. When you use the Query Editor, you need to supply only the SOSL
statement without the Apex code that surrounds it.
2. Copy and paste the following into the first box under Query Editor, and then
click Execute.
All account and contact records in your org that satisfy the criteria will display in the
Query Results section as rows with fields. The results are grouped in tabs for each
object (account or contact). The SOSL query returns records that have fields whose
values match Wingo. Based on our sample data, only one contact has a field with the
value Wingo, so this contact is returned..
Note
The search query in the Query Editor and the API must be enclosed within curly
brackets ( {Wingo} ). In contrast, in Apex the search query is enclosed within single
quotes ( 'Wingo' ).
Basic SOSL Syntax
SOSL allows you to specify the following search criteria:
Copy
SearchQuery is the text to search for (a single word or a phrase). Search terms can be
grouped with logical operators (AND, OR) and parentheses. Also, search terms can
include wildcard characters (*, ?). The * wildcard matches zero or more characters at
the middle or end of the search term. The ? wildcard matches only one character at
the middle or end of the search term.
Text searches are case-insensitive. For example, searching for Customer , customer ,
or CUSTOMER all return the same results.
SearchGroup is optional. It is the scope of the fields to search. If not specified, the
default search scope is all fields. SearchGroup can take one of the following values.
ALL FIELDS
NAME FIELDS
EMAIL FIELDS
PHONE FIELDS
SIDEBAR FIELDS
Single Word— single word, such as test or hello . Words in
the SearchQuery are delimited by spaces, punctuation, and changes from
letters to digits (and vice-versa). Words are always case insensitive.
Search Examples
To learn about how SOSL search works, let’s play with different search strings and
see what the output is based on our sample data. This table lists various example
search strings and the SOSL search results.
Search in all
Search Description Matched Re
fields for:
The Query This search returns all records whose fields contain both words: The and Query, in any Account: Th
location of the text. The order of words in the search term doesn’t matter. matched)
Wingo OR This search uses the OR logical operator. It returns records with fields containing the word Contact: Ca
Man Wingo or records with fields containing the word Man.
Account: T
field match
Search in all
Search Description Matched Re
fields for:
1212 This search returns all records whose fields contain the word 1212. Phone fields that end Account: Th
with -1212 are matched because 1212 is considered a word when delimited by the dash. '(415)555-1
Contact: C
1212'
wing* This is a wildcard search. This search returns all records that have a field value starting Contact: M
with wing.
Account: T
Descriptio
This example shows how to run a SOSL query in Apex. This SOSL query combines two
search terms by using the OR logical operator—it searches for Wingo or SFDC in any
field. This example returns all the sample accounts because they each have a field
containing one of the words. The SOSL search results are returned in a list of lists.
Each list contains an array of the returned records. In this case, the list has two
elements. At index 0, the list contains the array of accounts. At index 1, the list
contains the array of contacts.
Execute this snippet in the Execute Anonymous window of the Developer Console.
Next, inspect the debug log to verify that all records are returned.
RETURNING
Account(Name),Contact(FirstName,LastName,Department)];
System.debug(a.Name);
Types of Triggers
After triggers are used to access field values that are set by the system (such
as a record's Id or LastModifiedDate field), and to affect changes in other
records. The records that fire the after trigger are read-only.
To access the records that caused the trigger to fire, use context variables. For
example, Trigger.New contains all the records that were inserted in insert or update
triggers. Trigger.Old provides the old version of sObjects before they were updated
in update triggers, or a list of deleted sObjects in delete triggers. Triggers can fire
when one record is inserted, or when many records are inserted in bulk via the API or
Apex. Therefore, context variables, such as Trigger.New , can contain only one record
or multiple records. You can iterate over Trigger.New to get each individual sObject.
for(Account a : Trigger.New) {
Copy
Note
The system saves the records that fired the before trigger after the trigger finishes
execution. You can modify the records in the trigger without explicitly calling a DML
insert or update operation. If you perform DML statements on those records, you get
an error.
Trigger Context Variables
The following table is a comprehensive list of all context variables available for
triggers.
Variable Usage
isExecuting Returns true if the current context for the Apex code is a trigger, not a Visualforce page, a
isInsert Returns true if this trigger was fired due to an insert operation, from the Salesforce user interface
isUpdate Returns true if this trigger was fired due to an update operation, from the Salesforce user interfa
isDelete Returns true if this trigger was fired due to a delete operation, from the Salesforce user interface
isBefore Returns true if this trigger was fired before any record was saved.
isAfter Returns true if this trigger was fired after all records were saved.
isUndelete Returns true if this trigger was fired after a record is recovered from the Recycle Bin.
This recovery can occur after an undelete operation from the Salesforce user interface, Apex, or
the API.
This sObject list is only available in insert , update , and undelete triggers, and the
This map is only available in before update , after insert , after update , and after undel
This sObject list is only available in update and delete triggers.
consider using the switch statement with different permutations of unique trigger
Size The total number of records in a trigger invocation, both old and new.
You can call public utility methods from a trigger. Calling methods of other classes
enables code reuse, reduces the size of your triggers, and improves maintenance of
your Apex code. It also allows you to use object-oriented programming.
The following example trigger shows how to call a static method from a trigger. If the
trigger was fired because of an insert event, the example calls the
static sendMail() method on the EmailManager class. This utility method sends an email
to the specified recipient and contains the number of contact records inserted.
Note
The EmailManager class is included in the class example of the Get Started with Apex
unit. You must have saved the EmailManager class in your org and changed
the sendMail() method to static before saving this trigger.
1. In the Developer Console, click File | New | Apex Trigger.
3. Replace the default code with the following, and then modify the email
address placeholder text in sendMail() to your email address.
5. if (Trigger.isInsert) {
10. }
13. }
Copy
insert c ;
Copy
16. In the debug log, check that the trigger was fired. Toward the end of the log,
find the debug message that was written by the utility method: DEBUG|Email
sent successfully
17. Now check that you received an email with the body text 1 contact(s) were
inserted.
Let’s apply the design patterns you’ve learned by writing a trigger that accesses
accounts’ related opportunities. Modify the trigger example from the previous unit
for the AddRelatedRecord trigger. The AddRelatedRecord trigger operates in bulk, but is
not as efficient as it could be because it iterates over all Trigger.New sObject records.
This next example modifies the SOQL query to get only the records of interest and
then iterate over those records. If you haven’t created this trigger, don’t worry—you
can create it in this section.
Let’s start with the requirements for the AddRelatedRecord trigger. The trigger fires
after accounts are inserted or updated. The trigger adds a default opportunity for
every account that doesn’t already have an opportunity. The first problem to tackle is
to figure out how to get the child opportunity records. Because this trigger is
an after trigger, we can query the affected records from the database. They’ve
already been committed by the time the after trigger is fired. Let’s write a SOQL
query that returns all accounts in this trigger that don’t have related opportunities.
Copy
Now that we got the subset of records we’re interested in, let’s iterate over those
records by using a SOQL for loop, as follows.
Copy
You’ve now seen the basics of our trigger. The only missing piece is the creation of
the default opportunity, which we’re going to do in bulk. Here’s the complete trigger.
4.
6. // Iterate over accounts that are in this trigger but that don't
have opportunities.
12. StageName='Prospecting',
13.
CloseDate=System.today().addMonths(1),
14. AccountId=a.Id));
15. }
16.
19. }
}
Copy
20. To test the trigger, create an account in the Salesforce user interface and
name it Lions & Cats.
21. In the Opportunities related list on the account’s page, find the new
opportunity Lions & Cats. The trigger added the opportunity automatically!
Asynchronous Apex
In a nutshell, asynchronous Apex is used to run processes in a separate thread, at a
later time.
User efficiency
Let's say you have a process that makes many calculations on a custom object
whenever an Opportunity is created. The time needed to execute these calculations
could range from a minor annoyance to a productivity blocker for the user. Since
these calculations don't affect what the user is currently doing, making them wait for
a long running process is not an efficient use of their time. With asynchronous
processing the user can get on with their work, the processing can be done in the
background and the user can see the results at their convenience.
Scalability
Higher Limits
Asynchronous processes are started in a new thread, with higher governor and
execution limits. And to be honest, doesn’t everyone want higher governor and
execution limits?
Asynchronous Apex comes in a number of different flavors. We’ll get into more detail
for each one shortly, but here’s a high level overview.
Callouts to external Web services. If you are making callouts from a trigger or after
performing a DML operation, you must use a future or queueable method. A callout
in a trigger would hold the database connection open for the lifetime of the callout
and that is a "no-no" in a multitenant environment.
Operations you want to run in their own thread, when time permits such as some sort
of resource-intensive calculation or processing of records.
Isolating DML operations on different sObject types to prevent the mixed DML error.
This is somewhat of an edge-case but you may occasionally run across this issue.
See sObjects That Cannot Be Used Together in DML Operations for more details.
The reason why objects can’t be passed as arguments to future methods is
because the object can change between the time you call the method and the
time that it actually executes. Remember, future methods are executed when
system resources become available. In this case, the future method may have
an old object value when it actually executes, which can cause all sorts of bad
things to happen.
It’s important to note that future methods are not guaranteed to execute in
the same order as they are called. Again, future methods are not guaranteed
to execute in the same order as they are called. When using future methods,
it’s also possible that two future methods could run concurrently, which could
result in record locking and a nasty runtime error if the two methods were
updating the same record.
Test Classes-@isTest
Testing future methods is a little different than typical Apex testing. To test future
methods, enclose your test code between the startTest and stopTest test methods.
The system collects all asynchronous calls made after the startTest. When stopTest is
executed, all these collected asynchronous processes are then run synchronously.
You can then assert that the asynchronous call operated properly.
Things to Remember
Future methods are a great tool, but with great power comes great responsibility.
Here are some things to keep in mind when using them:
Methods with the future annotation must be static methods, and can only
return a void type.
The specified parameters must be primitive data types, arrays of primitive data
types, or collections of primitive data types; future methods can’t take objects
as arguments.
Future methods won’t necessarily execute in the same order they are called. In
addition, it’s possible that two future methods could run concurrently, which
could result in record locking if the two methods were updating the same
record.
Future methods can’t be used in Visualforce controllers in getMethodName(),
setMethodName(), nor in the constructor.
You can’t call a future method from a future method. Nor can you invoke a
trigger that calls a future method while running a future method. See the link
in the Resources for preventing recursive future method calls.
The getContent() and getContentAsPDF() methods can’t be used in methods
with the future annotation.
You’re limited to 50 future calls per Apex invocation, and there’s an additional
limit on the number of calls in a 24-hour period. For more information on
limits, see the link below.
Test code cannot actually send callouts to external systems, so you’ll have to ‘mock’
the callout for test coverage. Check out the Apex Integration Services module for
complete details on mocking callouts for testing.
Batch Apex
Batch Apex is used to run large jobs (think thousands or millions of records!) that
would exceed normal processing limits. Using Batch Apex, you can process records
asynchronously in batches (hence the name, “Batch Apex”) to stay within platform
limits. If you have a lot of records to process, for example, data cleansing or
archiving, Batch Apex is probably your best solution.
Here’s how Batch Apex works under the hood. Let’s say you want to process 1 million
records using Batch Apex. The execution logic of the batch class is called once for
each batch of records you are processing. Each time you invoke a batch class, the job
is placed on the Apex job queue and is executed as a discrete transaction. This
functionality has two awesome advantages:
Every transaction starts with a new set of governor limits, making it easier to
ensure that your code stays within the governor execution limits.
If one batch fails to process successfully, all other successful batch
transactions aren’t rolled back.
start
Used to collect the records or objects to be passed to the interface method execute
for processing. This method is called once at the beginning of a Batch Apex job and
returns either a Database.QueryLocator object or an Iterable that contains the
records or objects passed to the job.
Most of the time a QueryLocator does the trick with a simple SOQL query to
generate the scope of objects in the batch job. But if you need to do something
crazy like loop through the results of an API call or pre-process records before being
passed to the execute method, you might want to check out the Custom Iterators
link in the Resources section.
With the QueryLocator object, the governor limit for the total number of records
retrieved by SOQL queries is bypassed and you can query up to 50 million records.
However, with an Iterable, the governor limit for the total number of records
retrieved by SOQL queries is still enforced.
execute
Performs the actual processing for each chunk or “batch” of data passed to the
method. The default batch size is 200 records. Batches of records are not guaranteed
to execute in the order they are received from the start method.
finish
Used to execute post-processing operations (for example, sending an email) and is
called once after all batches are processed.
Copy
Invoking a Batch Class
To invoke a batch class, simply instantiate it and then call Database.executeBatch
with the instance:
Id batchId = Database.executeBatch(myBatchObject);
Copy
You can also optionally pass a second scope parameter to specify the number of
records that should be passed into the execute method for each batch. Pro tip: you
might want to limit this batch size if you are running into governor limits.
Each batch Apex invocation creates an AsyncApexJob record so that you can track
the job’s progress. You can view the progress via SOQL or manage your job in the
Apex Job Queue. We’ll talk about the Job Queue shortly.
The following sample class finds all account records that are passed in by the start()
method using a QueryLocator and updates the associated contacts with their
account’s mailing address. Finally, it sends off an email with the results of the bulk
job and, since we are using Database.Stateful to track state, the number of records
updated.
Database.Batchable<sObject>, Database.Stateful {
// instance member to retain state across transactions
return Database.getQueryLocator(
);
contact.MailingStreet = account.BillingStreet;
contact.MailingCity = account.BillingCity;
contact.MailingState = account.BillingState;
contact.MailingPostalCode = account.BillingPostalCode;
contacts.add(contact);
recordsProcessed = recordsProcessed + 1;
}
update contacts;
JobItemsProcessed,
TotalJobItems, CreatedBy.Email
FROM AsyncApexJob
WHERE Id = :bc.getJobId()];
EmailUtils.sendMessage(job, recordsProcessed);
Best Practices
As with future methods, there are a few things you want to keep in mind when using
Batch Apex. To ensure fast execution of batch jobs, minimize Web service callout
times and tune queries used in your batch Apex code. The longer the batch job
executes, the more likely other queued jobs are delayed when many jobs are in the
queue. Best practices include:
Only use Batch Apex if you have more than one batch of records. If you don't
have enough records to run more than one batch, you are probably better off
using Queueable Apex.
Tune any SOQL query to gather the records to execute as quickly as possible.
Minimize the number of asynchronous requests created to minimize the
chance of delays.
Use extreme care if you are planning to invoke a batch job from a trigger. You
must be able to guarantee that the trigger won’t add more batch jobs than
the limit.
Queueable Apex
Released in Winter '15, Queueable Apex is essentially a superset of future methods
with some extra #awesomesauce. We took the simplicity of future methods and the
power of Batch Apex and mixed them together to form Queueable Apex! It gives you
a class structure that the platform serializes for you, a simplified interface without
start and finish methods and even allows you to utilize more than just primitive
arguments! It is called by a simple System.enqueueJob() method, which returns a job
ID that you can monitor. It beats sliced bread hands down!
Queueable Apex allows you to submit jobs for asynchronous processing similar to
future methods with the following additional benefits:
@future
mySyncMethod(params);
Copy
Queueable Syntax
To use Queueable Apex, simply implement the Queueable interface.
Things to Remember
Queueable Apex is a great new tool but there are a few things to watch out for:
The execution of a queued job counts once against the shared limit for asynchronous
Apex method executions.
You can add up to 50 jobs to the queue with System.enqueueJob in a single
transaction.
When chaining jobs, you can add only one job from an executing job with
System.enqueueJob, which means that only one child job can exist for each parent
queueable job. Starting multiple child jobs from the same queueable job is a no-no.
No limit is enforced on the depth of chained jobs, which means that you can chain
one job to another job and repeat this process with each new child job to link it to a
new child job. However, for Developer Edition and Trial orgs, the maximum stack
depth for chained jobs is 5, which means that you can chain jobs four times and the
maximum number of jobs in the chain is 5, including the initial parent queueable job.
Scheduled Apex Syntax
To invoke Apex classes to run at specific times, first implement the Schedulable
interface for the class. Then, schedule an instance of the class to run at a specific time
using the System.schedule method.
Copy
The class implements the Schedulable interface and must implement the only
method that this interface contains, which is the execute method.
The parameter of this method is a SchedulableContext object. After a class has been
scheduled, a CronTrigger object is created that represents the scheduled job. It
provides a getTriggerId method that returns the ID of a CronTrigger API object.
Sample Code
This class queries for open opportunities that should have closed by the current date,
and creates a task on each one to remind the owner to update the opportunity.
FROM Opportunity
TaskUtils.remindOwners(opptys);
}
Copy
You can schedule your class to run either programmatically or from the Apex
Scheduler UI.
Use extreme care if you’re planning to schedule a class from a trigger. You must be
able to guarantee that the trigger won’t add more scheduled job classes than the
limit. In particular, consider API bulk updates, import wizards, mass record changes
through the user interface, and all cases where more than one record can be updated
at a time.
The System.Schedule method takes three arguments: a name for the job, a CRON
expression used to represent the time and date the job is scheduled to run, and the
name of the class.
Copy
For more information on the CRON expression used for scheduling, see the “Using
the System.Schedule Method” section in Apex Scheduler.
1. From Setup, enter Apex in the Quick Find box, then select Apex Classes.
2. Click Schedule Apex.
3. For the job name, enter something like Daily Oppty Reminder.
4. Click the lookup button next to Apex class and enter * for the search term to get a list
of all classes that can be scheduled. In the search results, click the name of your
scheduled class.
5. Select Weekly or Monthly for the frequency and set the frequency desired.
6. Select the start and end dates, and a preferred start time.
7. Click Save.
Before you can deploy your code or package it for the Lightning Platform
AppExchange, at least 75% of Apex code must be covered by tests, and all those
tests must pass. In addition, each trigger must have some coverage. Even though
code coverage is a requirement for deployment, don’t write tests only to meet this
requirement. Make sure to test the common use cases in your app, including positive
and negative test cases, and bulk and single-record processing.
// code_block
Copy
// code_block
Copy
Using the isTest annotation instead of the testMethod keyword is more flexible as
you can specify parameters in the annotation. We’ll cover one such parameter later.
The visibility of a test method doesn’t matter, so declaring a test method as public or
private doesn’t make a difference as the testing framework is always able to access
test methods. For this reason, the access modifiers are omitted in the syntax.
Test methods must be defined in test classes, which are classes annotated
with isTest . This sample class shows a definition of a test class with one test method.
@isTest
// code_block
Copy
Test classes can be either private or public. If you’re using a test class for unit testing
only, declare it as private. Public test classes are typically used for test data factory
classes, which are covered later.
The following simple example is of a test class with three test methods. The class
method that’s being tested takes a temperature in Fahrenheit as an input. It converts
this temperature to Celsius and returns the converted result. Let’s add the custom
class and its test class.
8. }
Copy
11. @isTest
15. System.assertEquals(21.11,celsius);
16. }
17.
20. System.assertEquals(0,celsius);
21. }
25. }
26.
27. @isTest static void testNegativeTemp() {
29. System.assertEquals(-23.33,celsius);
30. }
31.
Copy
The TemperatureConverterTest test class verifies that the method works as expected by
calling it with different inputs for the temperature in Fahrenheit. Each test method
verifies one type of input: a warm temperature, the freezing point temperature, the
boiling point temperature, and a negative temperature. The verifications are done by
calling the System.assertEquals() method, which takes two parameters: the first is the
expected value, and the second is the actual value. There is another version of this
method that takes a third parameter—a string that describes the comparison being
done, which is used in testBoilingPoint() . This optional string is logged if the
assertion fails.
3. To add all the test methods in the TemperatureConverterTest class to the test
run, click Add Selected.
4. Click Run.
5. In the Tests tab, you see the status of your tests as they’re running. Expand the
test run, and expand again until you see the list of individual tests that were
run. They all have green checkmarks.
After you run tests, code coverage is automatically generated for the Apex classes
and triggers in the org. You can check the code coverage percentage in the Tests tab
of the Developer Console. In this example, the class you’ve tested,
the TemperatureConverter class, has 100% coverage, as shown in this image.
Note
Whenever you modify your Apex code, rerun your tests to refresh code coverage
results.
A known issue with the Developer Console prevents it from updating code coverage
correctly when running a subset of tests. To update your code coverage results,
use Test | Run All rather than Test | New Run.
Up to this point, all tests pass because the conversion formula used in the class
method is correct. But that’s boring! Let’s try to simulate a failure just to see what
happens when an assertion fails. For example, let’s modify the boiling point
temperature test and pass in a false expected value for the boiling point Celsius
temperature (0 instead of 100). This causes the corresponding test method to fail.
3. Decimal celsius =
TemperatureConverter.FahrenheitToCelsius(212);
4. // Simulate failure
Copy
6. To execute the same test run, click the latest run in the Tests tab, and then
click Test | Rerun.
The assertion in testBoilingPoint() fails and throws a fatal error (an
AssertException that can’t be caught).
7. Check the results in the Tests tab by expanding the latest test run. The test run
reports one out of four tests failed. To get more details about the failure,
double-click the test run.
The test data in these test methods are numbers and not Salesforce records. You’ll
find out more about how to test Salesforce records and how to set up your data in
the next unit.
When writing tests, try to achieve the highest code coverage possible. Don’t just aim
for 75% coverage, which is the lowest coverage that the Lightning Platform requires
for deployments and packages. The more test cases that your tests cover, the higher
the likelihood that your code is robust. Sometimes, even after you write test methods
for all your class methods, code coverage is not at 100%. One common cause is not
covering all data values for conditional code execution. For example, some data
values tend to be ignored when your class method has if statements that cause
different branches to be executed based on whether the evaluated condition is met.
Ensure that your test methods account for these different values.
This example includes the class method, getTaskPriority() , which contains
two if statements. The main task of this method is to return a priority string value
based on the given lead state. The method validates the state first and returns null if
the state is invalid. If the state is CA, the method returns 'High'; otherwise, it returns
'Normal' for any other state value.
// Validate input
return null;
String taskPriority;
if (leadState == 'CA') {
taskPriority = 'High';
} else {
taskPriority = 'Normal';
return taskPriority;
Copy
Note
The equality operator ( == ) performs case-insensitive string comparisons, so there is
no need to convert the string to lower case first. This means that passing
in 'ca' or 'Ca' will satisfy the equality condition with the string literal 'CA' .
This is the test class for the getTaskPriority() method. The test method simply
calls getTaskPriority() with one state ( 'NY' ).
@isTest
System.assertEquals('Normal', pri);
Copy
Let’s run this test class ( TaskUtilTest ) in the Developer Console and check code
coverage for the corresponding TaskUtil class that this test covers. After the test run
finishes, the code coverage for TaskUtil is shown as 75%. If you open this class in the
Developer Console, you see six blue (covered) lines and two red (uncovered) lines, as
shown in this image.
The reason why line 5 wasn’t covered is because our test class didn’t contain a test to
pass an invalid state parameter. Similarly, line 11 wasn’t covered because the test
method didn’t pass 'CA' as the state. Let’s add two more test methods to cover
those scenarios. The following shows the full test class after adding
the testTaskHighPriority() and testTaskPriorityInvalid() test methods. If you rerun
this test class, the code coverage for TaskUtil is now at 100%!
@isTest
System.assertEquals('Normal', pri);
System.assertEquals('High', pri);
System.assertEquals(null, pri);
Copy
A test suite is a collection of Apex test classes that you run together. For example,
create a suite of tests that you run every time you prepare for a deployment or
Salesforce releases a new version. Set up a test suite in the Developer Console to
define a set of test classes that you execute together regularly.
You now have two test classes in your org. These two classes aren’t related, but let’s
pretend for the moment that they are. Assume that there are situations when you
want to run these two test classes but don’t want to run all the tests in your org.
Create a test suite that contains both classes, and then execute the tests in the suite.
8. Click Run Suites.
9. On the Tests tab, monitor the status of your tests as they’re running. Expand
the test run, and expand again until you see the list of individual tests that
were run. Like in a run of individual test methods, you can double-click
method names to see detailed test results.
Salesforce records that are created in test methods aren’t committed to the
database. They’re rolled back when the test finishes execution. This rollback behavior
is handy for testing because you don’t have to clean up your test data after the test
executes.
By default, Apex tests don’t have access to pre-existing data in the org, except for
access to setup and metadata objects, such as the User or Profile objects. Set up test
data for your tests. Creating test data makes your tests more robust and prevents
failures that are caused by missing or changed data in the org. You can create test
data directly in your test method, or by using a utility test class as you’ll find out
later.
Note
Even though it is not a best practice to do so, there are times when a test method
needs access to pre-existing data. To access org data, annotate the test method
with @isTest(SeeAllData=true) . The test method examples in this unit don’t access org
data and therefore don’t use the SeeAllData parameter.