logo
shape

Apex

Apex is a strongly typed, object-oriented programming language that allows developers to execute flow and transaction control statements on the Lightning platform server in conjunction with calls to the Lightning Platform API. Apex code in Salesforce is case-insensitive.

Apex Developer Guide
Triggering Automated Processes in Salesforce

trigger AccountTrigger on Account (before insert) {
    for(Account a : Trigger.new){
    a.AnnualRevenue = a.NumberOfEmployees * 1000;
    }
}
                    
APEX Class for a Custom Controller

public class MyController {
    public String myString {
        get; set;
}
    public MyController() {
        myString = 'Hello World';
    }
}
                    
Salesforce APEX Test Classes

@isTest
private class TestMyController {
    static testMethod void testMyController() {

MyController controller = new MyController();
    System.assertEquals('Hello World', controller.myString);
    }
}
                        
Batch APEX for Large Data Volumes

global class BatchApexExample implements Database.Batchable{
    global Database.QueryLocator start(Database.BatchableContext BC){
    r   eturn Database.getQueryLocator('SELECT Name FROM Account');
}
    global void execute(Database.BatchableContext BC, List scope){
        for(Account a : scope){
            a.Name = a.Name + ' - Processed';
        }
update scope;
    }
global void finish(Database.BatchableContext BC){}
}
                            
Write an Apex trigger that modifies Account fields before inserting records.
Please try to separate the trigger, class, and test class into three separate files and remember to create a metadata file for each class and trigger.

//Write an Apex trigger that fires before records are inserted and ensures that the ShippingState field has the same value as the BillingState field.

public class AccountTriggerHandler {
    public static void createAccounts(List accounts) {
        for (Account a : accounts) {
            if (a.ShippingState == null) {
                a.ShippingState = a.BillingState;
            }
        }
        // You should remove the insert statement as this class is called in before insert trigger
        // which means Salesforce will insert these records after trigger finishes execution.
        // insert accounts;
    }
}

//Create an Apex Trigger for the Account object with the following information:
//Name: AccountTrigger
//sObject: Account
//Fires: before insert
//Trigger must call the AccountTriggerHandler class and the createAccounts method.
//Use the isBefore and isInsert context variables to verify that the trigger is running before the records are inserted.

trigger AccountTrigger on Account (before insert) {
    if (Trigger.isBefore && Trigger.isInsert) {
        AccountTriggerHandler.createAccounts(Trigger.new);
    }
}

//Create a test class with the following information:
//Name: AccountTriggerTest
//Test class must insert 200 Account records with a BillingState value of CA.
//After the insert, verify that all 200 Account records have a ShippingState value of CA.

@isTest
public class AccountTriggerTest {
    @isTest static void test() {
        // Arrange
        List accounts = new List();
        for (Integer i = 0; i < 200; i++) {
            // When creating Accounts, set the BillingState, but not the ShippingState
            accounts.add(new Account(Name = 'Test Account ' + i, BillingState = 'CA'));
        }

        // Act
        Test.startTest();
        insert accounts;
        Test.stopTest();

        // Assert
        List insertedAccounts = [SELECT ShippingState FROM Account WHERE Id IN :accounts];
        for (Account a : insertedAccounts) {
            // Assert that the ShippingState was set to the BillingState value
            System.assertEquals('CA', a.ShippingState);
        }
    }
}
                            
Account Address Trigger

//Create an Apex Trigger with the following information:
//Object: Account
//Events: Before Insert and Before Update
//Condition: Match Billing Address is true
//Operation: Set the Shipping Postal Code to the Billing Postal Code

trigger AccountAddressTrigger on Account (before insert, before update) {
    for (Account a : Trigger.new) {
        if (a.Match_Billing_Address__c == true) {
            a.ShippingPostalCode = a.BillingPostalCode;
        }
    }
}
        
Apex bulkified
In the following code example, the trigger written is bulkified, meaning it can handle multiple records at a time, up to Salesforce's governor limit of 200 records per transaction. This code correctly uses a `list (List tasks = new List();)` to accumulate tasks and then performs a single DML operation `(insert tasks;)` outside of the loop. This is an example of bulkification, because it means your code only executes one DML operation regardless of the number of records processed.

//Create an Apex trigger with the following information:
//Object: Opportunity
//Name: ClosedOpportunityTrigger
//Events: After Insert and After Update
//Conditions: Stage is Closed Won
//Operation: Create a task with the following information:
//Subject: Follow Up Test Task
//WhatId: Opportunity ID (associates the task with the opportunity)
//Bulkification: The trigger should be bulkified so that it can insert or update up to 200 records at a time.

trigger ClosedOpportunityTrigger on Opportunity (after insert, after update) {
    List tasks = new List();
    for(Opportunity opp : Trigger.new){
        // Check if the StageName has changed to 'Closed Won'
        if(opp.StageName == 'Closed Won' && (Trigger.isInsert || opp.StageName != Trigger.oldMap.get(opp.Id).StageName)){
            Task t = new Task();
            t.Subject = 'Follow Up Test Task';
            t.WhatId = opp.Id;
            tasks.add(t);
        }
    }
    insert tasks;
}
        

Instead of only checking if the `StageName` is `Closed Won`, which means it creates tasks for all opportunities that are inserted or updated with that stage, even if they were already `Closed Won` before the update, you'd only want to create tasks when an opportunity is closed, not every time a `Closed Won` opportunity is updated. Remember, while Salesforce allows you to process up to 200 records at a time, you should always write your code to handle bulk operations using collections like lists, sets, and maps to avoid hitting governor limits.

Most Asked Questions about Apex
How does Apex handle error handling?

Apex provides a built-in exception handling mechanism, similar to other programming languages like Java. You can use try-catch-finally blocks to catch exceptions and perform appropriate actions. Apex provides a variety of system-defined exceptions, like DmlException, MathException, and others, and you can also define your own custom exceptions by extending the base Exception class.

How does testing work in Apex?

Salesforce requires at least 75% of your Apex code to be covered by unit tests, and all of those tests must complete successfully. Test methods and test classes are not counted as part of Apex code limit. This is done to ensure that all your Apex code is tested and working as expected before it's deployed to production. Test classes in Apex are annotated with @isTest.

What are Batch Apex and Queueable Apex?

Batch Apex and Queueable Apex are both used for processing large data sets, but they are used in slightly different scenarios. Batch Apex is used when you need to break down a large job into smaller batches of records and then process those records. It's useful when you're dealing with data sets that exceed normal governor limits. Queueable Apex is used when you need to start a new job and you want it to run asynchronously. It can be used for chaining jobs—meaning you start another job from a running job. Both Batch and Queueable Apex are part of Salesforce's asynchronous processing capabilities.

© All rights Reserved. 2023