Menu

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.
Menu

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:
  • Example: Integer I; and Integer i; are considered the same.

  • Object and Field Names:
  • Example: Account a1; and ACCOUNT a2; are considered the same.

  • SOQL and SOSL Statements:
  • 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};
                

Menu

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

  Blob myBlob = Blob.valueOf('Hello, Apex!');
  String myString = myBlob.toString();
                    
Boolean Represents true or false values.

If uninitialized, it will hold null value

  Boolean isTrue = true;
  Boolean myBooleanVar; // null
                    
Date Stores only the date component.

  Date myDate = Date.today();
  Date myCustomDate = date.new

Instance(2018,12,31);
                    
DateTime Represents a specific point in time, including date and time.

  DateTime myDateTime = DateTime.now();
                    
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.

  Decimal myDecimal = 10.5;
                    
Double Represents double-precision floating-point numbers in Apex.

  Double myDouble = 3.14;
                    
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.

  ID recordId = '001R0000003EJj7IAG';
  //If the ID isn't valid, it throws run time exception
                    
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.

  Integer myVariable = 10;
                    
Long Supports larger integer values in Apex.

  Long myLong = 9876543210L;
                    
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.

  Object myObject = new Account(Name='Example Account');

  Account acc1 = new Account();
  Opportunity opp = new Opportunity(Name = 'New Opportunity', CloseDate = date.today(), StageName = 'Qualification');
                    
String Represents a sequence of characters.

  String myString = 'Hello, Apex!';
                    
Time Stores only the time component.

It takes the hour, minutes, seconds, milliseconds parameters respectively.

  Time myTime = Time.newInstance(12, 30, 0, 0);
                    
Menu

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.
Menu

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,

Description of the image

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.

Menu

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.

Menu

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.
  */
                

Menu

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.

Menu

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.

Menu

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.

Menu

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.

Menu

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
Description of the image

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
    }
  }
                

Menu

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.

Menu

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.

Menu

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; }
          }
              

Menu

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.
Menu

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.
              

Menu

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.

Menu

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) uses this.message = message to set the instance variable message with the value passed to the constructor.
  • The no-argument constructor MyClass() calls the other constructor using this('Default Message through constructor chaining'), which is known as constructor chaining.
  • The printMessage method uses this.message to refer to the instance variable message 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, and printMessage prints: "This prints using dot notation".
  • obj2 is created with the default message through constructor chaining, and printMessage 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;
                

Menu

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.

                public with sharing class SharingClass {
                  // Code here
                  public void execute() {
                      // Your code logic here
                      // For example, querying contacts with user's sharing settings
                      List<Contact> contacts = [SELECT Id, Name FROM Contact LIMIT 10];
                  }
                }                                 
              
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.

                      public without sharing class NoSharing {
                      // Code here
                      public void execute() {
                          // Your code logic here
                          // For example, querying all contacts regardless of user's sharing settings
                          List<Contact> contacts = [SELECT Id, Name FROM Contact LIMIT 10];
                      }
                  }
                
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.

                      public inherited sharing class InheritClass {
                        // Code here
                        public void execute() {
                            // Your code logic here
                            // For example, querying contacts based on the calling class's sharing settings
                            List<Contact> contacts = [SELECT Id, Name FROM Contact LIMIT 10];
                        }
                    }                    
              

Using these keywords appropriately ensures that your Apex code respects the sharing rules and security model of your Salesforce organization.

Menu

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.
Menu

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.
              

Menu

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).

      Database.DMLOptions dml = new Database.DMLOptions();
      dml.allowFieldTruncation = true;
                              
assignmentRuleHeader Specifies assignment rules for cases or leads, either by ID or by using the default rule.

      Database.DMLOptions dmo = new Database.DMLOptions();
      dmo.assignmentRuleHeader.useDefaultRule = true;
      Lead l = new Lead(company='ABC', lastname='Smith');
      l.setOptions(dmo);
      insert l;
                              
duplicateRuleHeader Determines whether a record identified as a duplicate can be saved.

      Database.DMLOptions dml = new Database.DMLOptions();
      dml.duplicateRuleHeader.allowSave = true;
      Account duplicateAccount = new Account(Name='dupe');
      Database.SaveResult sr = Database.insert(duplicateAccount, dml);
                              
emailHeader Specifies options for triggering auto-response emails and user emails.

      Database.DMLOptions dlo = new Database.DMLOptions();
      dlo.EmailHeader.triggerAutoResponseEmail = true;
      Contact c = new Contact(lastName = 'Kingston');
      Case ca = new Case(subject='Plumbing Problems', contactid=c.id);
      database.insert(ca, dlo);
                              
localeOptions Specifies the language of any labels returned by Apex.

         Database.DMLOptions dml = new Database.DMLOptions();
         dml.localeOptions = 'fr_CA'; //French Canadian
                              
optAllOrNone Specifies whether the operation allows for partial success. If true, all changes are rolled back if any record causes errors.

            Database.DMLOptions dml = new Database.DMLOptions();
            dml.optAllOrNone = true;
            Account acc = new Account(Name = 'Test AllorNone Account');
            Database.SaveResult[] srList = Database.insert(new List{acc}, dml);
                              

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.

Menu

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.


Menu

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
   }
            

Menu

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 SetupDeveloper Console, then go to DebugOpen 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.
Menu

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.
Menu

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
  1. Go to Setup → Apex Classes.
  2. Click "Schedule Apex".
  3. 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.
Menu

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:

  1. Define an Apex class with the global access modifier.
  2. Define methods within the class using the webservice keyword.
  3. 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:

  1. Go to Setup → Apex Classes.
  2. Click on the name of your class that contains the webservice methods.
  3. Click on "Generate WSDL".
  4. 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, or Pattern.
  • 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:

  1. Annotate your Apex class with @RestResource and define a URL mapping.
  2. Annotate methods with HTTP method annotations like @HttpGet, @HttpPost, etc.
  3. 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.
Menu

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:

  1. Create an Apex class that implements the Messaging.InboundEmailHandler interface.
  2. Define an email service and associate it with the Apex class.
  3. 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:

  1. Go to Setup → Email → Email Services.
  2. Click "New Email Service".
  3. 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.
  4. 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 set result.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.
Menu

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.

Menu

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.


Buy me a coffee