Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Apex

Apex: The Complete Guide to Salesforce's Programming Language

Apex is Salesforce's strongly-typed, server-side language. This pillar covers data types, triggers, async patterns, governor limits, testing, and the detailed guides on every sub-topic.

The short answer

Apex is Salesforce's strongly typed, server-side programming language used to execute triggers, async processes, and complex business logic directly on the Lightning Platform. Writing production Apex requires working with database-native sObjects, staying within multi-tenant governor limits, and achieving at least 75% test coverage.

Key takeaways Apex is Salesforce's strongly-typed, server-side language, running on Lightning Platform servers behind triggers and custom REST endpoints. Multi-tenant governor limits bound everything you write, including the 100-query SOQL cap per transaction. There are 4 asynchronous execution types to choose between when the synchronous limits are too tight. Deploying to production requires 75% test coverage, so write bulk-safe code and its tests together rather than after.

This Apex developer guide covers what you need to build backend logic on the Salesforce platform, from data types through triggers and asynchronous execution. It sets out the multi-tenant governor limits, including the 100-query SOQL cap, the 4 async execution types, and the mandatory 75% test coverage. Use it as a reference for writing bulk-safe code and for deciding between Apex and Flow.

What is Apex?

Apex is Salesforce's proprietary, strongly-typed, server-side programming language. It looks like Java, runs on Lightning Platform servers, and sits behind triggers, custom REST endpoints, scheduled jobs, batch processing, and any business logic too complex for Flow.

Three things make Apex distinctive:

  1. Multi-tenant governor limits. Every transaction is capped on CPU time, SOQL queries, DML statements and heap size, so one tenant's bad code can't degrade another's experience.
  2. Database-native types. sObject types map directly to Salesforce objects: Account a = new Account(Name='Acme'); insert a; with no ORM and no manual mapping.
  3. Required test coverage. 75% line coverage to deploy to production, enforced at the platform level. It is a nuisance on day one and one of the reasons Salesforce orgs survive multi-developer teams over years.

Core concepts

Data types

Apex has five categories: primitives (Integer, Long, Double, Decimal, String, Boolean, Date, DateTime, Time, Id, Blob), collections (List, Set, Map), sObjects (Account, Contact, Custom__c), enums, and classes. Choosing the right one matters more than it looks: Decimal for currency because it is exact, Double for math because it is not, Long only when you need values past 2 billion. Full reference: SFDC Data Types in Apex: Primitives, Collections & sObjects.

Classes and methods

public with sharing class AccountService {
  public static List<Account> getActiveByIndustry(String industry) {
    return [SELECT Id, Name FROM Account WHERE Industry = :industry AND IsActive__c = TRUE];
  }
}

with sharing / without sharing / inherited sharing controls record visibility. public / private / global controls package visibility. Most production code uses public with sharing.

Triggers

Triggers are a special Apex type that runs on every DML operation against an object. There are 7 trigger events across 2 timing categories. Full guide: Types of Apex Triggers: Before, After & 7 Trigger Events.

Async Apex

When work exceeds synchronous limits or shouldn't block the UI, Apex gives you four async patterns:

Pattern When to use
Queueable Modern default. One job, full governor limits, supports chaining
Batch Apex Massive record sets, millions of rows processed in chunks
@future Legacy fire-and-forget, superseded by Queueable in most cases
Schedulable Cron-style scheduled execution

Common patterns

SOQL and DML

Apex embeds SOQL natively in square brackets:

List<Account> accs = [SELECT Id, Name FROM Account WHERE Industry = 'Tech'];
for (Account a : accs) {
  a.Name = a.Name.toUpperCase();
}
update accs;

Three rules: never put DML inside a loop, bulkify everything, and use bind variables (:myVar) instead of string concatenation. See the SOQL pillar for query depth.

String manipulation

Apex's String class has 40+ methods. The ones you'll use daily are split, join, contains, startsWith, replaceAll, format, and escapeSingleQuotes, which is what stands between dynamic SOQL and an injection bug. Full reference: Apex String Class: 30 Methods Salesforce Developers Use Daily.

Test classes

Every Apex class needs tests:

@isTest
private class AccountServiceTest {
  @TestSetup
  static void setup() {
    insert new Account(Name='Acme', Industry='Tech');
  }

  @isTest
  static void testGetActiveByIndustry() {
    List<Account> result = AccountService.getActiveByIndustry('Tech');
    System.assertEquals(1, result.size(), 'Expected one matching Account');
  }
}

For async testing patterns including the try/catch trap, see Apex Test Class: Catching Thrown Exceptions in Async Batch Jobs.

Governor limits cheat sheet

Resource Sync Async
SOQL queries 100 200
SOQL rows returned 50,000 50,000
DML statements 150 150
DML rows 10,000 10,000
Heap size 6 MB 12 MB
CPU time 10,000 ms 60,000 ms
Callouts 100 100
@future calls 50 50
Email recipients 5,000 / day org-wide shared

Hitting a limit throws a LimitException. Design for these limits up front with bulk-safe code, async offload and chunking. Catching the exception afterwards is not a plan.

Deep-dive guides

When to choose Apex over Flow

Salesforce's official guidance is "configure first, code last." Use Flow when:

  • Logic fits a visual canvas (decision branches, screens, loops over collections).
  • An admin should be able to maintain it.
  • It's a CRUD-style operation: create/update related records.

Use Apex when:

  • The logic is genuinely complex (recursion, deep transformations, fiddly validation).
  • You need fine-grained control over governor limits or async chaining.
  • You're building reusable libraries (utilities, integration adapters).
  • Performance matters, and Apex is faster than Flow for tight loops.

The decision tree, with examples: Apex vs Flow: When to Use Code.

Common Apex mistakes

  • DML in a loop. Collect into a List, then run the DML once after the loop.
  • No null check before .equals(). Flip it to 'expected'.equals(myStr), or check String.isNotBlank first.
  • Forgetting with sharing. Default Apex doesn't enforce sharing, so declare it explicitly every time.
  • String concatenation in a loop. Use String.join for output instead of +=.
  • No Test.startTest() / stopTest(). Async code in tests doesn't run without it, and the assertions pass silently.
  • Hardcoded IDs. Use bind variables, custom metadata, or a config class.
  • No bulk testing. A trigger that works on one record breaks at 200, so test in bulk inside @isTest.

Apex is one of those languages where the syntax is easy and the discipline is hard. The guides linked above cover the discipline: the patterns that make Apex code survive multi-year orgs and multi-developer teams. Start with data types, then triggers, then async, then testing. The other 80% you'll pick up by writing real code.

Frequently asked questions

What is Apex in Salesforce?

Apex is Salesforce's proprietary, strongly-typed, object-oriented programming language that runs on Lightning Platform servers. Syntax is Java-like. It's used for triggers, REST/SOAP API endpoints, scheduled jobs, batch processing, controller code behind Visualforce pages, and any logic too complex for declarative tools (Flow). Apex code respects multi-tenant governor limits and runs in a sandboxed runtime.

What are the main features of Apex?

Strongly-typed (every variable has a compile-time type), object-oriented (classes, interfaces, inheritance), tightly integrated with the Salesforce data model (sObject types, automatic SOQL/DML), governed by per-transaction limits (CPU, SOQL, DML, heap), test-coverage required (75% to deploy to production), and runs in user, system, or 'without sharing' contexts depending on declaration.

Is Apex similar to Java?

Syntactically, yes: class declarations, generics, exception handling, and access modifiers all follow Java conventions. Semantically, Apex is more like a stored-procedure language: bound to a database, single-threaded per transaction, with hard governor limits. There's no JVM, no manual garbage collection, no threading. Most Java developers ramp up in days; the Salesforce-specific concepts (sharing model, governor limits, sObjects) take weeks.

What are governor limits in Apex?

Governor limits are per-transaction caps Salesforce enforces to protect the multi-tenant runtime. Key limits per synchronous transaction: 100 SOQL queries, 150 DML statements, 10,000 records DML'd, 6 MB heap, 10,000ms CPU. Async (Batch, Queueable, Future) gets higher limits: 200 queries, 12 MB heap, 60,000ms CPU. Hit any limit and Salesforce throws a LimitException.

What's the difference between Batch Apex, Queueable, and @future?

All three run async. Batch processes massive record sets in chunks (Database.executeBatch), used for nightly jobs over millions of records. Queueable runs once with object-typed input, supports chaining, and has full governor limits, which makes it the modern default for async work. @future is the legacy fire-and-forget method for simple async; superseded by Queueable in most cases. See the Apex async guide for the decision tree.

Do I need to write tests for Apex?

Yes. Salesforce requires 75% code coverage on Apex to deploy to production, and every trigger must have at least 1% coverage. Tests must use @isTest annotation, run in transaction-isolated mode by default, and use Test.startTest()/stopTest() to flush async queues. Coverage on its own is not quality, so aim for 90%+ on production code and write tests that assert behavior instead of just line execution.

How do I learn Apex from scratch?

Three-step path: (1) Trailhead, starting with the Apex Basics module, then Triggers, then SOQL/DML; (2) build a personal Developer Edition org and write code that solves a real problem (a contact deduper, a scheduled email sender); (3) read the official Apex Developer Guide as reference. Avoid jumping straight to advanced patterns, because a solid grounding in classes, triggers, and async pays off long-term.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment