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 deep-dive 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.

This Apex developer guide covers the core concepts and patterns needed to build backend logic on the Salesforce platform, from basic data types to triggers and asynchronous execution. It breaks down multi-tenant governor limits like the 100-query SOQL cap, explores the 4 async execution types, and explains how to meet the mandatory 75% test coverage threshold. Use these reference sections and best practices to write bulk-safe code and choose 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 is the backbone of 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 — CPU time, SOQL queries, DML statements, 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; — no ORM, no manual mapping.
  3. Required test coverage. 75% line coverage to deploy to production, enforced at the platform level. The discipline this forces is 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 — Decimal for currency (exact), Double for math (approximate), 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 special Apex types that run on every DML operation against an object. There are 7 trigger events organized into 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 offers 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: (1) never put DML inside a loop, (2) bulkify everything, (3) 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: split, join, contains, startsWith, replaceAll, format, and critically escapeSingleQuotes for dynamic SOQL injection protection. Full reference: Apex String Class: 30 Methods Salesforce Developers Use Daily.

Test classes

Every Apex class needs corresponding 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. The trick is designing for these — bulk-safe code, async offload, chunking — rather than trying to catch the exception after.

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, intricate validation).
  • You need fine-grained control over governor limits or async chaining.
  • You're building reusable libraries (utilities, integration adapters).
  • Performance matters — 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. Always batch into a List, then DML once after the loop.
  • No null check before .equals(). Use 'expected'.equals(myStr) or String.isNotBlank first.
  • Forgetting with sharing. Default Apex doesn't enforce sharing — always declare explicitly.
  • String concatenation in a loop. Use String.join for output, not +=.
  • No Test.startTest() / stopTest(). Async code in tests doesn't run without it; assertions silently pass.
  • Hardcoded IDs. Use bind variables, custom metadata, or a config class.
  • No bulk testing. A trigger that works on one record breaks at 200. Always test bulk in @isTest.

Apex is one of those languages where the syntax is easy and the discipline is hard. The deep-dive 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 — 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 deep dive 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 isn't quality — aim for 90%+ on production code, and write tests that assert behavior, not just line execution.

How do I learn Apex from scratch?

Three-step path: (1) Trailhead — start 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 — 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