Skip to main content
The online Trailblazer Community becomes read-only Oct 30, 2026, as we move new community conversations to Slack. Read the FAQ

Get Started with Apex

Learning Objectives

After completing this unit, you’ll be able to:

  • Describe the key features of the Apex programming language.
  • Save an Apex class and call methods with Anonymous Apex.
  • Use Web Console to inspect debug logs.

Before You Begin

We're excited to accompany you on your Apex adventure. While this module covers basic information about the programming language, it also goes deep, quickly. If this module is your first exposure to Apex, we highly recommend that you first go through the Quick Start: Apex project. Then come back. We’ll be waiting for you!

What is Apex?

Apex is a programming language that uses Java-like syntax and acts like database stored procedures. Apex enables developers to add business logic to system events, such as button clicks, updates of related records, and Visualforce pages.

As a language, Apex is:

  • Hosted—Apex is saved, compiled, and executed on the server—the Salesforce Platform.
  • Object-oriented—Apex supports classes, interfaces, and inheritance.
  • Strongly-typed—Apex validates references to objects at compile time.
  • Multitenant-aware—Because Apex runs on a multitenant platform, it guards closely against runaway code by enforcing limits. These limits help prevent code from monopolizing shared resources.
  • Integrated with the database—It’s straightforward to access and manipulate records. Apex provides direct access to records and their fields, and provides statements and query languages to manipulate those records.
  • Data-focused—Apex provides transactional access to the database, allowing you to roll back operations.
  • Easy to use—Apex is based on familiar Java idioms.
  • Easy to test—Apex provides built-in support for unit test creation, execution, and code coverage.
  • Versioned—Custom Apex code can be saved against different versions of the API.

Apex is a cloud-based programming language

Apex Language Highlights

Like other object-oriented programming languages, these are some of the language constructs that Apex supports:

  • Classes, interfaces, properties, and collections (lists, maps, and sets).
  • Object and array notation.
  • Expressions, variables, and constants.
  • Conditional statements (if-then-else) and control flow statements (for loops and while loops).

Unlike other object-oriented programming languages, Apex supports:

  • Cloud development as Apex is stored, compiled, and executed in the cloud.
  • Triggers, which are similar to triggers in database systems.
  • Database statements that allow you to make direct database calls and query languages to query and search data.
  • Transactions and rollbacks.
  • The global access modifier, which is more permissive than the public modifier and allows access across namespaces and applications.
  • Versioning of custom code.

In addition, Apex is a case-insensitive language.

Development Tools

You can write and debug Apex on your client computer using the Salesforce Extensions for Visual Studio Code. See Develop with Ease with Salesforce Extensions.

You can also write Apex and access debugging information directly in the browser by using the Salesforce user interface. Web Console is a modern, lightweight, browser-based integrated development environment (IDE) designed to bridge the gap between outdated legacy tools and high-overhead development environments. To learn more, see the Web Console developer documentation.

You can open Web Console from the quick access menu (Setup gear icon).

Web Console

Data Types Overview

Apex supports various data types, including a data type specific to Salesforce—the sObject data type.

Apex supports the following data types.

  • A primitive, such as an Integer, Double, Long, Date, Datetime, String, ID, Boolean, among others.
  • An sObject, either as a generic sObject or as a specific sObject, such as an Account, Contact, or MyCustomObject__c. (You’ll learn more about sObjects in a later unit.)
  • A collection, including:
    • A list—an ordered, indexed collection of elements. List elements can be of any data type, including primitive types, collections, sObjects, user-defined types, and system-supplied (built-in) Apex types.
    • A set—an unordered collection of elements. Set elements can also be of any data type and cannot contain duplicate values.
    • A map—a collection of key-value pairs that can be of any data type.
  • A typed list of values, also known as an enum
  • User-defined (custom) Apex classes
  • System-supplied (built-in) Apex classes

Apex Collections: List

Lists hold an ordered collection of data of the same type.

The following two declarations are equivalent. The colors variable is declared using the List syntax.

List<String> colors = new List<String>();

Alternatively, the colors variable can be declared using array notation.

String[] colors = new List<String>();

Grow collections as needed by using the List.add() method to add new elements. You can use the square bracket array notation to reference existing elements in the collection by index. However, you cannot use square bracket array notation to add more elements.

This example shows how to add elements to a list when you create it, and then use the add() method to add more elements. 

// Create a list and add elements to it in one step
List<String> colors = new List<String> { 'red', 'green', 'blue' };
// Add elements to a list after it has been created
List<String> moreColors = new List<String>();
moreColors.add('orange');
moreColors.add('purple');

List elements can be read by specifying an index between square brackets, known as array notation. Also, you can use the get() method to read a list element. This example is based on the lists created in the previous example and shows how to read list elements using either method. The example also shows how to iterate over list elements.

// Get elements from a list
String color1 = moreColors.get(0);
String color2 = moreColors[0];
System.assertEquals(color1, color2);
// Iterate over a list to read elements
System.debug('Print out the colors in moreColors:');
for(String color : moreColors) {
    // Write value to the debug log
    System.debug(color);
}

Beyond the Basics

Apex supports two other collection types: Set and Map. You can learn more about these in the Collections section of the Apex Developer Guide.

Apex Classes

One of the benefits of Apex classes is code reuse. Class methods can be called by triggers and other classes. The following tutorial walks you through saving an example class in your org, using this class to update account descriptions, and inspecting debug logs.

Save an Apex Class

The AccountProcessor class has a public method updateAccountDescriptions(). This method takes a list of accounts and updates their description according to the format specified in the applyStandardFormatting() private helper method. You can’t call private helper methods externally; they must be called from within the class. The AccountProcessor class also contains the inspectResults() private helper method, which inspects the results of the update Database operation.

Save the AccountProcessor class in your Trailhead org.

  1. Open Web Console from the quick access menu (Setup gear icon).
  2. In Web Console, press Ctrl+Shift+P (Windows) or ⌘+Shift+P (macOS) to open the Command Palette.
  3. Enter SFDX: Create Apex Class and press Enter.
  4. For the template type, select DefaultApexClass, and then press Enter.
  5. For the class name, enter AccountProcessor, and then press Enter.
  6. For the output directory, select force-app/main/default/classes, and then press Enter.
  7. Replace the default class body with the AccountProcessorclass example.
public with sharing class AccountProcessor {
    /**
     * Applies a standard formatting to the descriptions of accounts in the list,
     * and updates each account.
     */
    public void updateAccountDescriptions(List<Account> acctList) {
        if (acctList == null || acctList.isEmpty()) {
            System.debug('Account list is null or empty.');
            return;
        }
        for (Account acc : acctList) {
            applyStandardFormatting(acc);
        }
        Database.SaveResult[] srList = Database.update(acctList,
false,
AccessLevel.USER_MODE);
        inspectResults(srList);
    }
    /**
     * Private helper method to handle the description formatting logic.
     */
    private static void applyStandardFormatting(Account acc) {
        String timestamp = String.valueOf(Datetime.now());
        if (String.isBlank(acc.Description)) {
            acc.Description = 'Account updated on: ' + timestamp;
        } else {
            acc.Description += ' | Last processed: ' + timestamp;
        }
    }
    /*
     * Private helper method to inspect the returned results.
     */
    private static void inspectResults(Database.SaveResult[] results) {
        for (Database.SaveResult sr : results) {
            if (sr.isSuccess()) {
                System.debug('Successfully updated account. Account ID: ' + sr.getId());
            } else {
                for (Database.Error err : sr.getErrors()) {
                    System.debug('The following error has occurred.');
                    System.debug(err.getStatusCode() + ': ' + err.getMessage());
                    System.debug('Account fields that affected this error: ' +
err.getFields());
                }
            }
        }
    }
}
  1. To save your class, press Ctrl+S (Windows) or ⌘+S (macOS).

Salesforce compiles and deploys your class when you save it.

Note

If your class contains errors, the deployment will fail when you try to save it. You can view information about the errors in the Output panel at the bottom of the Web Console window.

Beyond the Basics

The class you just saved makes use of object-oriented programming (OOP). The class encapsulates the methods that are related to managing email. To be a perfect example of OOP, the class would also contain member variables (attributes) and accessor methods to access those attributes, but for simplicity our class doesn’t have these.

Call a Method to Update an Account Description

Let’s invoke the public method. We’ll use anonymous Apex execution to do so. Anonymous Apex allows you to run lines of code on the fly and is a handy way to invoke Apex, especially to test out functionality. Debug log results are generated, as with any other Apex execution.

Note

There are other ways to invoke Apex, for example, through triggers. You’ll learn more about triggers in another module.

  1. In Web Console, press Ctrl+Shift+P (Windows) or Cmd+Shift+P (macOS) to open the Command Palette.
  2. Enter SFDX: Create Anonymous Apex Script and press Enter.
  3. For the class name, enter UpdateAccounts, and then press Enter.
  4. For the output directory, select scripts/apex, and then press Enter.
  5. Enter this code.
// Create new accounts
List<Account> newAccts = new List<Account>();
newAccts.add(new Account(Name='My New Account', Description='Original Description'));
newAccts.add(new Account(Name='My Second New Account'));
// Insert them into the database
try {
    insert as user newAccts;
    System.debug('Successfully inserted ' + newAccts.size() + ' accounts.');
} catch (Exception e) {
    System.debug('An error inserting the records occurred: ' + e.getMessage());
}
// Instantiate an AccountProcessor object
AccountProcessor myAcctProcessor = new AccountProcessor();
// Pass the original list to update the account descriptions
myAcctProcessor.updateAccountDescriptions(newAccts);
  1. To save your script, press Ctrl+S (Windows) or ⌘+S (macOS).
  2. To run the script, right-click in the text editor and select SFDX: Execute Anonymous Apex with Currently Open Editor.
  3. In Salesforce, open the My New Account and the My Second New Account record detail pages.

The Description field of My New Account should be Original Description | Last processed: 2026-01-01 00:00:00 and the Description field of My Second New Account should be Account updated on: 2026-01-01 00:00:00, except both timestamps will reflect the time and date that you executed the anonymous Apex transaction.

Inspect Debug Logs

Debug logs are useful for debugging your code. When Apex methods execute, the calls are logged in the debug log. Also, you can write your own debug messages to the log, which helps in debugging your code in case there are errors. The inspectResults() helper method, which is called by updateAccountDescriptions(), writes messages to the log by using the System.debug() method to indicate whether the update database operation was successful or had errors. You can look for these messages in the debug log that was generated when you executed the method.

  1. After you run the UpdateAccounts script in Web Console, open the Output panel.
  2. Select Debug Only to filter the log so that only log lines for System.debug() statements are shown.
  3. In the filter field at the top of the Output panel, enter USER_DEBUG to filter the log so that only log lines for System.debug() statements are shown.
  4. You can also view the debug log in the .sfdx/tools/debug/logs directory.

Filter the debug log in Web Console to view debug messages

You’ll see the following messages in the filtered log view, assuming the script ran without errors.

DEBUG|Successfully inserted 2 accounts.
DEBUG|Successfully updated account. Account ID: {id}
DEBUG|Successfully updated account. Account ID: {id}
Note

Also, you can filter the debug log by searching for any keyword in the filter field. For log event types, see Debug Log Levels.

Call a Static Method

Because the updateAccountDescriptions()method in our class doesn’t access class member variables, it doesn’t need to be an instance method. Let’s change it to a static method by adding the static keyword to its declaration. Static methods are easier to call than instance methods because they don’t need to be called on an instance of the class but are called directly on the class name.

  1. In Web Console, open the AccountProcessor.clsclass file.
  2. Modify the first line of the updateAccountDescriptions() definition. The only change is the added static keyword.
public static void updateAccountDescriptions(List<Account> acctList) {
  1. To save your class, press Ctrl+S (Windows) or ⌘+S (macOS). The class is redeployed when you save.
  2. Open the UpdateAccounts.apex Anonymous Apex script file.
  3. Replace the current code with the following code, so that the script calls the static method on the class name.
// Create new accounts
// For clarity, use different account names than the ones you used previously
List<Account> newAccts = new List<Account>();
newAccts.add(new Account(Name='My Third New Account', Description='Original Description'));
newAccts.add(new Account(Name='My Fourth New Account'));
// Insert them into the database
try {
    insert as user newAccts;
    System.debug('Successfully inserted ' + newAccts.size() + ' accounts.');
} catch (Exception e) {
    System.debug('An error inserting the records occurred: ' + e.getMessage());
}
// Pass the original list to update the account descriptions
// The static method is called directly on the class
AccountProcessor.updateAccountDescriptions(newAccts);
  1. To save your script, press Ctrl+S (Windows) or ⌘+S (macOS).
  2. To run the script, right-click in the text editor and select SFDX: Execute Anonymous Apex with Currently Open Editor. Now that this method has been executed, you can verify the updated account descriptions in Salesforce, and optionally, the debug log as in the previous steps.

Resources

Share your Trailhead feedback over on Salesforce Help.

We'd love to hear about your experience with Trailhead - you can now access the new feedback form anytime from the Salesforce Help site.

Learn More Continue to Share Feedback