Apex is Salesforce’s proprietary programming language designed specifically for building custom business logic on the Salesforce platform. What is apex exactly? It’s a strongly-typed, object-oriented language that runs entirely on Salesforce servers, allowing developers and administrators to create custom applications, automate processes, and extend platform functionality beyond standard configuration options.
Unlike client-side languages like JavaScript, Apex executes on Salesforce’s multi-tenant architecture with built-in security, governor limits, and seamless integration with Salesforce data and metadata. This server-side execution model ensures consistent performance and maintains Salesforce’s security standards across all custom code.
Understanding Apex Fundamentals
Apex shares syntax similarities with Java and C#, making it accessible to developers familiar with these languages. The language supports object-oriented programming concepts including classes, interfaces, inheritance, and polymorphism. Key characteristics include:
- Strongly typed: Variables must be declared with specific data types
- Case-insensitive: Apex keywords and identifiers are not case-sensitive
- Automatic memory management: Garbage collection handles memory allocation
- Built-in testing framework: Requires minimum 75% code coverage for production deployment
Here’s a basic Apex class example:
public class AccountProcessor {
public static void updateAccountType(List<Account> accounts) {
for (Account acc : accounts) {
if (acc.AnnualRevenue > 1000000) {
acc.Type = 'Enterprise';
} else {
acc.Type = 'Standard';
}
}
update accounts;
}
}
Apex Code Execution Context
Apex code executes within specific contexts on the Salesforce platform. Understanding these execution contexts is crucial for writing efficient, governor-limit-compliant code:
| Execution Context | Description | Common Use Cases |
|---|---|---|
| Trigger Context | Executes before/after DML operations | Data validation, field updates, related record creation |
| Batch Context | Processes large data sets asynchronously | Mass data updates, cleanup operations |
| Scheduled Context | Runs at specified times | Recurring maintenance tasks, report generation |
| Future Context | Asynchronous execution | External web service callouts, long-running operations |

SFDC Apex Development Environment
Salesforce provides multiple development environments for writing and testing Apex code:
Developer Console
The built-in Developer Console offers basic IDE functionality including:
- Syntax highlighting and code completion
- Debug logs and checkpoint debugging
- Query Editor for SOQL and SOSL
- Test execution and code coverage analysis
Visual Studio Code with Salesforce Extensions
The recommended development environment provides:
- Advanced IntelliSense and error detection
- Integrated source control with Git
- Org browser for metadata exploration
- Apex PMD for code quality analysis
Salesforce CLI Integration
Command-line tools enable:
- Scratch org creation and management
- Metadata deployment and retrieval
- Automated testing and continuous integration
- Package development workflows
Apex Developer Guide: Core Concepts
Data Types and Variables
Apex supports primitive data types, sObject types, and collection types:
// Primitive types
Integer count = 10;
String name = 'Salesforce';
Boolean isActive = true;
Date today = Date.today();
// sObject types
Account acc = new Account(Name='Test Account');
Contact con = [SELECT Id, Name FROM Contact LIMIT 1];
// Collection types
List<String> cities = new List<String>{'New York', 'London', 'Tokyo'};
Set<Id> accountIds = new Set<Id>();
Map<Id, Account> accountMap = new Map<Id, Account>();
SOQL and DML Operations
Apex integrates seamlessly with Salesforce’s query language (SOQL) and data manipulation language (DML):
// SOQL Query
List<Account> accounts = [SELECT Id, Name, Type
FROM Account
WHERE AnnualRevenue > 500000
ORDER BY Name
LIMIT 100];
// DML Operations
List<Contact> contactsToInsert = new List<Contact>();
for (Account acc : accounts) {
contactsToInsert.add(new Contact(
FirstName = 'John',
LastName = 'Doe',
AccountId = acc.Id
));
}
insert contactsToInsert;
Apex Basics: Governor Limits and Best Practices
Salesforce enforces governor limits to ensure platform stability in the multi-tenant environment. Key limits include:
- SOQL Queries: 100 synchronous, 200 asynchronous per transaction
- DML Statements: 150 per transaction
- CPU Time: 10 seconds synchronous, 60 seconds asynchronous
- Heap Size: 6MB synchronous, 12MB asynchronous
Bulkification Patterns
Always write bulk-safe code to handle multiple records efficiently:
// Bad: Non-bulkified trigger
trigger AccountTrigger on Account (after insert) {
for (Account acc : Trigger.new) {
// SOQL in loop - governor limit violation
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}
}
// Good: Bulkified trigger
trigger AccountTrigger on Account (after insert) {
Set<Id> accountIds = new Set<Id>();
for (Account acc : Trigger.new) {
accountIds.add(acc.Id);
}
// Single SOQL query outside loop
Map<Id, List<Contact> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact con : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
if (!contactsByAccount.containsKey(con.AccountId)) {
contactsByAccount.put(con.AccountId, new List<Contact>());
}
contactsByAccount.get(con.AccountId).add(con);
}
}
Common Apex Use Cases
Trigger Development
Triggers execute custom logic before or after record changes:
trigger OpportunityTrigger on Opportunity (before update) {
OpportunityTriggerHandler.handleBeforeUpdate(Trigger.new, Trigger.oldMap);
}
public class OpportunityTriggerHandler {
public static void handleBeforeUpdate(List<Opportunity> newOpps,
Map<Id, Opportunity> oldMap) {
for (Opportunity opp : newOpps) {
Opportunity oldOpp = oldMap.get(opp.Id);
if (opp.StageName != oldOpp.StageName && opp.StageName == 'Closed Won') {
opp.CloseDate = Date.today();
}
}
}
}
Batch Processing
Handle large data volumes with batch Apex:
public class AccountCleanupBatch implements Database.Batchable<sObject> {
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT Id, Name FROM Account WHERE LastModifiedDate < LAST_N_DAYS:365'
);
}
public void execute(Database.BatchableContext bc, List<Account> accounts) {
for (Account acc : accounts) {
acc.Description = 'Archived: ' + Date.today().format();
}
update accounts;
}
public void finish(Database.BatchableContext bc) {
// Send completion email or trigger next process
}
}
Testing and Deployment
Apex requires comprehensive test coverage for production deployment:
@isTest
public class AccountProcessorTest {
@testSetup
static void setupTestData() {
List<Account> accounts = new List<Account>{
new Account(Name='Enterprise Corp', AnnualRevenue=2000000),
new Account(Name='Small Business', AnnualRevenue=50000)
};
insert accounts;
}
@isTest
static void testUpdateAccountType() {
List<Account> accounts = [SELECT Id, AnnualRevenue FROM Account];
Test.startTest();
AccountProcessor.updateAccountType(accounts);
Test.stopTest();
List<Account> updatedAccounts = [SELECT Id, Type FROM Account];
for (Account acc : updatedAccounts) {
System.assertNotEquals(null, acc.Type, 'Account type should be set');
}
}
}
Integration and External Services
Apex supports REST and SOAP web service integration:
public class ExternalServiceCallout {
@future(callout=true)
public static void makeHttpCallout(String endpoint) {
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint(endpoint);
request.setMethod('GET');
request.setTimeout(120000);
HttpResponse response = http.send(request);
if (response.getStatusCode() == 200) {
// Process response
String responseBody = response.getBody();
// Parse JSON or XML response
}
}
}
Performance Optimization
Optimize Apex code for better performance:
- Use selective SOQL queries: Include WHERE clauses and limit result sets
- Leverage indexes: Query on indexed fields (Id, Name, external ID fields)
- Minimize DML operations: Batch DML statements when possible
- Use asynchronous processing: For long-running operations
- Implement caching: Store frequently accessed data in static variables
Security Considerations
Apex runs in system context by default, bypassing sharing rules and field-level security. Implement security controls:
// Enforce sharing rules
public with sharing class SecureAccountProcessor {
public static List<Account> getAccessibleAccounts() {
return [SELECT Id, Name FROM Account WITH SECURITY_ENFORCED];
}
}
// Check field accessibility
if (Schema.sObjectType.Account.fields.AnnualRevenue.isAccessible()) {
// Safe to access AnnualRevenue field
}
Frequently Asked Questions
What is apex used for in Salesforce?
Apex is used for creating custom business logic, automating processes, building triggers for data validation, developing batch processes for large data operations, integrating with external systems via web services, and extending Salesforce functionality beyond standard configuration options.
Is apex code difficult to learn?
Apex has a moderate learning curve. Developers familiar with Java or C# will find the syntax familiar. For Salesforce administrators new to programming, expect 3-6 months to become proficient with basic concepts, triggers, and simple classes. Trailhead provides structured learning paths for beginners.
What are apex governor limits?
Governor limits are runtime limits enforced by Salesforce to ensure platform stability. Key limits include 100 SOQL queries per synchronous transaction, 150 DML statements per transaction, 10 seconds CPU time for synchronous operations, and 6MB heap size. These limits require careful code design and bulkification patterns.
Can apex code access external systems?
Yes, Apex can integrate with external systems using HTTP callouts for REST APIs, SOAP web services, and platform events. Callouts must be made from asynchronous contexts (future methods, batch apex, or queueable apex) and are subject to timeout limits of 120 seconds.
How do I debug apex code?
Debug Apex code using the Developer Console debug logs, System.debug() statements, checkpoints for variable inspection, and the Apex Replay Debugger in VS Code. Enable debug logging for your user, set appropriate log levels, and use try-catch blocks for error handling in production code.
What is the difference between apex and workflow rules?
Workflow rules provide declarative automation for simple field updates and email alerts, while Apex offers programmatic control for complex business logic. Apex can handle bulk operations, external integrations, and advanced data manipulation that workflow rules cannot perform. Use workflow rules for simple automation and Apex for complex requirements.