What is Apex?
Apex is a proprietary, strongly typed, object-oriented programming language designed specifically for the Salesforce platform, featuring a syntax similar to Java.
In Apex, a "Hello World" program would typically look like this:
public class HelloWorld {
public static void helloWorldMethod() {
System.debug('Hello, World!');
}
}
Features of Apex
- Integrated: Supports DML Operations (INSERT, UPDATE, DELETE), SOQL (SQL-like query language), and SOSL (used to perform text-based searches), Record-locking mechanism
- Easy to use: Apex is like Java and SQL. It's easy for developers from other platforms to learn and start using Salesforce quickly.
- Multitenant architecture: Apex operates in a multitenant environment, meaning it shares resources among multiple users, and the runtime engine is designed to prevent any code from using excessive resources and causing problems for others.
- Easy to test: Apex provides built-in support for unit test creation and execution
- Automatic Upgrades: Salesforce doesn't require manual upgrades, as it undergoes automatic upgrades with each season.
Apex Limitations
- Limited UI Interaction: Cannot display elements in the user interface; limited to error messages (.addError method).
- No Direct Changes to Standard SFDC Functionality: Unable to modify standard Salesforce functionality directly, but can influence execution or introduce new features.
- No Temporary File Creation: Incapable of generating temporary files.
- Single-Threaded Execution: Does not support the creation of multiple threads.
Environments in Salesforce
Developer Environment | For writing, modifying, and testing Apex code. |
Sandbox Environments | Mirrors of production for safe development and testing and sometimes used for load testing. |
Production Environment | Live environment for end-users. Cannot modify code here. |
Apex Variables
A variable is like a nickname for a storage spot in the computer's memory. It's a handy way to keep and
play around with information in a program.
Since Apex is a strongly-typed language, you must declare the data type of a variable when you
first refer to it.
Examples -
Integer x;
Boolean flag;
Double myNumber;
If you create any variable but forget to give it a value, it will be null.
Case sensitivity
Variables in apex are case-insensitive. Which means identifier "a" and "A" are both the same. For example -
Integer a = 10;
Integer A = 50; //Returns error - Duplicate variable: A, since A is already declared in the above statement
In Apex, the following elements are case-insensitive:
- Variable and Method Names:
- Object and Field Names:
- SOQL and SOSL Statements:
Example: Integer I;
and Integer i;
are considered the same.
Example: Account a1;
and ACCOUNT a2;
are considered the same.
Example: [sELect ID From ACCouNT where nAme = 'mAvERick'];
is acceptable with varying
case.
Example: [FinD 'mAvERick' In All fiELdS reTURnInG aPexClaSS(nAme)];
is acceptable with
varying case.
Data Types
In Apex, data types are categorized into two main groups- Primitive
- Non-primitive
Primitive Data Types
These are fundamental or basic data types built into the language.Examples -
Boolean: Represents true or false values. Integer: Holds whole numbers.
Double: Stores numbers with decimal places. String: Holds text.
Passing Behavior: Passed by Value. Changes in a function don't affect the original value.
Boolean isTrue = true;
Integer count = 42;
Double price = 99.99;
String greeting = 'Hello';
Non-Primitive Data Types
These are more complex data types and include objects and collections.Examples -
Object (Custom or Standard): Represents records or instances of objects.
List: Holds a collection of elements.
Set: Stores unique values.
Map: Stores key-value pairs.
Passing Behavior: Passed by Reference. Changes in a function directly affect the original data outside the function.
Account acc = new Account(Name='Sample');
List names = new List{'Alice', 'Bob', 'Charlie'};
Set numbers = new Set{1, 2, 3};
Map nameToAge = new Map{'Alice' => 25, 'Bob' => 30};
Primitive Data Types
Datatype | Stores | Examples |
---|---|---|
Blob | Binary data storage for large content.
You can convert this data type to String or from String using the toString and valueOf methods |
|
Boolean | Represents true or false values.
If uninitialized, it will hold null value |
|
Date | Stores only the date component. |
|
DateTime | Represents a specific point in time, including date and time. |
|
Decimal | Supports decimal numbers with high precision in Apex.
1.1 and 1.10 are considered different when using Decimal data type. Currency fields are automatically set as decimal type. |
|
Double | Represents double-precision floating-point numbers in Apex. |
|
ID |
Represents a unique identifier for records in Apex.
If you set ID to a 15-character value, Apex converts the value to its 18-character representation to eliminate case sensitivity of 15-character ID. ID's can be stored in String datatype. |
|
Integer |
Stores whole numbers.
It spans from -2,147,483,648 to 2,147,483,647. When 2,147,483,647 (highest number) is added +1, it returns -2,147,483,648 (lowest number). This is called overflow. |
|
Long | Supports larger integer values in Apex. |
|
Object |
A generic data type for any data structure.
An sObject in Salesforce represents a record in the database and is a fundamental object type that can be customized to store data for various entities. |
|
String | Represents a sequence of characters. |
|
Time |
Stores only the time component.
It takes the hour, minutes, seconds, milliseconds parameters respectively. |
|
Collections
Collections are variables that can hold multiple values. Collections in Apex can be lists, sets, or maps.
Lists
- Ordered collection of elements.
- Data can be duplicated
- Accessed by an index that starts from 0.
- List elements can be of any data type—primitive types, collections, sObjects, user-defined types, and built-in Apex types.
- A list can contain up to seven levels of nested collections inside it, that is, up to eight levels overall.
//basic syntax to define a new list
List<Integer> myList = new List<Integer>();
// Adding elements to the list
myList.add(50);
// Retrieving the element
Integer i = myList.get(0);
System.assertEquals(50, i);
// Initializing during declaration
List<String> fruits = new List<String>{'apple', 'banana', 'orange'};
// A list with two levels of nested lists
List<List<String>> nestedList = new List<List<String>>();
/* A very good example of a nested list of 2 levels is the return type of SOSL Query -- > list<list<sObject>> */
List and array are the interchangeable terms in Apex. The declarations below are all essentially the same.
Integer[] myList = new Integer[5];
Integer[] myList = new List();
List myList = new Integer[5];
//For lists declared with fixed size using square brackets [], the .add() method is used to add elements into the list that makes it elastic and the size becomes dynamic.
//But if you're using square brackets to add an element to a list, the list behaves like an array and isn't elastic and you won't be allowed to add more elements than the declared array size.
Commonly used list methods
//add - Adds an element to the end of the list.
myList.add(yourElement);
//clear - Removes all elements from a list, consequently setting the list's length to zero.
myList.clear();
//get - Returns the list element stored at the specified index.
Integer firstElementInMyList = myList.get(0);
//isEmpty - Returns true if the list has zero elements.
if(myList.isEmpty() == true)
{
//do something
}
//remove - Removes the list element stored at the specified index, returning the element that was removed.
String elementRemovedAtIndexTwo = myList.remove(2);
//size - Returns the number of elements in the list.
Integer listSize = myList.size();
//sort - Sorts the items in the list in ascending order.
myList.sort();
Sets
- Unordered collection of unique elements.
- Does not allow duplicate values.
- Cannot be accessed by an index since the order isn't guaranteed.
- A set can contain up to seven levels of nested collections inside it, that is, up to eight levels overall.
// Basic syntax to define a new set
Set<Integer> mySet = new Set<Integer>();
// Adding elements to the set
mySet.add(50);
// Initializing during declaration
Set<String> fruits = new Set<String>{'apple', 'banana', 'orange'};
// A set with two levels of nested sets
Set<Set<String>> nestedSet = new Set<Set<String>>();
Commonly used list methods
//add - Adds an element to the set if it is not already present.
mySet.add(yourElement);
//clear - Removes all of the elements from the set.
mySet.clear();
//isEmpty - Returns true if the list has zero elements.
if(mySet.isEmpty() == true)
{
//do something
}
//remove - Removes the specified element from the set if it is present.
Set<String> mySet = new Set<String>{'apple', 'banana', 'orange'};
String elementToRemove = 'banana';
Boolean removed = mySet.remove(elementToRemove); //returns true since 'banana' was removed
//size - Returns the number of elements in the set (its cardinality).
Integer setSize = mySet.size();
Maps
- Collection of key-value pairs.
- Each key must be unique.
- Each unique key is mapped to a single value.
- Enables fast data retrieval based on the key.
// Basic syntax to define a new map
Map<Integer, String> myMap = new Map<Integer, String>();
// Adding elements to the map
myMap.put(1, 'One');
myMap.put(2, 'Two');
myMap.put(3, 'Three');
// Initializing during declaration
Map<String, String> countryCapitalMap = new Map<String, String>{'USA' => 'Washington, D.C.', 'Canada' => 'Ottawa', 'France' => 'Paris', 'India' => 'New Delhi'};
// A map with two levels of nested sets
Map<String, Map<String, Integer>> nestedMap = new Map<String, Map<String, Integer>>();
Commonly used list methods
//clear - Removes all of the key-value mappings from the map.
myMap.clear();
//containsKey(key) - Returns true if the map contains a mapping for the specified key.
if(myMap.containsKey(key) == true)
{
//do something
}
//get(key) - Returns the value to which the specified key is mapped, or null if the map contains no value for this key.
Integer myValue = myMap.get(key);
//isEmpty() - Returns true if the map has zero key-value pairs.
if(myMap.isEmpty() == true)
{
//do something
}
//keySet() - Returns a set that contains all of the keys in the map.
Set<String> myKeySet = myMap.keySet();
//put(key, value) - Associates the specified value with the specified key in the map.
Map<String, String> myMap = new Map<String, String>();
myMap.put('EU', 'European Union');
//remove - Removes the mapping for the specified key from the map, if present, and returns the corresponding value.
Map<String, Integer> ageMap = new Map<String, Integer>{
'John' => 25,
'Alice' => 30,
'Bob' => 28
};
Integer removedAge = ageMap.remove('Alice'); //returns 30
//size - Returns the number of key-value pairs in the map.
Integer mapSize = myMap.size();
//values() - Returns a list that contains all the values in the map.
Map<String, Integer> ageMap = new Map<String, Integer>{
'John' => 25,
'Alice' => 30,
'Bob' => 28
};
List<Integer> allAges = new List<Integer>(ageMap.values());
Considerations when using Map
- Map keys can have a value of null.
- If you add a map entry with a key that already exists, the new entry will replace the existing one.
- String map keys are case-sensitive. For example, keys "earth" and "Earth" are distinct and can have separate entries.
- Salesforce recommends to access map elements using their keys since iteration order is deterministic.
Enums
An enum is a data type that represents a set of named values, often used to define options without a specific numerical order. To define an enum, use the enum keyword in your declaration and enclose the list of possible values within curly braces. For example,
Suits of cards
// Define an enum named "CardSuit"
enum CardSuit {
HEARTS,
DIAMONDS,
CLUBS,
SPADES
}
By defining the CardSuit enum, you've introduced a new data type named CardSuit. You can utilize this data type just like any other. For instance:
// Declare a variable named selectedCardSuit of type CardSuit and assign it the value HEARTS
CardSuit selectedCardSuit = CardSuit.HEARTS;
// Define a method named getOppositeHemisphereCardSuit that takes a CardSuit parameter
public CardSuit getOppositeHemisphereCardSuit(CardSuit currentCardSuit) {
if (currentCardSuit == CardSuit.CLUBS) {
return selectedCardSuit;
}
You can use an enum in any place you can use another data type name.
Constants
Constants in programming are like containers that hold values which stay the same throughout the entire
program. In Apex, constants are marked with the final
keyword.
public class MathOperations {
// Constant for the mathematical constant PI
public static final Double PI = 3.14159;
// Method to calculate the area of a circle
public Double calculateCircleArea(Double radius) {
return PI * radius * radius;
}
}
The final keyword in Apex indicates that a variable's value, once assigned, cannot be changed or reassigned.
Expressions and Operators
Expressions
An expression in Apex is a combination of variables, operators, and method invocations that evaluates to a single value.
Example:
// Define variables
Integer a = 5;
Integer b = 3;
// Expression with addition and multiplication
Integer result = a + (2 * b);
// Display the result
System.debug('Result: ' + result); // Output: Result: 11
Expression Operators
Expressions can be combined using operators to create compound expressions. For example:
Integer result = 5 + (2 * 3); // Compound expression with addition and multiplication
Expressions can be combined using operators to create compound expressions. For more details on all expression operators, check Expressions Operators.
Safe Navigation Operator
The safe navigation operator (?.) is used to handle null references. It short-circuits expressions when attempting to operate on a null value, returning null instead of throwing a NullPointerException. Example:
//Using shorthand operator
a?.b
//is the same as
a == null ? null : a.b
//is the same as
if (a != null) {
result = a.b;
} else {
result = null;
}
Operator Precedence
Operators are interpreted in a specific order, according to rules. For example, multiplication has a higher precedence than addition. Use parentheses to control the order of evaluation. Example:
Integer result = 5 + 2 * 3; // Multiplication is performed before addition
Here's a breakdown of operator precedence in Apex: Operator Precedence.
Comments
Both single-line and multiline comments are supported in Apex. They are used to annotate code for better understanding and documentation. Example:
// This is a single-line comment
/*
This is a multiline comment
spanning multiple lines.
*/
Control Flow Statements
Control flow statements in programming are constructs that allow the order in which statements are executed to be altered or controlled. Apex features if-else statements, switch statements, and loops to control the code flow
Conditional Statements
1. Simple If
A simple if statement is a conditional statement that executes a block of code only if a specified condition is true.
//Syntax
if (condition) {
// Code to be executed if the condition is true
}
//Example
Integer number = 10;
if (number > 5) {
System.debug('The number is greater than 5.');
}
In the above example since number 10 is greater than 5 satisfying the condition and returning true, it enters the loop and prints the statement inside.
2. If-Else Statement
A simple if-else statement is a conditional statement that executes one block of code if a specified condition is true and another block if the condition is false.
//Syntax
if (condition) {
// Code to be executed if the condition is true
} else {
// Code to be executed if the condition is false
}
//Example
Integer number = 3;
if (number > 5) {
System.debug('The number is greater than 5.');
} else {
System.debug('The number is not greater than 5.');
}
In the above example, since the number is 3 and not greater than 5, it satisfies the false condition, and the code inside the else block is executed, printing the corresponding statement.
3. Else-if Ladder
An else-if ladder is a series of if-else statements where each condition is checked sequentially. The first true condition's block of code is executed, and the rest are skipped.
//Syntax
if (condition1) {
// Code to be executed if condition1 is true
} else if (condition2) {
// Code to be executed if condition2 is true
} else if (condition3) {
// Code to be executed if condition3 is true
}
// Add more else if blocks as needed
//Example
Integer number = 7;
if (number < 5) {
System.debug('The number is less than 5.');
} else if (number < 10) {
System.debug('The number is between 5 and 10.');
} else {
System.debug('The number is greater than or equal to 10.');
}
In the above example, the else-if ladder checks multiple conditions sequentially. Since the number is 7, the second condition is true, and the corresponding code block is executed.
4. Switch When
The switch-when statement provides a concise way to compare a single expression against multiple values. It executes the code block associated with the first matching value.
//Syntax
switch on expression {
when value1 {
// Code block 1
}
when value2 {
// Code block 2
}
when value3 {
// Code block 3
}
when else {
// Default block (optional)
// Code block 4
}
}
//Example
String fruit = 'apple';
switch on fruit {
when 'apple' {
System.debug('Selected fruit is an apple.');
}
when 'banana' {
System.debug('Selected fruit is a banana.');
}
when 'orange' {
System.debug('Selected fruit is an orange.');
}
when else {
System.debug('Selected fruit is unknown.');
}
}
In the above example, the switch-when statement checks the value of the 'fruit' variable against different cases. The code block associated with the matching case is executed. If the selected fruit is not 'apple,' 'banana,' or 'orange,' the code block within "when else" is executed, providing a default behavior.
Loops
Loops are control structures that enable repetitive execution of a block of code. In Apex, there are three primary types of loops: the for loop, the while loop, and the do while loop.
For Loop
The for loop in Apex allows you to iterate over a set of values or perform a set of statements a specific number of times. There are three types of for loops in Apex:
- Traditional For Loop
- Enhanced For Loop (For-Each Loop)
- For Loop with Inline SOQL Query
Traditional For Loop
//Syntax
for (initialization; Boolean_exit_condition; increment) {
// Statement to be executed
}
//Example
// Type 1 Example
for (Integer i = 0; i < 5; i++) {
System.debug('Iteration ' + i);
}
This is the classic loop structure where you have an initialization step, a condition to check before each iteration, and an increment (or decrement) step.
Enhanced For Loop (For-Each Loop)
// Syntax
for (variable : collection) {
// Statement to be executed
}
// Example
List numbers = new List{1, 2, 3, 4, 5};
for (Integer num : numbers) {
System.debug('Number: ' + num);
}
This loop is used for iterating over collections like arrays or sets. It simplifies the loop structure by handling the initialization, condition, and increment automatically.
For Loop with Inline SOQL Query
// Syntax
for (variable : [inline_soql_query]) {
// Statement to be executed
}
// Example
List accounts = [SELECT Id, Name FROM Account LIMIT 5];
for (Account acc : accounts) {
System.debug('Account Name: ' + acc.Name);
}
In Apex, this type of loop is used to iterate over query results retrieved directly in the loop header using inline SOQL (Salesforce Object Query Language).
While Loop
// Syntax
while (Boolean_exit_condition) {
// Statement to be executed
}
// Example
Integer count = 0;
while (count < 5) {
System.debug('Count: ' + count);
count++;
}
The while loop executes a block of code as long as a specified condition is true .
Do-While Loop
// Syntax
do {
// Statement to be executed
} while (Boolean_exit_condition);
// Example
Integer number = 0;
do {
System.debug('Number: ' + number);
number++;
} while (number < 5);
The do-while loop is similar to the while loop, but it always executes the block of code at least once before checking the condition.
Break and Continue Statements
Break Statement
The break
statement is used to exit a loop prematurely if a specified condition is met.
// Syntax
while (Boolean_exit_condition) {
if (some_condition) {
break;
}
// Statement to be executed
}
// Example
Integer count = 0;
while (count < 10) {
System.debug('Count: ' + count);
if (count == 5) {
break;
}
count++;
}
The code uses a while loop with a condition (Boolean_exit_condition). The loop runs while the condition is true. Inside the loop, it checks if count is equal to 5 (if (count == 5)). If true, the loop is terminated prematurely with the break statement.
Continue Statement
The continue
statement is used to skip the rest of the code inside a loop for the current
iteration and move to the next iteration.
// Syntax
while (Boolean_exit_condition) {
if (some_condition) {
continue;
}
// Statement to be executed
}
// Example
for (Integer i = 0; i < 5; i++) {
if (i == 2) {
continue;
}
System.debug('Iteration ' + i);
}
This example demonstrates a for loop iterating from 0 to 4. Inside the loop, it checks if i is equal to 2 (if (i == 2)). If true, the loop skips the remaining code for that iteration using the continue statement, moving to the next iteration.
Class
A class in Apex is a blueprint for creating objects. It defines properties and behaviors that objects of that class will have.
Apex Class Definition
[private | public | global]
[virtual | abstract | with sharing | without sharing] class ClassName [implements InterfaceNameList] [extends ClassName]{
// The body of the class
}
In Apex, a class declaration includes optional access modifiers, sharing modifiers, and other keywords. It can implement one or more interfaces and extend another class. The body of the class contains the class members and methods.
Class Example
To define a class, you must use one of the access modifiers (public or global) and the keyword class
public class SampleClass {
// Class members and methods go here
}
Here, we have a public class named `SampleClass` that implements `SomeInterface`. The `public` keyword indicates that the class is accessible outside its own namespace.
Apex Class Variables
Class variables in Apex are properties defined within a class that store data values. They are accessible to all methods within the class and define the state of objects created from the class.
//Syntax
[public | private | protected | global] [final] [static] data_type variable_name [= value]
// Example
public class MyClass {
private Integer myNumber; // Private class variable
public String myString = 'What a nice day!'; // Public class variable
}
Apex Class Methods
Class methods in Apex are functions defined within a class that perform specific actions or return values. They define the behavior of objects instantiated from the class and can access class variables and other methods within the same class.
An Apex method can have a maximum of 32 parameters.
//Syntax
[public | private | protected | global] [override] [static] data_type method_name (input parameters) {
// The body of the method
}
// Examples
// Public method without parameters
public void displayMessage() {
System.debug('Hello, World!');
}
// Private method with parameters
private Integer multiplyNumbers(Integer num1, Integer num2) {
return num1 * num2;
}
// Static method with a return type
public static String greetUser(String userName) {
return 'Welcome, ' + userName + '!';
}
In the next section we will explore further terminology introduced in the examples.
Access Modifiers
Access modifiers in Apex control the visibility and accessibility of classes, methods, and variables. There are four main access modifiers in Apex:
Global
Public
Protected
Private
Global members are accessible from any Apex code in the Salesforce organization and also in cross-org platforms.
global class MyClass {
global String globalVariable;
global void globalMethod() {
// Code to be executed
}
}
Public members are accessible/visible throughout the org.
public class MyClass {
public String publicVariable;
public void publicMethod() {
// Code to be executed
}
}
Protected members are accessible within the same class and classes that extend other classes that contain the protected members. Protected methods can only be decalred within classes that are virtual.
public virtual class MyClass {
protected String protectedVariable;
protected void protectedMethod() {
// Code to be executed
}
}
// Subclass extending MyClass
public class MySubclass extends MyClass {
public void accessProtectedMember() {
// Accessing the protected variable from the superclass
System.debug('Protected Variable: ' + protectedVariable);
// Calling the protected method from the superclass
protectedMethod();
}
}
Private members are only visible to the same class.
public class MyClass {
private String privateVariable;
private void privateMethod() {
// Code to be executed
}
}
Constructor
A constructor in Apex is a special method with the same name as the class, used to initialize the class's properties when an object is created. Every class has a default constructor provided automatically.
You can also override the default constructor by using a custom constructor as below.
// Example
public class MathOperations {
private Integer operand1;
private Integer operand2;
// Class constructor
public MathOperations(Integer num1, Integer num2) {
operand1 = num1;
operand2 = num2;
}
// Method for addition
public Integer add() {
return operand1 + operand2;
}
// Method for subtraction
public Integer subtract() {
return operand1 - operand2;
}
}
The code illustrates an Apex class MathOperations
with a constructor, a method for addition,
and a method for subtraction. The constructor
(public MathOperations(Integer num1, Integer num2)
) initializes the class variables
operand1
and operand2
with the values passed as parameters when an object of the
class is created.
The constructor sets the initial state of the object, allowing subsequent use of the add
and
subtract
methods to perform operations on the initialized values.
- Constructors have no return type.
- A class can have multiple constructors with different parameter lists.
Static vs Instance Elements
Static Elements
In Apex, you can have static methods, variables, and initialization code. However, Apex classes can't be static.
// Example of Static Elements
public class StaticExample {
private static Integer staticVariable;
// Static method
public static void setStaticVariable(Integer value) {
staticVariable = value;
}
// Static initialization code
static {
// Initialization logic for static elements
}
// Other class methods...
}
The example includes a static method (setStaticVariable
), a static variable
(staticVariable
), and static initialization code. Static methods and variables belong to the
class itself, and static initialization code is executed once when the class is loaded.
Using Instance Methods and Variables
Instance methods and member variables are used by an instance of a class, that is, by an object. An instance member variable is declared inside a class, but not within a method. Instance methods usually use instance member variables to affect the behavior of the method.
Suppose that you want to have a class that collects two-dimensional points and plots them on a graph. The following skeleton class uses member variables to hold the list of points and an inner class to manage the two-dimensional list of points.
public class Plotter {
// This inner class manages the points
class Point {
Double x;
Double y;
Point(Double x, Double y) {
this.x = x;
this.y = y;
}
Double getXCoordinate() {
return x;
}
Double getYCoordinate() {
return y;
}
}
List<Point> points = new List<Point>();
public void plot(Double x, Double y) {
points.add(new Point(x, y));
}
// The following method takes the list of points and does something with them
public void render() {
// Implementation for rendering the points
}
}
In this example, the Plotter
class has an inner class Point
to manage
two-dimensional points.
The Point(Double x, Double y) constructor is used to initialize the x and y member variables of the Point
class when a new Point object is created.
The getXCoordinate() and getYCoordinate() methods in the Point class are getter methods designed to
retrieve the values of the x and y coordinates, respectively.
The plot
method is an instance method that adds a new point to the list, and the
render
method is another instance method that does something with the list of points.
Apex Properties
Apex properties are similar to variables but allow additional logic to be executed when a property is accessed or modified. They can be used to validate data, trigger actions on changes, or expose data from another source.
An example class, BasicProperty, is provided below to demonstrate the use of properties. In this case, the property is named prop, and it is both read and write.
public class BasicProperty {
public Integer prop {
get { return prop; }
set { prop = value; }
}
}
The provided code declares a property named prop with both get and set accessors. The get accessor is executed when the property is read, and the set accessor is executed when the property is assigned a new value.
The following code segment demonstrates the usage of 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
Explanation:
- The provided class, BasicProperty, declares a property named prop with both get and set accessors.
- The get accessor is invoked when the property is read, and the set accessor is invoked when a new value is assigned to the property.
- In the example usage, an instance of BasicProperty is created, and the get and set accessors are exercised by assigning and retrieving values from the prop property.
Automatic Properties
Properties can have empty get and set accessors for automatic properties, simplifying code maintenance. They can be read-only, read-write, or write-only.
public class AutomaticProperty {
public Integer MyReadOnlyProp { get; }
public Double MyReadWriteProp { get; set; }
public String MyWriteOnlyProp { set; }
}
The following code segment exercises these properties:
AutomaticProperty ap = new AutomaticProperty();
ap.MyReadOnlyProp = 5; // This produces a compile error: not writable
ap.MyReadWriteProp = 5; // No error
System.assertEquals(5, ap.MyWriteOnlyProp); // This produces a compile error: not readable
Using Static Properties
Static properties execute in a static context and can't access non-static members.
Example of a class with static and instance properties:
public class StaticProperty {
private static Integer StaticMember;
private Integer NonStaticMember;
// The following produces a system error
// public static Integer MyBadStaticProp { return NonStaticMember; }
public static Integer MyGoodStaticProp {
get { return StaticMember; }
set { StaticMember = value; }
}
public Integer MyGoodNonStaticProp {
get { return NonStaticMember; }
set { NonStaticMember = value; }
}
}
// Usage
StaticProperty sp = new StaticProperty();
// The following produces a system error
// sp.MyGoodStaticProp = 5;
StaticProperty.MyGoodStaticProp = 5; // Correct usage
Using Access Modifiers on Property Accessors
Accessors can have their own access modifiers, which must be more restrictive than the property.
Example:
global virtual class PropertyVisibility {
// X is private for read and public for write
public Integer X { private get; set; }
// Y can be globally read but only written within the class
global Integer Y { get; public set; }
// Z can be read within the class but only subclasses can set it
public Integer Z { get; protected set; }
}
Interface
An interface is like a blueprint for a class with empty methods. A class must fill in the methods of an interface. Interfaces help separate what a method should do from how it does it (provides abstraction by separating method declaration from implementation). Different classes can use the same interface but do things differently.
Example: Animals with Sounds
Step 1: Define an Interface
public interface Animal {
void makeSound();
}
Step 2: Implement the Interface for a Dog
public class Dog implements Animal {
public void makeSound() {
System.debug('Bark!');
}
}
Step 3: Implement the Interface for a Cat
public class Cat implements Animal {
public void makeSound() {
System.debug('Meow!');
}
}
Usage
Animal myDog = new Dog();
myDog.makeSound(); // Output: Bark!
Animal myCat = new Cat();
myCat.makeSound(); // Output: Meow!
Simple Points:
- Interface: A list of empty methods (just names, no details).
- Implement: A class must fill in the details for all the methods in an interface.
-
Example:
- Animal is an interface with one method makeSound().
- Dog and Cat are classes that say what makeSound() does for each animal.
- Usage: You can use the interface type to call the methods on any class that implements it.
Extending a Class
Extending a class inherits its methods and properties. Use the extends
keyword to extend a
class. Use the override
keyword to override virtual methods for polymorphism. A class can
only extend one class but can implement multiple interfaces.
Example 1: Basic Inheritance and Polymorphism
public class Animal {
public virtual void Speak() {
System.debug('Animal speaks.');
}
}
public class Dog extends Animal {
public override void Speak() {
System.debug('Dog barks.');
}
}
// Usage
Animal animal = new Animal();
animal.Speak(); // Output: Animal speaks.
Animal dog = new Dog();
dog.Speak(); // Output: Dog barks.
Example 2: Adding New Methods in Extended Class
public class Cat extends Animal {
public override void Speak() {
System.debug('Cat meows.');
}
public void Purr() {
System.debug('Cat purrs.');
}
}
// Usage
Cat cat = new Cat();
cat.Speak(); // Output: Cat meows.
cat.Purr(); // Output: Cat purrs.
Extending an Interface
Interfaces can extend other interfaces, and classes use the implements
keyword to inherit an
interface. This allows a class to provide concrete implementations for the methods defined by the
interface.
public interface Vehicle {
void Start();
}
public interface Car extends Vehicle {
void Drive();
}
// Implementation
public class Sedan implements Car {
public void Start() {
System.debug('Car starts.');
}
public void Drive() {
System.debug('Car drives.');
}
}
// Usage
Sedan myCar = new Sedan();
myCar.Start(); // Output: Car starts.
myCar.Drive(); // Output: Car drives.
Custom Iterators
An iterator is used to go through each item in a collection, like counting items in a list.
Example of a simple loop:
Integer count = 0;
while (count < 11) {
System.debug(count);
count++;
}
Using the Iterator interface, you can create custom instructions for going through a List.
Using Custom Iterators
To create a custom iterator, make a class that implements the Iterator interface. The interface has two methods:
hasNext()
: Returns true if there's another item in the collection.next()
: Returns the next item in the collection.
These methods must be declared as global or public.
Example of Custom Iterator
public class IterableString implements Iterator<String> {
private List<String> items;
private Integer index = 0;
public IterableString(String str) {
items = str.split(' ');
}
public Boolean hasNext() {
return index < items.size();
}
public String next() {
return items[index++];
}
}
Usage
IterableString x = new IterableString('This is a really cool test.');
while(x.hasNext()){
System.debug(x.next());
}
Custom iterators can only be used in while loops and are not supported in for loops.
Keywords
final Keyword
The final
keyword in Apex can be used with variables, methods, and classes. Here are the key points:
-
Final Variables:
- Final variables can only be assigned a value once, either when you declare the variable or inside a constructor.
- Static final variables can’t be changed in static initialization code or where defined.
- Member final variables can be changed in initialization code blocks, constructors, or with other variable declarations.
- To define a constant, mark a variable as both static and final.
-
Final Methods and Classes:
- Methods and classes are final by default.
Example
public class ConstantsExample {
public static final Double PI = 3.14159; //Constant
public final Integer MAX_USERS;
// Constructor to initialize the final variable
public ConstantsExample(Integer maxUsers) {
this.MAX_USERS = maxUsers;
}
// Method to demonstrate usage of final variables
public void displayValues() {
System.debug('PI: ' + PI);
System.debug('Max Users: ' + MAX_USERS);
}
}
// Usage
ConstantsExample example = new ConstantsExample(100);
example.displayValues();
instanceof Keyword
The instanceof
keyword checks if an object is an instance of a specific class or any subclass of that class. It confirms whether the object belongs to a particular type in the inheritance hierarchy.
Example
public class Animal {
public void makeSound() {
System.debug('Some generic animal sound');
}
}
public class Dog extends Animal {
public void makeSound() {
System.debug('Bark');
}
}
public class Cat extends Animal {
public void makeSound() {
System.debug('Meow');
}
}
// Usage
public class TestInstanceof {
public static void test() {
Animal myDog = new Dog();
Animal myCat = new Cat();
System.debug(myDog instanceof Dog); // true
System.debug(myCat instanceof Cat); // true
System.debug(myDog instanceof Cat); // false
}
}
super Keyword
The super
keyword in Apex is used to call methods and constructors from a parent class. It allows you to access and use the parent class's members (methods and constructors) that have been overridden or hidden by the subclass.
Example
In this example, we have a base class Animal
with a method makeSound
and a constructor. The subclass Dog
extends Animal
and uses the super
keyword to call the parent class's constructor and method.
// Declare a super class
public virtual class Animal {
public Animal() {
System.debug('Animal constructor');
}
public virtual void makeSound() {
System.debug('Some generic animal sound');
}
}
// Declare a sub class
public class Dog extends Animal {
public override void makeSound() {
super.makeSound(); // Calls the parent class method
System.debug('Bark'); // Calls the overridden method from sub class
}
}
// Usage
Dog d = new Dog();
d.makeSound();
In this example, the constructor of the Animal
class is called first. Then, the super
keyword is used to call the method from the Animal
class, followed by the execution of the overridden method in the Dog
class.
this Keyword
The this
keyword in Apex has two main uses. It refers to the current instance of the class in which it appears. This keyword helps in accessing instance variables and methods within the class. Let's understand this with an example.
Example of using this
keyword
Here is a simple class MyClass
that shows how to use the this
keyword:
public class MyClass {
String message;
// Constructor with a parameter
public MyClass(String message) {
this.message = message; // Use 'this' to set the instance variable
}
// No-argument constructor
public MyClass() {
this('Default Message through constructor chaining'); // Call the other constructor with 'this'
}
// Method to print the message
public void printMessage() {
System.debug(this.message); // Use 'this' to refer to the instance variable
}
}
In this class:
- The constructor
MyClass(String message)
usesthis.message = message
to set the instance variablemessage
with the value passed to the constructor. - The no-argument constructor
MyClass()
calls the other constructor usingthis('Default Message through constructor chaining')
, which is known as constructor chaining. - The
printMessage
method usesthis.message
to refer to the instance variablemessage
and prints its value.
Now, let's see a test class that creates objects of MyClass
and calls the methods:
public class MyClassTest {
public static void testMyClass() {
MyClass obj1 = new MyClass('This prints using dot notation');
obj1.printMessage();
MyClass obj2 = new MyClass();
obj2.printMessage();
}
}
In the MyClassTest
class:
obj1
is created with a custom message, andprintMessage
prints: "This prints using dot notation".obj2
is created with the default message through constructor chaining, andprintMessage
prints: "Default Message through constructor chaining".
Usage:
To run the test, call the static method:
MyClassTest.testMyClass();
transient keyword
Use the transient
keyword to declare instance variables in Apex that shouldn't be saved or transmitted as part of the view state for a Visualforce page. This helps reduce view state size. Typically, you use transient for fields needed only during a page request, which don't need to be saved and would be resource-intensive to recompute.
Transient Integer currentTotal;
Sharing Keywords
Sharing keywords in Apex determine the sharing rules that apply to the code. There are three main sharing keywords: with sharing, without sharing, and inherited sharing.
Keyword | Description | Example |
---|---|---|
with sharing | Enforces the sharing rules of the current user, ensuring that the code runs in the context of the current user's sharing settings. |
|
without sharing | Ensures that the sharing rules for the current user are not enforced, allowing the code to bypass the current user's sharing settings. |
|
inherited sharing | Enforces the sharing rules of the class that calls it, allowing the code to run in either with sharing or without sharing mode based on the caller's sharing settings. |
|
Using these keywords appropriately ensures that your Apex code respects the sharing rules and security model of your Salesforce organization.
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. An annotation is always defined before the method or class definition.
Apex supports the following annotations:
Annotation | Explanation |
---|---|
@AuraEnabled |
Enables an Apex method or property to be called from a Lightning Component. |
@Deprecated |
Marks an Apex method or class as deprecated, indicating it should no longer be used. |
@Future |
Runs an Apex method asynchronously in the future. |
@InvocableMethod |
Makes an Apex method available to be called from a process, flow, or external system. |
@InvocableVariable |
Makes an Apex variable available for input or output in a process or flow. |
@IsTest |
Defines an Apex class or method as a test, which does not count against the organization's code limits. |
@JsonAccess |
Allows JSON serialization and deserialization for an Apex class. |
@NamespaceAccessible |
Grants access to a class or method across namespaces. |
@ReadOnly |
Marks an Apex class or method to execute in read-only mode, preventing DML operations. |
@RemoteAction |
Allows an Apex method to be called from JavaScript code in a Visualforce page. |
@SuppressWarnings |
Suppresses compiler warnings for the annotated Apex method or class. |
@TestSetup |
Defines a method in a test class to create common test data that is rolled back after execution. |
@TestVisible |
Makes a private method or variable visible to test methods. |
Apex REST annotations are used to define RESTful web services in Apex. These annotations modify the way methods in an Apex class are exposed to and accessed by external applications through HTTP requests.
Apex supports the following REST annotations:
Annotation | Explanation |
---|---|
@ReadOnly |
The @ReadOnly annotation marks an Apex class or method to execute in read-only mode, preventing DML operations. |
@RestResource(urlMapping='/yourUrl') |
The @RestResource annotation is used at the class level and enables you to expose an Apex class as a REST resource. |
@HttpDelete |
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.
|
@HttpGet |
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.
|
@HttpPatch |
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.
|
@HttpPost |
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.
|
@HttpPut |
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.
|
sObject
An sObject variable represents a row of data and can only be declared in Apex using the SOAP API name of the object.
For example:
Account a = new Account();
MyCustomObject__c co = new MyCustomObject__c();
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; // System.TypeException: Invalid conversion from runtime type Account to Contact
Because sObjects work like objects, you can also have the following:
Object obj = s;
// and
a = (Account)obj;
Accessing SObject Fields
As in Java, SObject fields can be accessed or changed with simple dot notation. For example:
Account a = new Account();
a.Name = 'Acme';
Using SObject Fields
SObject fields can be initially set or not set (unset); unset fields are not the same as null or blank fields. When you perform a DML operation on an SObject, you can change a field that is set; you can’t change unset fields.
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.debug(nullFirst.isSet('FirstName'));
// The isSet method does not check if the field is non-null; it only checks if the field was explicitly set. Since FirstName was explicitly assigned a value (which happens to be null), isSet('FirstName') returns true
Contact unsetFirst = new Contact(LastName='Astro');
System.debug(unsetFirst.isSet('FirstName'));
//In this case, FirstName was not mentioned in the constructor, so it was not explicitly set to any value (not even null).
//Since the FirstName field was never explicitly set, unsetFirst.isSet('FirstName') returns false.
All about DMLs
Single vs. Bulk DML Operations
You can perform DML operations either on a single sObject, or in bulk on a list of sObjects. Performing bulk DML operations is recommended to avoid hitting governor limits, such as the DML limit of 150 statements per Apex transaction.
This example performs DML calls on single sObjects, which isn’t efficient:
Contact con = new Contact(LastName = 'Schwarzenegger');
insert con;
//OR
List<Contact> conList = [Select Department , Description from Contact];
for(Contact badCon : conList) {
if (badCon.Department == 'Finance') {
badCon.Description = 'New description';
// Not a good practice since governor limits might be hit if there are more than 150 contacts to insert.
update badCon;
}
}
This example below performs DML in bulk, avoiding the governor limit:
// 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);
}
}
// Call update on the list of contacts.
// This results in one DML call for the entire list.
update updatedList;
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.
DML Statements vs. Database Class Methods
Apex offers two ways to perform DML operations: using DML statements or Database class methods. This provides flexibility in how you perform data operations. DML statements are more straightforward to use and result in exceptions that you can handle in your code.
//DML Statement
Account acc = new Account(Name = 'New Account');
insert acc;
//Database Class Method
Account acc = new Account(Name = 'New Account');
Database.SaveResult sr = Database.insert(acc);
if (sr.isSuccess()) {
System.debug('Account inserted successfully: ' + sr.getId());
} else {
System.debug('Failed to insert account: ' + sr.getErrors()[0].getMessage());
}
The following helps you decide when to use DML statements or Database class methods:
- Use DML statements if you want any error during bulk DML processing to be thrown as an Apex exception that immediately interrupts control flow (by using try...catch blocks).
- Use Database class methods if you want to allow partial success of a bulk DML operation. When using this form, you can write code that never throws DML exception errors. Instead, use the appropriate results array to judge success or failure.
Database Class Method Result Objects
Operation | Result Class |
---|---|
insert, update | SaveResult Class |
upsert | UpsertResult Class |
merge | MergeResult Class |
delete | DeleteResult Class |
undelete | UndeleteResult Class |
convertLead | LeadConvertResult Class |
emptyRecycleBin | EmptyRecycleBinResult Class |
DML Operations
// Insert new records
Account acc = new Account(Name = 'New Account');
insert acc;
// Update existing records
Account acc = [SELECT Id FROM Account WHERE Name = 'New Account' LIMIT 1];
acc.Name = 'Updated Account';
update acc;
// Upsert records
Account acc = new Account(Name = 'Upserted Account');
upsert acc;
// Merge records
Account acc1 = [SELECT Id FROM Account WHERE Name = 'Account1' LIMIT 1];
Account acc2 = [SELECT Id FROM Account WHERE Name = 'Account2' LIMIT 1];
merge acc1 acc2;
// Delete records
Account acc = [SELECT Id FROM Account WHERE Name = 'Account to delete' LIMIT 1];
delete acc;
// Restore deleted records
Account acc = [SELECT Id FROM Account WHERE IsDeleted = TRUE LIMIT 1 ALL ROWS];
undelete acc;
// Convert leads
Lead myLead = [SELECT Id, LastName, Company FROM Lead WHERE Status = 'Open - Not Contacted' LIMIT 1];
Database.LeadConvert lc = new Database.LeadConvert();
lc.setLeadId(myLead.Id);
lc.setConvertedStatus('Closed - Converted');
Database.LeadConvertResult lcr = Database.convertLead(lc);
DML Options
Property | Description | Example |
---|---|---|
allowFieldTruncation | Specifies whether string values that are too long should be truncated (true) or cause an error (false). |
|
assignmentRuleHeader | Specifies assignment rules for cases or leads, either by ID or by using the default rule. |
|
duplicateRuleHeader | Determines whether a record identified as a duplicate can be saved. |
|
emailHeader | Specifies options for triggering auto-response emails and user emails. |
|
localeOptions | Specifies the language of any labels returned by Apex. |
|
optAllOrNone | Specifies whether the operation allows for partial success. If true, all changes are rolled back if any record causes errors. |
|
Mixed DML Error
A Mixed DML error occurs in Salesforce when you attempt to perform DML operations on both Setup objects (like User, Role, etc.) and non-Setup objects (like Account, Contact, etc.) in the same transaction. Salesforce throws this error to maintain data integrity and security, ensuring that system settings (Setup objects) and regular data (non-Setup objects) are not modified in a potentially unsafe manner within the same transaction.
This example demonstrates how a Mixed DML error occurs:
// Create a new User Role (Setup Object)
UserRole userRole = new UserRole();
userRole.Name = 'Test Role';
userRole.DeveloperName = 'Test_Role';
insert userRole;
// Create a new Account (Non-Setup Object)
Account acc = new Account();
acc.Name = 'Test Account';
// Attempt to insert the Account
insert acc; // This will cause a Mixed DML error
To work around the Mixed DML error, you can use asynchronous methods, such as using a future method to separate the transactions:
//Create a class
public class MixedDMLHandler {
@future
public static void insertAccountAsync(String accountName) {
Account acc = new Account(Name = accountName);
insert acc;
}
}
// Through anon apex create a new User Role (Setup Object)
UserRole userRole = new UserRole();
userRole.Name = 'Test Role';
userRole.DeveloperName = 'Test_Role';
insert userRole;
// Call the future method to insert the Account asynchronously
MixedDMLHandler.insertAccountAsync('Test Account');
Transaction Control
In Apex, Transaction Control is essential for managing database operations. It ensures that your operations are either fully completed or fully undone, maintaining the integrity of the database. Two key concepts in transaction control are Savepoint
and Database.rollback()
.
Savepoint
A Savepoint is a marker within a transaction. You can think of it as a checkpoint. If something goes wrong later in the transaction, you can roll back to this savepoint instead of undoing the entire transaction.
Database.rollback()
The Database.rollback(savepoint)
method is used to undo all changes made after a specific savepoint. This is useful when an error occurs and you need to revert the database to a previous state without affecting earlier successful operations.
// Define the CustomException class
public class CustomException extends Exception {
}
// Start of the transaction
Savepoint sp1 = Database.setSavepoint();
Savepoint sp2; // Declare sp2 outside the try block
try {
// Insert an account
Account acc = new Account(Name = 'New Account');
insert acc;
// Set another savepoint after the account is inserted
sp2 = Database.setSavepoint();
// Insert a contact related to the account
Contact con = new Contact(FirstName = 'John', LastName = 'Doe', AccountId = acc.Id);
insert con;
// Let's simulate an error
if(con.LastName == 'Doe') {
throw new CustomException('Simulated Error');
}
} catch(CustomException e) {
// Rollback to savepoint sp2 - this will undo the contact insertion but not the account
if (sp2 != null) {
Database.rollback(sp2);
}
System.debug('Transaction rolled back to savepoint sp2: ' + e.getMessage());
} catch(Exception e) {
// Rollback to savepoint sp1 - this will undo both account and contact insertion
Database.rollback(sp1);
System.debug('Transaction rolled back to savepoint sp1: ' + e.getMessage());
}
Explanation
- Savepoint sp1: Created before any records are inserted, allowing a rollback of the entire transaction.
- Insert Account: An account is inserted into the database.
- Savepoint sp2: Created after the account is inserted. This savepoint allows a rollback that preserves the account insertion while undoing any subsequent changes.
- Insert Contact: A contact, related to the account, is inserted into the database.
-
CustomException Handling: If the simulated error occurs (triggered by the contact’s last name):
- The code rolls back to sp2, undoing the contact insertion but leaving the account intact.
-
General Exception Handling: If any other type of exception occurs (not a CustomException):
- The code rolls back to sp1, undoing both the account and contact insertions.
Transaction Control mechanism is used for the below reasons
- Control: You have precise control over which parts of your transaction can be undone.
- Integrity: Ensures the database remains in a consistent state even if something goes wrong.
Locking of Records and Deadlocks
Locking of Records
In Apex, you can use the FOR UPDATE
clause in SOQL queries to lock sObject records while they are being updated. This prevents race conditions and other thread safety issues. 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 lock is released when the transaction completes. To lock a set of sObject records in Apex, include the keywords FOR UPDATE
after any inline SOQL statement. For example, the following statement queries and locks two accounts:
Account[] accts = [SELECT Id FROM Account LIMIT 2 FOR UPDATE];
Locking Considerations
- While the records are locked, the locking client can modify their field values within the same transaction. Other clients have to wait until the transaction completes before they can update the same records. However, other clients can still query the locked records.
-
If you attempt to lock a record currently locked by another client, your process waits up to 10 seconds for the lock to be released. If the wait time exceeds 10 seconds, a
QueryException
is thrown. -
Similarly, if you try to update a record currently locked by another client and the lock isn’t released within 10 seconds, a
DmlException
is thrown (UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record or 1 records). -
Record locks obtained via the
FOR UPDATE
clause are automatically released when making callouts, which is logged in the debug log. - When performing a DML operation on one record, related records may also be locked.
Locking in a SOQL For Loop
The FOR UPDATE
keywords can also be used within SOQL for loops. For example:
for (Account[] accts : [SELECT Id FROM Account FOR UPDATE]) {
// Your code
}
This ensures that records are locked as they are processed in the loop, preventing other operations from modifying them during the loop's execution.
Avoiding Deadlocks
Deadlocks can occur when multiple database tables or rows are locked in a conflicting order by different processes. To avoid deadlocks, Apex runtime engine follows certain practices:
- It locks sObject parent records before locking child records.
- It locks sObject records in the order of their IDs when multiple records of the same type are being edited.
As a developer, you should ensure that your code follows standard deadlock avoidance techniques by accessing tables and rows in the same order across all parts of your application.
SOQL
SOQL Statements
SOQL (Salesforce Object Query Language) 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 aa = [SELECT Id, Name FROM Account WHERE Name = 'Acme'];
From this list, you can access individual elements:
if (!aa.isEmpty()) {
// Execute commands
}
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 before the record is inserted into the database.
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'];
You can also operate on the results using standard arithmetic:
Integer j = 5 * [SELECT COUNT() FROM Account];
SOQL For Loops
SOQL for loops iterate over all of the sObject records returned by a SOQL query.
The syntax of a SOQL for loop is either:
for (variable : [soql_query]) {
code_block
}
or
for (variable_list : [soql_query]) {
code_block
}
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
}
Anonymous Apex
Anonymous Apex is a way to run short snippets of Apex code on the fly without saving the code in your Salesforce org. It's useful for testing, debugging, or running quick tasks.
Key Features
- Temporary Execution: The code doesn't get saved to your org's metadata. Once executed, it's gone.
- Immediate Testing: Great for testing code snippets, SOQL queries, or DML operations quickly.
- Execution Context: Runs in the context of the current user, respecting their permissions.
How to Execute Anonymous Apex
You can run Anonymous Apex using:
- Developer Console: In Salesforce, navigate to Setup → Developer Console, then go to Debug → Open Execute Anonymous Window.
- Visual Studio Code: Use the Salesforce Extensions for VS Code and run commands from the command palette.
- API Calls: Use the
executeAnonymous()
SOAP API call in your integrations.
Example: Simple Anonymous Apex
Here's how you might write and execute a simple Anonymous Apex block:
System.debug('Hello, Anonymous Apex!');
When you run this code, it will output "Hello, Anonymous Apex!" to the debug log.
Example: Creating a New Record
You can perform DML operations like creating records:
// Create a new Account
Account acc = new Account();
acc.Name = 'New Anonymous Account';
insert acc;
System.debug('New Account ID: ' + acc.Id);
This code creates a new Account record and outputs its ID to the debug log.
Important Considerations
- Permissions: Anonymous Apex runs with the current user's permissions. If the user doesn't have access to certain objects or fields, the code may fail.
- No Need to Commit: You don't need to explicitly commit changes; DML operations are automatically committed if the code executes successfully.
- Error Handling: If an error occurs, all changes are rolled back.
- Temporary Scope: Classes or variables defined in Anonymous Apex are not saved and can't be accessed later.
Example: Query and Update Records
You can also query and modify existing records:
// Query Contacts with last name 'Smith'
List contacts = [SELECT Id, LastName FROM Contact WHERE LastName = 'Smith'];
// Update each Contact's description
for (Contact con : contacts) {
con.Description = 'Updated via Anonymous Apex';
}
update contacts;
System.debug(contacts.size() + ' contacts updated.');
This code finds all Contacts with the last name 'Smith', updates their Description field, and saves the changes.
Limitations
- No Static Methods: You can't define static methods or variables in Anonymous Apex.
- Transient Code: Any classes or methods you define are temporary and vanish after execution.
- Governor Limits Apply: All standard Salesforce governor limits are enforced.
Why Use Anonymous Apex?
Anonymous Apex is ideal for:
- Testing and Debugging: Quickly test snippets of code without deploying them.
- Data Manipulation: Perform one-time data fixes or updates.
- Learning: Experiment with Apex code to better understand how it works.
Triggers
Triggers in Apex are pieces of code that execute before or after specific database events occur on Salesforce records. They allow you to perform custom actions such as updating related records, enforcing business rules, or preventing operations.
What are Triggers?
- Database Event Handlers: Triggers run automatically in response to database events like insert, update, delete, merge, upsert, and undelete.
-
Before and After Triggers:
- Before Triggers: Execute before the record is saved to the database. Used to validate or modify values before they are saved.
- After Triggers: Execute after the record has been saved. Used to access system-set values like record IDs or to make changes to other records.
When to Use Triggers?
Use triggers to perform operations that can't be done with standard workflows or process builders, such as:
- Updating related records that are not directly accessible.
- Preventing certain operations based on complex conditions.
- Enforcing custom validation beyond what's possible with validation rules.
Trigger Syntax
The basic syntax for a trigger is:
trigger TriggerName on ObjectName (trigger_events) {
// Your code here
}
Where trigger_events
can be one or more of the following:
before insert
before update
before delete
after insert
after update
after delete
after undelete
Example: Before Insert Trigger
This trigger sets a default value for a custom field before a new Account record is inserted:
trigger AccountBeforeInsert on Account (before insert) {
for (Account acc : Trigger.new) {
// Set a default value for a custom field
acc.Custom_Field__c = 'Default Value';
}
}
Trigger Context Variables
Apex provides implicit variables that allow you to access context information within your triggers:
Trigger.new
: A list of the new versions of the records. Available in before and after insert, before and after update, and after undelete triggers.Trigger.old
: A list of the old versions of the records. Available in update and delete triggers.Trigger.newMap
: A map of IDs to the new versions of the records.Trigger.oldMap
: A map of IDs to the old versions of the records.Trigger.isInsert
,Trigger.isUpdate
, etc.: Boolean variables indicating the type of trigger.
Example: After Update Trigger
This trigger updates a related record when an Opportunity is closed:
trigger OpportunityAfterUpdate on Opportunity (after update) {
List accountsToUpdate = new List();
for (Opportunity opp : Trigger.new) {
Opportunity oldOpp = Trigger.oldMap.get(opp.Id);
if (opp.StageName == 'Closed Won' && oldOpp.StageName != 'Closed Won') {
Account acc = new Account(Id = opp.AccountId);
acc.Custom_Status__c = 'Customer';
accountsToUpdate.add(acc);
}
}
if (!accountsToUpdate.isEmpty()) {
update accountsToUpdate;
}
}
Best Practices
- Bulkify Your Code: Always assume that your trigger is handling multiple records and write your code accordingly.
- Avoid SOQL Queries and DML Statements Inside Loops: Move queries and DML operations outside of loops to avoid governor limits.
- Use Collections: Use lists, sets, and maps to efficiently process records.
- One Trigger Per Object: Implement all logic for an object in a single trigger and delegate operations to handler classes.
Trigger Handler Pattern
It's a good practice to use a handler class to keep your trigger code clean and maintainable:
// Trigger
trigger AccountTrigger on Account (before insert, before update) {
AccountTriggerHandler.handleTrigger(Trigger.new, Trigger.oldMap);
}
// Handler Class
public class AccountTriggerHandler {
public static void handleTrigger(List newList, Map oldMap) {
for (Account acc : newList) {
// Your logic here
}
}
}
Limitations and Considerations
- Trigger Order: If multiple triggers are defined on the same object and event, the order of execution is not guaranteed.
- Recursive Triggers: Be cautious of triggers that update records and cause themselves to execute again. Use static variables to prevent infinite loops.
- Governor Limits: Triggers are subject to Salesforce governor limits. Write efficient code to avoid hitting these limits.
Preventing Operations with addError()
You can use the addError()
method to prevent DML operations from occurring:
trigger AccountBeforeDelete on Account (before delete) {
for (Account acc : Trigger.old) {
if (acc.Name == 'Protected Account') {
acc.addError('You cannot delete this account.');
}
}
}
This code prevents deletion of any Account named 'Protected Account' and shows an error message to the user.
Testing Triggers
Ensure you write test classes to cover your trigger logic:
- Create test records that invoke your trigger.
- Use
System.assert()
methods to verify the expected outcomes. - Aim for at least 75% code coverage.
Asynchronous Apex
Asynchronous Apex allows you to run processes in the background, independently of the user's interaction. This is useful for operations that take a long time to process, need to be scheduled, or require handling large data volumes.
Types of Asynchronous Apex
Salesforce provides several ways to run Apex code asynchronously:
- Future Methods
- Queueable Apex
- Scheduled Apex
- Batch Apex
Let's explore each of these in detail.
Future Methods
Future methods are a way to run code asynchronously in Salesforce. They are ideal for short operations and for making callouts to external web services without blocking the main thread.
When to Use Future Methods
- To execute long-running operations without blocking the user.
- To make callouts to external web services from triggers or other contexts where callouts are not allowed.
- To avoid the "Mixed DML" error when performing DML operations on setup and non-setup objects in the same context.
Key Features
- Annotated with
@future
. - Must be static methods.
- Can only accept primitive data types as parameters (or collections of primitives), not sObjects.
Example: Future Method with Callout
This example demonstrates how to define and call a future method that performs a callout:
@future(callout=true)
public static void callExternalService(String accountId) {
// Make a callout to an external service
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://api.example.com/data');
request.setMethod('GET');
HttpResponse response = http.send(request);
// Process the response
if (response.getStatusCode() == 200) {
// Update the account with data from the response
Account acc = [SELECT Id, Name FROM Account WHERE Id = :accountId];
acc.Description = response.getBody();
update acc;
}
}
To call this future method:
// Call the future method
MyClass.callExternalService('001XXXXXXXXXXXXAAA');
Limitations
- Cannot pass sObjects or complex types as parameters(On the flip side we can make use of JSON to pass complex variables if needed).
- No guarantee of execution order.
- Limited to a maximum of 50 future method invocations per Apex invocation.
Queueable Apex
Queueable Apex is similar to future methods but provides more features, such as the ability to pass complex types and chain jobs.
When to Use Queueable Apex
- When you need to pass sObjects or complex data types to the job.
- When you need to chain jobs to run in sequence.
- When you need an ID to track the job's progress.
Key Features
- Implements the
Queueable
interface. - Allows passing complex types like sObjects.
- Can chain jobs for sequential processing.
- Provides a job ID for tracking.
Example: Queueable Class
This example demonstrates a queueable class that processes a list of accounts:
public class AccountProcessor implements Queueable {
private List accountsToProcess;
public AccountProcessor(List accounts) {
this.accountsToProcess = accounts;
}
public void execute(QueueableContext context) {
// Perform processing on the accounts
for (Account acc : accountsToProcess) {
acc.Description = 'Processed by Queueable Apex';
}
update accountsToProcess;
}
}
To enqueue this job:
// Prepare a list of accounts
List accList = [SELECT Id, Name FROM Account WHERE ...];
// Enqueue the job
System.enqueueJob(new AccountProcessor(accList));
Chaining Queueable Jobs
You can chain queueable jobs by enqueuing a new job from within the execute
method:
public void execute(QueueableContext context) {
// Process current batch
// ...
// Chain the next job
System.enqueueJob(new NextJob());
}
Limitations
- A maximum of 50 jobs can be added to the queue with
System.enqueueJob
in a single transaction. - Only one child job can be chained from the
execute
method.
Scheduled Apex
Scheduled Apex allows you to schedule Apex classes to run at specific times. This is useful for daily or weekly maintenance tasks.
When to Use Scheduled Apex
- To schedule a class to run at a specific time.
- For recurring tasks like nightly updates or weekly reports.
Key Features
- Implements the
Schedulable
interface. - Can be scheduled via the Salesforce UI or using code.
Example: Scheduled Class
This example demonstrates how to define a scheduled Apex class:
public class DailyJob implements Schedulable {
public void execute(SchedulableContext context) {
// Your scheduled logic here
System.debug('Scheduled Apex Job is running.');
}
}
Scheduling the Job
You can schedule the job via the UI or programmatically:
Scheduling via UI
- Go to Setup → Apex Classes.
- Click "Schedule Apex".
- Enter the job name, select the class, and set the schedule.
Scheduling via Code
// CRON expression: Seconds Minutes Hours Day_of_month Month Day_of_week optional_year
String cronExp = '0 0 22 * * ?'; // Every day at 10 PM
String jobName = 'Daily Job';
System.schedule(jobName, cronExp, new DailyJob());
Limitations
- A maximum of 100 scheduled Apex jobs can be active at one time.
- Cron expressions must be valid and follow the supported syntax.
Batch Apex
Batch Apex is designed to handle large volumes of data by breaking the data into manageable chunks (batches) and processing them asynchronously.
When to Use Batch Apex
- When dealing with large data volumes that exceed normal processing limits.
- For operations that require processing records in batches, like data cleansing or archiving.
- When you need to query more than 50,000 records.
Key Features
- Implements the
Database.Batchable
interface. - Processes records in batches of a specified size (default is 200).
- Can have an optional
finish
method for post-processing.
Example: Batch Class
This example demonstrates a batch class that updates all accounts:
public class AccountBatchProcessor implements Database.Batchable<sObject> {
public Database.QueryLocator start(Database.BatchableContext context) {
// Query all accounts
return Database.getQueryLocator('SELECT Id, Name FROM Account');
}
public void execute(Database.BatchableContext context, List<sObject> scope) {
List<Account> accountsToUpdate = (List<Account>) scope;
for (Account acc : accountsToUpdate) {
acc.Description = 'Updated by Batch Apex';
}
update accountsToUpdate;
}
public void finish(Database.BatchableContext context) {
// Optional post-processing
System.debug('Batch Apex Job Finished.');
}
}
Executing the Batch Job
To execute the batch job:
// Instantiate and execute the batch job
AccountBatchProcessor batch = new AccountBatchProcessor();
Database.executeBatch(batch, 100); // Batch size of 100
OR
Database.executeBatch(new AccountBatchProcessor(), 100);
Limitations
- A maximum of 5 batch jobs can be queued or active at the same time.
- Total number of records processed by batch Apex in a 24-hour period is limited.
Summary
Asynchronous Apex allows you to handle complex, long-running operations efficiently. Choose the appropriate feature based on your specific needs:
- Future Methods: Simple asynchronous processing without complex data types.
- Queueable Apex: Advanced asynchronous processing with complex data types and job chaining.
- Scheduled Apex: Schedule Apex code to run at specific times.
- Batch Apex: Process large volumes of data in manageable chunks.
SOAP/REST Web Services
Apex allows you to expose your classes and methods as web services, enabling external applications to interact with your Salesforce data and logic. There are two main ways to expose Apex code as web services:
- SOAP Web Services: Use the
webservice
keyword to expose Apex methods as SOAP web services. - REST Web Services: Use annotations like
@RestResource
and@HttpGet
,@HttpPost
, etc., to expose Apex classes and methods as RESTful web services.
Exposing Apex Methods as SOAP Web Services
You can expose your Apex methods as SOAP web services by using the webservice
keyword. This allows external applications to call your methods using the SOAP protocol.
Creating a SOAP Web Service
To create a SOAP web service:
- Define an Apex class with the
global
access modifier. - Define methods within the class using the
webservice
keyword. - Methods must be
static
.
Example: SOAP Web Service Method
Here is a simple example of an Apex class exposing a method as a SOAP web service:
global class MyWebServiceClass {
webservice static Id createContact(String lastName, Id accountId) {
Contact c = new Contact(LastName = lastName, AccountId = accountId);
insert c;
return c.Id;
}
}
This class exposes the createContact
method as a SOAP web service that can be called by external applications to create a new Contact record.
Generating the WSDL
To allow external applications to use your SOAP web service, they need the WSDL file:
- Go to Setup → Apex Classes.
- Click on the name of your class that contains the
webservice
methods. - Click on "Generate WSDL".
- Provide the WSDL file to external developers.
Considerations
- The class must be declared as
global
. - Methods must be
webservice
,static
, and return allowed types. - Not all data types are supported. You cannot use certain types like
Set
,Map
, orPattern
. - SOAP web services run in system context, so they do not respect user permissions or field-level security by default.
Example: SOAP Web Service with Custom Types
You can also expose custom data types:
global class AccountWebService {
global class AccountInfo {
webservice String name;
webservice String phone;
}
webservice static Id createAccount(AccountInfo info) {
Account acc = new Account();
acc.Name = info.name;
acc.Phone = info.phone;
insert acc;
return acc.Id;
}
}
In this example, AccountInfo
is a custom class used as a parameter for the web service method.
Exposing Apex Classes as REST Web Services
You can expose your Apex classes and methods as RESTful web services by using annotations. This allows external applications to interact with your Salesforce data using standard HTTP methods.
Creating a REST Web Service
To create a REST web service:
- Annotate your Apex class with
@RestResource
and define a URL mapping. - Annotate methods with HTTP method annotations like
@HttpGet
,@HttpPost
, etc. - Methods can accept parameters and return data in JSON or XML format.
Example: REST Web Service Class
Here's an example of an Apex class exposed as a REST web service:
@RestResource(urlMapping='/Account/*')
global with sharing class AccountService {
@HttpGet
global static Account getAccount() {
RestRequest req = RestContext.request;
String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
Account acc = [SELECT Id, Name, Phone FROM Account WHERE Id = :accountId];
return acc;
}
@HttpPost
global static String createAccount(String name, String phone) {
Account acc = new Account(Name = name, Phone = phone);
insert acc;
return acc.Id;
}
}
In this example, the class AccountService
is exposed at the URL pattern /services/apexrest/Account/*
. It includes methods for retrieving and creating accounts.
Calling the REST Web Service
You can call the REST web service using standard HTTP methods:
GET Request Example
Retrieve an account:
curl -H "Authorization: Bearer access_token" \
"https://yourInstance.salesforce.com/services/apexrest/Account/accountId"
POST Request Example
Create a new account:
curl -H "Authorization: Bearer access_token" \
-H "Content-Type: application/json" \
-d '{"name":"New Account", "phone":"123-456-7890"}' \
"https://yourInstance.salesforce.com/services/apexrest/Account/"
Considerations
- The class must be declared as
global
and annotated with@RestResource
. - Methods must be annotated with HTTP method annotations.
- REST web services run in system context, so they do not respect user permissions or field-level security by default.
- You can use standard Apex data types, sObjects, and user-defined types as parameters and return types.
Example: REST Web Service with Parameters
Here's an example that demonstrates using parameters and handling request data:
@RestResource(urlMapping='/CaseManagement/*')
global with sharing class CaseService {
@HttpPost
global static String attachFile() {
RestRequest req = RestContext.request;
String caseId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
Blob body = req.requestBody;
Attachment att = new Attachment();
att.ParentId = caseId;
att.Body = body;
att.Name = 'AttachmentFromREST';
att.ContentType = 'application/octet-stream';
insert att;
return att.Id;
}
}
In this example, the attachFile
method reads binary data from the request and attaches it to a Case record.
Handling Request and Response Data
You can access the request and response objects via RestContext.request
and RestContext.response
. This allows you to read headers, body content, query parameters, and set response status codes.
Error Handling
You can throw exceptions or set the response status code to handle errors:
@RestResource(urlMapping='/Account/*')
global with sharing class AccountService {
@HttpGet
global static Account getAccount() {
RestRequest req = RestContext.request;
String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
try {
Account acc = [SELECT Id, Name, Phone FROM Account WHERE Id = :accountId];
return acc;
} catch (Exception e) {
RestContext.response.statusCode = 404;
RestContext.response.responseBody = Blob.valueOf('Account not found');
return null;
}
}
}
Authentication
External applications must authenticate with Salesforce to call your web services. They can use OAuth 2.0 or session IDs obtained via login.
Security Considerations
- By default, web services run in system context and do not enforce object permissions, field-level security, or sharing rules.
- To enforce sharing rules, declare your class as
with sharing
. - Use field and object describe methods or the
Security.stripInaccessible
method to enforce field-level and object-level security.
Summary
Apex provides powerful ways to expose your code as SOAP or REST web services, enabling integration with external systems. Choose the method that best suits your needs:
- SOAP Web Services: For standardized, contract-based integrations using WSDL.
- REST Web Services: For lightweight, stateless interactions using HTTP and JSON/XML.
Apex Email Service
Apex Email Service allows you to process incoming emails within Salesforce. You can write Apex classes to handle the content, headers, and attachments of inbound emails. This is useful for automating tasks like creating records based on email content, logging emails, or updating records.
What is Apex Email Service?
It's a feature that enables you to define email addresses that Salesforce will monitor. When an email is sent to one of these addresses, your custom Apex code processes the email.
Key Components
- Email Service: Defines how Salesforce processes incoming emails.
- Email Address: A generated email address associated with the email service.
- Apex Class: Implements the
Messaging.InboundEmailHandler
interface to process the email.
Creating an Apex Email Service
Here are the steps to set up an Apex Email Service:
- Create an Apex class that implements the
Messaging.InboundEmailHandler
interface. - Define an email service and associate it with the Apex class.
- Generate an email address for the service.
Example: Processing Inbound Emails
This example demonstrates how to create a task when an email is received from a known contact:
public class EmailToTaskHandler implements Messaging.InboundEmailHandler {
public Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email,
Messaging.InboundEnvelope envelope) {
Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
// Find the contact based on the sender's email address
Contact sender = [SELECT Id FROM Contact WHERE Email = :email.fromAddress LIMIT 1];
if (sender != null) {
// Create a new task
Task newTask = new Task();
newTask.Subject = email.subject;
newTask.Description = email.plainTextBody;
newTask.OwnerId = sender.OwnerId;
newTask.WhatId = sender.Id;
insert newTask;
}
result.success = true;
return result;
}
}
This class implements the handleInboundEmail
method, which processes incoming emails.
Setting Up the Email Service
To set up the email service:
- Go to Setup → Email → Email Services.
- Click "New Email Service".
-
Fill in the details:
- Function: The Apex class you created.
- Accept Email From: Specify allowed sender domains or addresses.
- Active: Ensure the service is active.
- Click "Save and New Email Address" to generate a new email address for the service.
Example: Unsubscribe Handler
This example demonstrates how to process unsubscribe requests via email:
public class UnsubscribeEmailHandler implements Messaging.InboundEmailHandler {
public Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email,
Messaging.InboundEnvelope envelope) {
Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
if (email.subject != null && email.subject.toLowerCase().contains('unsubscribe')) {
// Unsubscribe the sender
List contacts = [SELECT Id, HasOptedOutOfEmail FROM Contact
WHERE Email = :email.fromAddress];
for (Contact c : contacts) {
c.HasOptedOutOfEmail = true;
}
if (!contacts.isEmpty()) {
update contacts;
}
// Optionally, process Leads as well
List leads = [SELECT Id, HasOptedOutOfEmail FROM Lead
WHERE Email = :email.fromAddress];
for (Lead l : leads) {
l.HasOptedOutOfEmail = true;
}
if (!leads.isEmpty()) {
update leads;
}
}
result.success = true;
return result;
}
}
This handler checks if the subject contains 'unsubscribe' and updates the relevant records.
Testing Your Email Service
To test your email service:
- Send an email to the generated email address from an allowed sender.
- Check Salesforce to see if the expected actions occurred (e.g., a task was created).
Best Practices
- Security: Restrict the email addresses or domains that can send emails to your service.
- Error Handling: Use the
result.success
flag and setresult.message
if needed. - Limits: Be aware of governor limits and handle exceptions gracefully.
Limitations
- A maximum email size of 25 MB (varies depending on language and character set), including attachments.
- Daily email processing limits depend on your organization's user licenses.
Governor Limits
Governor Limits are Salesforce's way of ensuring that no single Apex execution impacts the overall performance of shared resources. Because Apex runs in a multitenant environment, the Apex runtime engine strictly enforces limits to prevent code from consuming excessive resources.
What are Governor Limits?
Governor Limits are runtime limits enforced by the Apex runtime engine to ensure efficient execution of code and maintain the health of the Salesforce platform.
Types of Governor Limits
The limits are broadly categorized into:
- Per-Transaction Apex Limits: Limits that apply per Apex transaction.
- Lightning Platform Apex Limits: Limits that apply to the platform overall.
- Static Apex Limits: Limits that are fixed and do not change per transaction.
- Size-Specific Apex Limits: Limits that are based on the size of data or code.
- Miscellaneous Apex Limits: Other limits that don't fall into the above categories.
Per-Transaction Apex Limits
These limits count for each Apex transaction. For Batch Apex, these limits are reset for each execution of a batch of records in the execute
method.
Limit Description | Synchronous Limit | Asynchronous Limit |
---|---|---|
Total number of SOQL queries issued | 100 | 200 |
Total number of records retrieved by SOQL queries | 50,000 | 50,000 |
Total number of records retrieved by Database.getQueryLocator |
10,000 | 10,000 |
Total number of SOSL queries issued | 20 | 20 |
Total number of records retrieved by a single SOSL query | 2000 | 2000 |
Total number of DML statements issued | 150 | 150 |
Total number of records processed as a result of DML statements, Approval.process , or Database.emptyRecycleBin |
10,000 | 10,000 |
Total stack depth for any Apex invocation that recursively fires triggers due to insert,
update, or delete statements
|
16 | 16 |
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 transaction | 120 | 120 |
Maximum number of methods with the @future annotation allowed per Apex invocation |
50 | 0 (Batch and Future methods), 50 (Queueable) |
Maximum number of Apex jobs added to the queue with System.enqueueJob |
50 | 1 |
Total heap size | 6 MB | 12 MB |
Maximum CPU time on the Salesforce servers | 10,000 ms | 60,000 ms |
Maximum execution time for each Apex transaction | 10 minutes | 10 minutes |
Total 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 call | 2000 | 2000 |
Maximum number of EventBus.publish calls for platform events configured to publish immediately |
2000 | 2000 |
Maximum number of rows per Apex cursor | 50 mil | 50 mil |
Maximum number of Apex cursors per day | 10,000 | 10,000 |
Total number of sendEmail methods allowed |
10 | 10 |
Common Governor Limits and How to Avoid Them
1. SOQL Query Limits
Limit: Maximum of 100 SOQL queries in a synchronous transaction.
How to Avoid:
- Avoid placing SOQL queries inside loops.
- Use collections (lists, sets, maps) to store data and query once before the loop.
Example of SOQL Query Inside Loop (Bad Practice)
for (Account acc : accountList) {
// This query runs once per iteration
List contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
// Process contacts
}
Corrected Version (Good Practice)
Query outside the loop and use maps to store data:
// Collect Account IDs
Set accountIds = new Set();
for (Account acc : accountList) {
accountIds.add(acc.Id);
}
// Query all contacts related to accounts
List contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];
// Create a map of AccountId to Contacts
Map> accountToContacts = new Map>();
for (Contact con : contacts) {
if (!accountToContacts.containsKey(con.AccountId)) {
accountToContacts.put(con.AccountId, new List());
}
accountToContacts.get(con.AccountId).add(con);
}
// Now process contacts per account without additional queries
for (Account acc : accountList) {
List relatedContacts = accountToContacts.get(acc.Id);
// Process relatedContacts
}
2. DML Statement Limits
Limit: Maximum of 150 DML statements per transaction.
How to Avoid:
- Avoid performing DML operations inside loops.
- Use collections to batch DML operations.
Example of DML Inside Loop (Bad Practice)
for (Account acc : accountsToUpdate) {
// This DML operation runs once per iteration
update acc;
}
Corrected Version (Good Practice)
Collect records and perform DML once:
// Collect accounts to update
List accountsToUpdate = new List();
for (Account acc : accountList) {
acc.Some_Field__c = 'New Value';
accountsToUpdate.add(acc);
}
// Perform DML operation once
update accountsToUpdate;
3. CPU Time Limits
Limit: Maximum CPU time on the Salesforce servers is 10,000 ms (synchronous).
How to Avoid:
- Optimize code for efficiency.
- Avoid nested loops and inefficient algorithms.
- Use asynchronous processing where appropriate.
Best Practices to Avoid Governor Limit Exceptions
- Bulkify Your Code: Ensure your code can handle multiple records efficiently.
- Avoid SOQL/DML in Loops: Move queries and DML statements outside loops.
- Use Collections: Leverage lists, sets, and maps to process data efficiently.
- Use @future Methods: For operations that can be run asynchronously.
- Use Limits Apex Methods: Use
Limits.getQueries()
,Limits.getLimitQueries()
, etc., to monitor limits.
Governor Limits in Triggers
Triggers are especially susceptible to governor limits because they can process large volumes of records. Always bulkify your triggers.
Example of Bulkified Trigger
trigger ContactTrigger on Contact (before insert, before update) {
Set accountIds = new Set();
for (Contact con : Trigger.new) {
if (con.AccountId != null) {
accountIds.add(con.AccountId);
}
}
Map accountMap = new Map(
[SELECT Id, Name FROM Account WHERE Id IN :accountIds]
);
for (Contact con : Trigger.new) {
if (con.AccountId != null && accountMap.containsKey(con.AccountId)) {
// Perform logic with the account
Account acc = accountMap.get(con.AccountId);
// ...
}
}
}
Using Limits Class
The Limits
class provides methods to monitor governor limits during execution.
Example:
System.debug('Number of Queries Used: ' + Limits.getQueries());
System.debug('Maximum Number of Queries Allowed: ' + Limits.getLimitQueries());
System.debug('Heap Size Used: ' + Limits.getHeapSize() + ' bytes');
System.debug('Maximum Heap Size Allowed: ' + Limits.getLimitHeapSize() + ' bytes');
Governor Limits for Asynchronous Apex
Asynchronous Apex (like future methods and Batch Apex) have different limits for some governors, such as higher SOQL query limits and CPU time.
Summary
Understanding and respecting Governor Limits is crucial when developing Apex code. By following best practices and writing efficient, bulkified code, you can avoid hitting these limits and ensure your applications run smoothly.
Common Governor Limit Errors
Salesforce enforces governor limits to ensure efficient use of resources in a multi-tenant environment. Below are common governor limits, the errors they generate, and examples to simulate these errors.
1. Total Number of SOQL Queries Issued
Limit: Synchronous: 100, Asynchronous: 200
Error Message: System.LimitException: Too many SOQL queries: 101
Example to Simulate the Error
This code performs a SOQL query inside a loop more than 100 times, which will trigger the limit exception.
Savepoint sp = Database.setSavepoint(); // Create a savepoint to roll back later
// Insert 150 Accounts
List<Account> accountList = new List<Account>();
for (Integer i = 0; i < 150; i++) {
accountList.add(new Account(Name = 'Test Account ' + i));
}
insert accountList; // Insert accounts
// Query the newly inserted accounts
List<Account> accounts = [SELECT Id FROM Account LIMIT 150];
// This loop will query 150 times, and should throw SOQL 101 error
for (Integer i = 0; i < accounts.size(); i++) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :accounts[i].Id];
}
Database.rollback(sp); // Rollback the transaction, avoiding any permanent inserts
Note: Running this code in Anonymous Apex will result in a "Too many SOQL queries: 101" error.
2. Total Number of Records Retrieved by SOQL Queries
Limit: 50,000 for both Synchronous and Asynchronous
Error Message: System.LimitException: Too many query rows: 50001
Example to Simulate the Error
This code attempts to retrieve more than 50,000 records in a single transaction.
// Anonymous Apex Code
// Assuming you have more than 50,000 Account records in your org
List<Account> accounts = [SELECT Id FROM Account];
Note: If the total number of Account records exceeds 50,000, this code will trigger the limit exception.
3. Total Number of Records Retrieved by Database.getQueryLocator
Limit: 10,000 for both Synchronous and Asynchronous
Error Message: System.LimitException: Too many query locator rows: 10001
Example to Simulate the Error
This example uses a query locator to retrieve more than 10,000 records.
// Anonymous Apex Code
// Assuming you have more than 10,000 Account records in your org
System.debug(Database.getQueryLocator('SELECT Id FROM Account'));
Note: If the query retrieves more than 10,000 records, it will trigger the limit exception.
4. Total Number of SOSL Queries Issued
Limit: 20 for both Synchronous and Asynchronous
Error Message: System.LimitException: Too many SOSL queries: 21
Example to Simulate the Error
This code runs an SOSL query inside a loop more than 20 times.
// Anonymous Apex Code
for (Integer i = 0; i < 25; i++) {
// This SOSL query runs 25 times
List<List<SObject>> searchList = [FIND 'test' IN ALL FIELDS RETURNING Account(Id)];
}
Note: Running this code will result in a "Too many SOSL queries: 21" error.
5. Total Number of Records Retrieved by a Single SOSL Query
Limit: 2,000 for both Synchronous and Asynchronous
Error Message: No error here. Salesforce automatically caps the max limit by 2000
Example to Simulate the Error
This SOSL query attempts to retrieve more than 2,000 records but only returns a max of 2000.
// Anonymous Apex Code
List<List<SObject>> searchList = [FIND '*a*a*' IN ALL FIELDS RETURNING
Account(Id Order by Id)];
System.debug(searchList[0].size());
Note: The search returns up to 2,000 records due to the limit.
6. Total Number of DML Statements Issued
Limit: 150 for both Synchronous and Asynchronous
Error Message: System.LimitException: Too many DML statements: 151
Example to Simulate the Error
This code performs a DML operation inside a loop more than 150 times.
// Anonymous Apex Code
List<Opportunity> opportunities = new List<Opportunity>();
for (Integer i = 0; i < 151; i++) {
Opportunity opp = new Opportunity(Name = 'Test Opportunity ' + i, StageName = 'Lost', CloseDate = Date.today());
insert opp; // This DML statement runs 151 times
}
Note: Running this code will result in a "Too many DML statements: 151" error.
7. Total Number of Records Processed as a Result of DML Statements
Limit: 10,000 for both Synchronous and Asynchronous
Error Message: System.LimitException: Too many DML rows: 10001
Example to Simulate the Error
This code attempts to insert more than 10,000 records in a single DML operation.
// Anonymous Apex Code
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 10001; i++) {
accounts.add(new Account(Name = 'Test Account ' + i));
}
insert accounts; // Attempt to insert 10,001 records
Note: This code will trigger the "Too many DML rows: 10001" error when trying to insert the records.
8. Total Stack Depth for Recursive Trigger Invocation
Limit: 16
Error Message: System.LimitException: Maximum trigger depth exceeded
Example to Simulate the Error
This trigger recursively updates records, exceeding the maximum stack depth.
trigger AccountTrigger on Account (before insert, before update) {
// This static variable will keep track of the number of times the trigger has executed
if (Trigger.isInsert || Trigger.isUpdate) {
for (Account acc : Trigger.new) {
// Attempt to trigger the recursion by modifying a field
acc.Name = acc.Name + ' modified';
// Recursively insert another Account to trigger the recursion
// This will lead to a recursion limit breach if done enough times
Account newAcc = new Account(Name = acc.Name);
insert newAcc; // This will trigger the same trigger again
}
}
}
// Anonymous Apex Code to update an Account
Account acc = [SELECT Id FROM Account LIMIT 1];
acc.Name = 'Trigger Test';
update acc;
Note: This code will cause the trigger to fire recursively until the maximum depth is exceeded.
9. Total Number of Callouts in a Transaction
Limit: 100 for both Synchronous and Asynchronous
Error Message: System.LimitException: Too many callouts: 101
Example to Simulate the Error
This code performs more than 100 HTTP callouts in a single transaction.
// Anon apex
String endpoint = 'https://example.com/api/resource';
// Initialize HTTP and HTTPRequest objects
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint(endpoint);
request.setMethod('GET');
// Counter for the callouts
Integer calloutCount = 0;
// Loop to make 101 callouts to trigger the error
while (calloutCount < 101) {
try {
HttpResponse response = http.send(request);
System.debug('Callout ' + (calloutCount + 1) + ' Response: ' + response.getBody());
calloutCount++;
} catch (Exception e) {
System.debug('Error during callout: ' + e.getMessage());
}
}
Note: This code will result in a "Too many callouts: 101" error.
10. Maximum Cumulative Timeout for All Callouts
Limit: 120 seconds for both Synchronous and Asynchronous
Error Message: System.LimitException: Maximum cumulative callout timeout: 120001 ms
Example to Simulate the Error
This code makes callouts with total timeout exceeding 120 seconds.
// Anonymous Apex Code
Http http = new Http();
for (Integer i = 0; i < 13; i++) {
HttpRequest request = new HttpRequest();
request.setEndpoint('https://www.example.com/slow'); // Assume this endpoint takes 10 seconds to respond
request.setMethod('GET');
request.setTimeout(10000); // 10 seconds
HttpResponse response = http.send(request);
}
Note: 13 callouts * 10 seconds = 130 seconds, exceeding the 120-second limit.
11. Maximum Number of @future Methods per Invocation
Limit: Synchronous: 50, Asynchronous: 0 for Batch and Future methods, 50 for Queueable
Error Message: System.LimitException: Too many future calls: 51
Example to Simulate the Error
This code calls a @future method more than 50 times in a single transaction.
// Apex Class
public class FutureCallExample {
@future
public static void futureMethod() {
// Future method logic
}
}
// Anonymous Apex Code
for (Integer i = 0; i < 51; i++) {
FutureCallExample.futureMethod(); // 51 future method calls
}
Note: This will trigger the "Too many future calls: 51" error.
12. Maximum Number of Apex Jobs Added to the Queue
Limit: Synchronous: 50, Asynchronous: 1
Error Message: System.LimitException: Too many queueable jobs added to the queue: 2
Example to Simulate the Error
This code enqueues more than one job from an asynchronous context.
// Queueable Class
public class QueueableJob implements Queueable {
public void execute(QueueableContext context) {
// Enqueue another job
System.enqueueJob(new QueueableJob()); // First enqueue
System.enqueueJob(new QueueableJob()); // Second enqueue (exceeds limit)
}
}
// Anonymous Apex Code
System.enqueueJob(new QueueableJob());
Note: Enqueuing more than one job from an asynchronous context will trigger the limit exception.
13. Total Heap Size
Limit: Synchronous: 6 MB, Asynchronous: 12 MB
Error Message: System.LimitException: Apex heap size too large: X MB
Example to Simulate the Error
This code consumes more than the allowed heap size by creating large objects.
// Anonymous Apex Code
List <String> largeList = new List<String>();
// Create a large string to add to the list
String largeString = 'a'; // Start with a single character
// Create a very large string by appending 'a' 1000000 times
for (Integer i = 0; i < 1000000; i++) {
largeString += 'aaaaaaaaaaaaaaaaaaaaa'; // Increase the string size significantly
if (largeString.length() > 100000) { // After 100000 characters, add to the list
largeList.add(largeString);
largeString = 'aaaaaaaaaaaaaaaaaaaaa'; // Reset the largeString to start over
}
}
// This should exceed the heap size limit in synchronous execution
String finalString = String.join(largeList, '');
// Output size (this will likely not run)
System.debug('Total size of finalString: ' + finalString.length());
Note: This code will exceed the 6 MB heap size limit in synchronous execution.
14. Maximum CPU Time on the Salesforce Servers
Limit: Synchronous: 10,000 ms, Asynchronous: 60,000 ms
Error Message: System.LimitException: Apex CPU time limit exceeded
Example to Simulate the Error
This code performs a computation-intensive task to exceed CPU time.
// Anonymous Apex Code
Integer count = 0;
for (Integer i = 0; i < 1000000; i++) {
for (Integer j = 0; j < 100; j++) {
count += 1;
}
}
System.debug('Count: ' + count);
Note: This nested loop will consume more than 10,000 ms of CPU time.
15. Total Number of sendEmail Methods Allowed
Limit: 10 for both Synchronous and Asynchronous
Error Message: System.LimitException: Too many Email Invocations: 11
Example to Simulate the Error
This code calls Messaging.sendEmail more than 10 times.
// Anonymous Apex Code
for (Integer i = 0; i < 11; i++) {
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setSubject('Test Email ' + i);
mail.setPlainTextBody('This is a test email.');
mail.setToAddresses(new String[] {'test@example.com'});
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
Note: This will trigger the "Too many Email Invocations: 11" error.