Salesforce Development
Hands-onBeginner 45 min· Developer · 9 of 17

Apex basics

Learn Apex from scratch: types, collections, loops, classes, DML, error handling and bulk-safe habits. You run everything in your own org and finish with a reusable service class.

Apex is Salesforce's programming language. It runs on Salesforce's servers, looks like Java, and is built to work with your org's data: querying records, inserting and updating them, and reacting to changes. If you know Java, C# or even Python, you will pick it up quickly.

In this tutorial you will run Apex in the Developer Console, learn types, collections, loops and classes, change data with DML, handle errors, and write a small reusable service class.

Prerequisites: a Developer Edition org, and having read *SOQL and SOSL* (you will use queries inside Apex).

Run your first Apex

  1. 1Click the gear icon and choose Developer Console.
  2. 2Choose Debug > Open Execute Anonymous Window (shortcut Ctrl+E).
  3. 3Type the code below, tick Open Log, and click Execute.
  4. 4In the log window tick Debug Only to see just your messages. Lines with USER_DEBUG are your output.
Execute Anonymous
String shop = 'Brew & Bean';
Integer cups = 3;
Decimal price = 180.50;
Boolean open = true;
System.debug(shop + ' sold ' + cups + ' cups, total ' + (cups * price));
System.debug('Open now? ' + open);

The log shows: Brew & Bean sold 3 cups, total 541.50 and Open now? true.

Apex is strongly typed: every variable has a declared type, and each statement ends with a semicolon. Names are not case sensitive (Shop and shop are the same), but keep one style anyway.

Types and collections

Common types

TypeHoldsExample
StringText, in single quotes'Acme'
Integer, LongWhole numbers42
Decimal, DoubleNumbers with decimals. Use Decimal for money99.95
Booleantrue or falsetrue
Date, DatetimeCalendar valuesDate.today(), Datetime.now()
IdA Salesforce record Id'001xx000003DGb2AAG'
sObjectAny record. Account, Contact and custom objects are specific sObjectsAccount a = new Account(Name = 'Acme');

Apex has three collection types, all generic:

List is an ordered list that allows duplicates, read by position. Set has no duplicates and no order. Map stores key and value pairs. Sets and maps of Ids are everywhere in Apex, because they let you look things up without extra queries.

Collections
List<String> drinks = new List<String>{'Espresso', 'Cold Brew'};
drinks.add('Filter Coffee');
System.debug(drinks[0] + ', size ' + drinks.size());

Set<String> cities = new Set<String>{'Bengaluru', 'Pune', 'Bengaluru'};
System.debug(cities.size()); // 2, the duplicate is dropped

Map<String, Decimal> menu = new Map<String, Decimal>{
    'Espresso' => 120, 'Masala Chai' => 90
};
menu.put('Cappuccino', 180);
System.debug(menu.get('Espresso')); // 120
System.debug(menu.containsKey('Latte')); // false

Prints Espresso, size 3, then 2, then 120, then false.

Control flow

Loops and if
Integer total = 0;
for (Integer i = 1; i <= 5; i++) {
    total += i;
}
System.debug('Sum: ' + total); // 15

List<Integer> orders = new List<Integer>{120, 540, 90};
for (Integer amount : orders) {
    if (amount >= 500) {
        System.debug(amount + ' is a big order');
    } else if (amount >= 100) {
        System.debug(amount + ' is a normal order');
    } else {
        System.debug(amount + ' is a small order');
    }
}

Prints Sum: 15, then 120 normal, 540 big, 90 small. The second loop style, for (type item : collection), is the one you will use most.

Working with records

Records are objects too. Set fields with the constructor or with dot notation, then save with a DML statement. DML (Data Manipulation Language) statements are insert, update, delete, upsert and undelete. After an insert, Salesforce fills in the record's Id.

Create, update, read, delete
Account acc = new Account(Name = 'Brew & Bean Cafe', Industry = 'Technology');
insert acc;
System.debug('New Id: ' + acc.Id);

Contact c = new Contact(FirstName = 'Asha', LastName = 'Rao', AccountId = acc.Id);
insert c;

acc.Phone = '080-5550100';
update acc;

Account saved = [SELECT Id, Name, Phone FROM Account WHERE Id = :acc.Id];
System.debug(saved.Name + ' / ' + saved.Phone);

delete c;

Creates an Account and a Contact, updates the Account's phone, reads it back with SOQL, then deletes the Contact. Check Accounts in the org afterwards: the Account is still there.

One DML for many records

Run DML on lists, not one record at a time inside a loop. Each DML statement counts against a limit of 150 per transaction. insert myList; is one statement for any number of records (up to 10,000 rows). The same applies to queries: never put SOQL inside a loop. See *Apex triggers and governor limits*.

Bulk insert
List<Contact> newContacts = new List<Contact>();
for (Integer i = 1; i <= 5; i++) {
    newContacts.add(new Contact(LastName = 'Tester ' + i));
}
insert newContacts; // one DML statement, five records

Five Contacts are created with a single insert. Delete them later with a SOQL query on LastName LIKE 'Tester%' followed by delete.

Handling errors

A normal DML statement is all or nothing: if any record fails (for example a required field is missing), it throws a DmlException and the whole statement is undone. Catch it to show a helpful message. If you want the good records saved and the bad ones reported, use the Database methods with allOrNone set to false.

try/catch and partial save
List<Account> accts = new List<Account>{
    new Account(Name = 'Good Cafe'),
    new Account() // Name is required, so this one fails
};

try {
    insert accts;
} catch (DmlException e) {
    System.debug('Insert failed: ' + e.getMessage());
}

Database.SaveResult[] results = Database.insert(accts, false);
for (Database.SaveResult sr : results) {
    if (sr.isSuccess()) {
        System.debug('Saved ' + sr.getId());
    } else {
        System.debug('Error: ' + sr.getErrors()[0].getMessage());
    }
}

The first insert fails as a whole and prints the error. The Database.insert call saves Good Cafe and reports a required-field error for the second record.

Classes and methods

Real Apex lives in classes, saved in your org and called from triggers, components or other classes. A class has fields, methods and access modifiers: public (visible across the org) or private (only inside the class). static methods belong to the class and are called without creating an object, which suits helper code.

To create one: in the Developer Console choose File > New > Apex Class, name it AccountService, paste the code and press Ctrl+S to save.

AccountService.cls
public with sharing class AccountService {

    // Create an account and a first contact in one go
    public static Account createWithContact(String accountName, String lastName) {
        Account acc = new Account(Name = accountName);
        insert acc;
        insert new Contact(LastName = lastName, AccountId = acc.Id);
        return acc;
    }

    // Return the accounts in an industry, biggest revenue first
    public static List<Account> findByIndustry(String industry) {
        return [
            SELECT Id, Name, AnnualRevenue
            FROM Account
            WHERE Industry = :industry
            ORDER BY AnnualRevenue DESC NULLS LAST
            LIMIT 50
        ];
    }

    // Number of contacts for each account Id
    public static Map<Id, Integer> countContacts(Set<Id> accountIds) {
        Map<Id, Integer> counts = new Map<Id, Integer>();
        for (Id accId : accountIds) {
            counts.put(accId, 0);
        }
        for (AggregateResult ar : [
            SELECT AccountId acc, COUNT(Id) total
            FROM Contact
            WHERE AccountId IN :accountIds
            GROUP BY AccountId
        ]) {
            counts.put((Id) ar.get('acc'), (Integer) ar.get('total'));
        }
        return counts;
    }
}

A service class with three static methods: one DML example, one query, one aggregate. The keyword 'with sharing' makes the class respect the user's record sharing.

Execute Anonymous
Account a = AccountService.createWithContact('Lakeview Foods', 'Menon');
System.debug('Created ' + a.Id);

Map<Id, Integer> counts = AccountService.countContacts(new Set<Id>{a.Id});
System.debug(counts.get(a.Id) + ' contact(s)');

Prints the new Account Id, then 1 contact(s).

with sharing makes the class obey record sharing rules for the running user. without sharing ignores them. inherited sharing uses the caller's mode. Beginners should default to with sharing. Note that field-level security is a separate matter: use WITH USER_MODE in queries or Security.stripInaccessible when it matters.

Where Apex runs

Entry pointExampleNotes
TriggerRun code when a record is savedCovered in Apex triggers and governor limits
Lightning Web ComponentAn @AuraEnabled method called from a pageCovered in Lightning Web Components
FlowAn invocable Apex actionLets admins call your code
Batch, Queueable, SchedulableProcess large data or run on a scheduleAsynchronous Apex
REST service@RestResource class exposed as an APICovered in Integration: REST APIs and platform events

Debugging tip. Use System.debug() and read the log, or set up a trace flag from Debug > Change Log Levels. Most Apex errors say what and where. Read the message, then the line number.

Prompt · Explain this Apex
You are a senior Salesforce developer teaching a fresher. Explain this Apex code line by line in plain English, point out anything that could hit a governor limit or fail on 200 records, and suggest improvements.

[paste Apex]

What to expect: A good answer explains each statement, flags SOQL or DML inside loops, and suggests collections and bulk-safe patterns. Always run the improved code yourself.

How this comes up in interviews. Expect: the difference between List, Set and Map; what DML is and how many statements are allowed; with sharing versus without sharing; how to handle partial success with Database.insert(list, false); what an sObject is; and why static methods are used for helpers.

Try it: Write a ContactService

Create a class ContactService with two public static methods: emailsForAccount(Id accountId) that returns a List<String> of the non-blank emails of that account's contacts, and markAllAsDoNotCall(Id accountId) that sets DoNotCall = true on all of its contacts using one update statement. Test both in Execute Anonymous with the account you created above.

Key takeaways

  1. Apex is a strongly typed, Java-like language that runs on Salesforce servers and works directly with records.
  2. Use List, Set and Map; sets and maps of Ids make bulk-safe code possible.
  3. DML statements (insert, update, delete, upsert, undelete) save data; run them on a list, outside loops.
  4. Normal DML is all or nothing and throws DmlException; Database methods with allOrNone false allow partial success.
  5. Put logic in classes with clear access modifiers, and use with sharing by default.

Check yourself

Finishing the quiz marks the lesson as done.

1. You need to look up an Account by its Id many times in a loop without querying again. Which structure fits best?

2. What happens when one record in insert myList; fails a required-field check?

3. Why should DML statements be outside loops?

4. What does with sharing on a class do?

5. Which type should you generally use for currency amounts in Apex?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free