Apex Best Practices 2025: Architect-Approved Coding Standards

🛠️ Introduction

If you’re still writing Apex the same way you did three years ago… we need to talk. 😄

The Salesforce ecosystem has evolved — and so should our coding standards.

Whether you’re a beginner, a mid-level dev trying to clean up legacy mess, or an aspiring architect aiming for future-proof code, this post outlines the Apex best practices for 2025, straight from the architect’s playbook.

Let’s dive into what’s still gold, what’s outdated, and what you need to upgrade now.


✅ 1. Bulkify Everything — No Exceptions

Still valid? Yes. More than ever.

In 2025, governor limits haven’t gone anywhere. Bulkification remains your first line of defence.

🚫 Don’t:

for (Contact c : contacts) {
c.Account = [SELECT Name FROM Account WHERE Id = :c.AccountId];
}

✅ Do:

Set<Id> accountIds = new Set<Id>();
for (Contact c : contacts) accountIds.add(c.AccountId);

Map<Id, Account> accountMap = new Map<Id, Account>(
[SELECT Name FROM Account WHERE Id IN :accountIds]
);

for (Contact c : contacts) {
c.Account = accountMap.get(c.AccountId);
}

📌 Think scale: If it breaks at 201 records, it’s not ready.


✅ 2. Use Custom Metadata & Custom Settings Like a Pro

Hardcoding values is so 2018.
Today, we build configurable, admin-friendly logic.

🔍 Use cases:

  • Feature toggles
  • Business logic flags (ex: region-wise discount logic)
  • External system keys or endpoint URLs

📦 Architects think “framework,” not just “function.”


✅ 3. Keep Triggers Lightweight with Trigger Handlers

🚫 Bad:

trigger OpportunityTrigger on Opportunity (after update) {
for (Opportunity o : Trigger.new) {
if (o.StageName == 'Closed Won') {
o.Description = 'Won!';
}
}
}

✅ Better (Trigger → Handler → Service):

trigger OpportunityTrigger on Opportunity (after update) {
OpportunityTriggerHandler.handleAfterUpdate(Trigger.new, Trigger.oldMap);
}
public class OpportunityTriggerHandler {
public static void handleAfterUpdate(List<Opportunity> newList, Map<Id, Opportunity> oldMap) {
// business logic here
}
}

🔥 Bonus: Use a framework like fFlib, or roll out your own clean structure.
🧼 Because triggers are not dumping grounds.


✅ 4. Don’t Ignore Naming Conventions

Yes, naming is hard. But it’s your legacy. Choose wisely.

BadGood
calcDisccalculateDiscountAmount
tempAccListnewCustomerAccounts
HelperClass1InvoicePDFGeneratorHelper

🎯 Name it like someone will read it later — because they will. (It might be you.)


✅ 5. One Class = One Responsibility

Keep classes focused. Don’t mix data access, business logic, and UI logic in one monster file.

Split your layers:

  • Service Layer: Handles orchestration
  • Helper Layer: Reusable business logic
  • DAO (Data Access Object): Queries and DML

👑 Architects design with intention — not just implementation.


✅ 6. Test Coverage Is Not a Goal. Quality Is.

🚫 What NOT to do:

@isTest
private class AccountTest {
static testMethod void testRun() {
Account a = new Account(Name='Test');
insert a;
}
}

✅ Do this:

  • Assert outcomes, not just execution
  • Cover positive & negative paths
  • Use Test.startTest() / Test.stopTest()
@isTest
private class AccountServiceTest {
@isTest
static void testCreateAccountWithInvalidName() {
try {
AccountService.createAccount('');
System.assert(false, 'Expected exception was not thrown.');
} catch (MyCustomException ex) {
System.assertEquals('Account Name cannot be blank', ex.getMessage());
}
}
}

🧪 Good tests prevent regressions, not just red highlights.


✅ 7. Leverage Modern Features in Apex

🚀 In 2025, embrace:

  • switch statements
  • User mode database operations (Database.insert(record, accessLevel) )
  • Safe navigation (?.)
  • Custom metadata for logic branching
  • Apex defined types with Flow

🤹‍♂️ Don’t stick to Apex 2014 when you have Apex 2025 power.


✅ 8. Security First — Not As an Afterthought

  • Use with sharing unless there’s a very good reason not to.
  • Respect CRUD/FLS using Security.stripInaccessible().
  • Audit critical logic with field-level checks.

🔐 Don’t ship logic that breaks visibility. Or trust.


✅ 9. Log Smartly, Not Loudly

Too much logging = noise.
No logging = darkness.

Use structured logging:

LoggingService.logInfo('LeadConversion', 'Lead {0} converted to Opportunity {1}', new List<String>{leadId, oppId});

📁 Store logs using Platform Events, Custom Objects, or integrated with external logging tools like Datadog or Splunk.

🧭 Logging is your compass when bugs hit prod.


✅ 10. Comment With Purpose — Not Just Because

Bad:

// This for loop processes contacts
for (Contact c : contactList) { ... }

Good:

// Filter out contacts who opted out of email communication
List<Contact> filteredContacts = ...

✍️ Comments should explain intent, not echo code.


🧩 Bonus: Design Pattern You Should Be Using

Strategy Pattern – Clean way to handle rule variations (like different pricing logic by region).

Factory Pattern – Useful for returning the right handler class based on record type or custom metadata config.

Facade Pattern – Great for simplifying complex flows (like quote-to-cash).


🧠 Final Thought

Writing Apex isn’t hard.
Writing good Apex — that scales, survives, and supports change — is where real engineering begins.

Every class you write is a tiny piece of architecture.
And in 2025, code is no longer just execution — it’s enablement.

So write Apex that:

  • Admins can work with
  • Other devs can extend
  • Your future self can still understand 😅

Exit mobile version