{"id":20233,"date":"2026-08-19T09:15:47","date_gmt":"2026-08-19T09:15:47","guid":{"rendered":"https:\/\/dianapps.com\/blog\/?p=20233"},"modified":"2026-08-19T11:03:02","modified_gmt":"2026-08-19T11:03:02","slug":"apex-programming-guide","status":"publish","type":"post","link":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/","title":{"rendered":"Apex Programming Guide: Triggers, Testing and Best Practices"},"content":{"rendered":"<p><span style=\"font-weight: 400;\">Apex is easy to learn and hard to write well, as the syntax takes about a week while the constraints take a quarter.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Most Apex problems in production aren\u2019t syntax errors; they\u2019re code that worked perfectly against ten records and collapsed against fifty thousand, or a trigger that fires twice because nobody added a recursion guard, or a class that passes 78% coverage while asserting nothing at all.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">This guide covers the language and then the parts that actually break.<\/span><\/p>\n<blockquote><p><b>Quick Answer:<\/b><span style=\"font-weight: 400;\"> Apex is Salesforce\u2019s server-side language: Java-like syntax running inside hard per-transaction limits. Bulkify everything and assume 200 records. One trigger per object, delegating to a handler. Use Queueable as your default async option. Enforce security with <\/span><b>WITH USER_MODE<\/b><span style=\"font-weight: 400;\"> rather than manual FLS checks. And treat 75% coverage as a deployment gate not a quality target.<\/span><\/p><\/blockquote>\n<h2><b>What Is Apex?<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Apex is Salesforce\u2019s proprietary, strongly typed, object-oriented programming language. It runs on Salesforce servers, executes in the context of a database transaction, and has SOQL and DML built directly into the syntax rather than bolted on through a driver.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">You reach for it when the declarative tools can\u2019t express what you need &#8211; complex bulk processing, callouts with retry logic, recursion control, transactional rollback, or logic that has to be unit-tested to a standard Flow can\u2019t reach. See <\/span><span style=\"font-weight: 400;\">Flow vs Apex<\/span><span style=\"font-weight: 400;\"> for where that line sits.<\/span><\/p>\n<h2><b>Apex Looks Like Java. It Doesn\u2019t Behave Like Java.<\/b><\/h2>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-20247\" src=\"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_12_27-PM.webp\" alt=\"Comparison of Java and Apex across resource limits, threading, static state, data access, security, testing, and deployment\" width=\"1536\" height=\"1024\" srcset=\"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_12_27-PM.webp 1536w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_12_27-PM-1024x683.webp 1024w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_12_27-PM-768x512.webp 768w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_12_27-PM-640x427.webp 640w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_12_27-PM-400x267.webp 400w\" sizes=\"auto, (max-width: 1536px) 100vw, 1536px\" \/><\/p>\n<p><i><span style=\"font-weight: 400;\">Comparison of Java and Apex across resource limits, threading, static state, data access, security, testing, and deployment<\/span><\/i><\/p>\n<p><b>The three that catch experienced developers hardest:<\/b><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Static State Resets Every Transaction:<\/b><span style=\"font-weight: 400;\"> In Java, a static variable lives as long as the application. In Apex it\u2019s wiped when the transaction ends. That sounds limiting until you realise it\u2019s exactly what makes the static recursion guard pattern work.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>There Are No Threads:<\/b><span style=\"font-weight: 400;\"> Asynchronous work means Queueable, Batch, Future, or Scheduled &#8211; four specific mechanisms with different rules, not a thread pool you control.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Governor Limits Aren\u2019t Performance Guidance:<\/b><span style=\"font-weight: 400;\"> They\u2019re hard caps. Exceed one and the entire transaction rolls back immediately. There\u2019s no degradation, no warning, and no partial success.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Full reference: <\/span><span style=\"font-weight: 400;\">Salesforce governor limits<\/span><\/p>\n<h2><b>Apex Syntax Essentials<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">public with sharing class AccountService\u00a0<\/span><span style=\"font-weight: 400;\"><br \/>\n<\/span><span style=\"font-weight: 400;\"><br \/>\n<\/span><span style=\"font-weight: 400;\">\u00a0<\/span><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true\"><span style=\"font-weight: 400;\">{<\/span>\r\npublic static void updateRating(List&lt;Account&gt; accounts) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 List&lt;Account&gt; toUpdate = new List&lt;Account&gt;();\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 for (Account acc : accounts) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if (acc.AnnualRevenue &gt; 1000000) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 acc.Rating = 'Hot';\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 toUpdate.add(acc);\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 \u00a0 \u00a0 }\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 if (!toUpdate.isEmpty()) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 update toUpdate;\r\n\u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400;\">Four things in that snippet worth naming:<\/span><\/p>\n<ul>\n<li><b>With sharing:<\/b><span style=\"font-weight: 400;\"> Enforces the running user\u2019s record access. Make it your default. <\/span><span style=\"font-weight: 400;\">without sharing<\/span><span style=\"font-weight: 400;\"> is a deliberate, documented exception &#8211; never a habit.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Collections over singletons:<\/b> <span style=\"font-weight: 400;\">List<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">Set<\/span><span style=\"font-weight: 400;\"> and <\/span><span style=\"font-weight: 400;\">Map<\/span><span style=\"font-weight: 400;\"> are the working vocabulary of Apex. A <\/span><span style=\"font-weight: 400;\">Map&lt;Id, SObject&gt;<\/span><span style=\"font-weight: 400;\"> is the single most useful structure you\u2019ll write.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>DML outside the loop:<\/b><span style=\"font-weight: 400;\"> One <\/span><span style=\"font-weight: 400;\">update<\/span><span style=\"font-weight: 400;\"> on a collection, not 200 updates inside a loop.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>The <\/b><b>isEmpty()<\/b><b> guard:<\/b><span style=\"font-weight: 400;\"> DML on an empty list still consumes a statement against your limit.<\/span><\/li>\n<\/ul>\n<h2><b>Triggers and the Trigger Handler Framework<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">A trigger is Apex that fires on a database event &#8211; before or after insert, update, delete, or undelete.<\/span><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true \">trigger AccountTrigger on Account (before insert, before update,\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 after insert, after update) {\r\n\u00a0 \u00a0 new AccountTriggerHandler().run();\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400;\">That\u2019s the whole trigger, and the logic belongs elsewhere.<\/span><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-20248\" src=\"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_13_48-PM.webp\" alt=\"Four-layer trigger framework showing trigger for routing, handler for event decisions and recursion guard, service for business logic and selector for all SOQL\" width=\"1536\" height=\"1024\" srcset=\"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_13_48-PM.webp 1536w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_13_48-PM-1024x683.webp 1024w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_13_48-PM-768x512.webp 768w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_13_48-PM-640x427.webp 640w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_13_48-PM-400x267.webp 400w\" sizes=\"auto, (max-width: 1536px) 100vw, 1536px\" \/><\/p>\n<p><i><span style=\"font-weight: 400;\">Four-layer trigger framework showing trigger for routing, handler for event decisions and recursion guard, service for business logic and selector for all SOQL<\/span><\/i><\/p>\n<p><b>Why One Trigger Per Object Matters:<\/b><span style=\"font-weight: 400;\"> If you have three triggers on Account, Salesforce decides the order they execute in and doesn\u2019t tell you. You get non-deterministic behaviour that appears only under specific conditions. One trigger, one handler, explicit ordering.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">The recursion guard, which lives in the handler:<\/span><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true \">public class AccountTriggerHandler {\r\n\u00a0 \u00a0 private static Boolean hasRun = false;\r\n\r\n\u00a0 \u00a0 public void run() {\r\n\u00a0 \u00a0 \u00a0 \u00a0 if (hasRun) return;\r\n\u00a0 \u00a0 \u00a0 \u00a0 hasRun = true;\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 if (Trigger.isBefore &amp;&amp; Trigger.isUpdate) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 AccountService.updateRating(Trigger.new);\r\n\u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400;\">This works precisely <\/span><i><span style=\"font-weight: 400;\">because<\/span><\/i><span style=\"font-weight: 400;\"> static state resets between transactions. Without a guard, an update inside a trigger re-fires the trigger, and you get cascading updates that hit governor limits in ways that are genuinely difficult to trace.<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Context Variables You\u2019ll Use Constantly:<\/b> <span style=\"font-weight: 400;\">Trigger.new<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">Trigger.old<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">Trigger.newMap<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">Trigger.oldMap<\/span><span style=\"font-weight: 400;\">, plus <\/span><span style=\"font-weight: 400;\">isBefore<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">isAfter<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">isInsert<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">isUpdate<\/span><span style=\"font-weight: 400;\">.<\/span><\/li>\n<\/ul>\n<p><b>Before or after?<\/b><span style=\"font-weight: 400;\"> Before, for modifying the record that fired the trigger &#8211; no DML needed; the values are written as part of the same save. After for related records and for anything needing the record ID on insert.<\/span><\/p>\n<h2><b>Bulkification: The Single Most Important Habit<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">This is the difference between code that works and code that works in production.<\/span><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true \">\/\/ \u2717 Fails at 100 records\r\nfor (Contact c : Trigger.new) {\r\n\u00a0 \u00a0 Account a = [SELECT Name FROM Account WHERE Id = :c.AccountId];\r\n\u00a0 \u00a0 c.Description = a.Name;\r\n}\r\n\r\n\/\/ \u2713 One query, any volume\r\nSet&lt;Id&gt; accountIds = new Set&lt;Id&gt;();\r\nfor (Contact c : Trigger.new) {\r\n\u00a0 \u00a0 if (c.AccountId != null) accountIds.add(c.AccountId);\r\n}\r\n\r\nMap&lt;Id, Account&gt; accounts = new Map&lt;Id, Account&gt;([\r\n\u00a0 \u00a0 SELECT Id, Name FROM Account WHERE Id IN :accountIds\r\n]);\r\n\r\nfor (Contact c : Trigger.new) {\r\n\u00a0 \u00a0 if (accounts.containsKey(c.AccountId)) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 c.Description = accounts.get(c.AccountId).Name;\r\n\u00a0 \u00a0 }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>The Pattern Never Changes:<\/b><span style=\"font-weight: 400;\"> Collect IDs into a <\/span><span style=\"font-weight: 400;\">Set<\/span><span style=\"font-weight: 400;\">, query once outside the loop into a <\/span><span style=\"font-weight: 400;\">Map<\/span><span style=\"font-weight: 400;\">, loop again to apply. Learn it once, apply it everywhere.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Never Assume One Record:<\/b> <span style=\"font-weight: 400;\">Trigger.new[0]<\/span><span style=\"font-weight: 400;\"> is a bug waiting for a data load. A trigger receives up to 200 records per batch, and Data Loader will find that out for you.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Related: <\/span><a href=\"https:\/\/dianapps.com\/blog\/soql-and-sosl-in-salesforce\/\"><span style=\"font-weight: 400;\">SOQL and SOSL guide<\/span><\/a><\/p>\n<h2><b>The Order of Execution<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">When a record saves, Salesforce runs a defined sequence &#8211; and misunderstanding it causes the most confusing bugs in the platform.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">The simplified order: system validation \u2192 <\/span><b>before triggers<\/b><span style=\"font-weight: 400;\"> \u2192 custom validation rules \u2192 duplicate rules \u2192 <\/span><b>record saved but not committed<\/b><span style=\"font-weight: 400;\"> \u2192 <\/span><b>after triggers<\/b><span style=\"font-weight: 400;\"> \u2192 assignment rules \u2192 auto-response rules \u2192 <\/span><b>workflow rules<\/b><span style=\"font-weight: 400;\"> \u2192 <\/span><b>escalation rules<\/b><span style=\"font-weight: 400;\"> \u2192 <\/span><b>record-triggered Flows (after-save)<\/b><span style=\"font-weight: 400;\"> \u2192 roll-up summaries \u2192 criteria-based sharing \u2192 <\/span><b>commit<\/b><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><b>Two Important Consequences:<\/b><\/p>\n<p><span style=\"font-weight: 400;\">Validation rules run <\/span><i><span style=\"font-weight: 400;\">after<\/span><\/i><span style=\"font-weight: 400;\"> before-triggers, so a before-trigger can set a value that a validation rule then rejects.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Workflow field updates re-fire before and after triggers, which is one of the main reasons recursion guards exist.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">If a field is being set by something and you can\u2019t find what, the order of execution is where to look &#8211; and the culprit is frequently a Flow rather than Apex. See <\/span><a href=\"https:\/\/dianapps.com\/blog\/the-ultimate-guide-to-salesforce-flow\/\"><span style=\"font-weight: 400;\">Salesforce Flow<\/span><\/a><span style=\"font-weight: 400;\">.<\/span><\/p>\n<h2><b>Asynchronous Apex<\/b><\/h2>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-20249\" src=\"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_16_41-PM.webp\" alt=\"Which Asynchronous Apex do you need?\" width=\"1536\" height=\"1024\" srcset=\"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_16_41-PM.webp 1536w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_16_41-PM-1024x683.webp 1024w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_16_41-PM-768x512.webp 768w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_16_41-PM-640x427.webp 640w, https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/ChatGPT-Image-Aug-19-2026-02_16_41-PM-400x267.webp 400w\" sizes=\"auto, (max-width: 1536px) 100vw, 1536px\" \/><\/p>\n<p><i><span style=\"font-weight: 400;\">Comparison of Future, Queueable, Batch and Scheduled Apex showing what each is for and when to use it<\/span><\/i><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Use Queueable by default:<\/b><span style=\"font-weight: 400;\"> It accepts sObjects and complex types as parameters, returns a job ID you can monitor, and chains to the next job. <\/span><span style=\"font-weight: 400;\">@future<\/span><span style=\"font-weight: 400;\"> is the older approach with primitive-only parameters and no chaining &#8211; largely superseded.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Use Batch for volume:<\/b><span style=\"font-weight: 400;\"> Up to 50 million records, processed in chunks of 200, with fresh governor limits per chunk. That last part is why Batch is the answer to \u201cI need to process everything.\u201d<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Use Scheduled to trigger Batch:<\/b><span style=\"font-weight: 400;\"> Scheduled Apex is a timer, not a processor &#8211; the pattern is a Schedulable class that enqueues a Batch job.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">public class AccountUpdateQueueable implements Queueable, <\/span><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true \">Database.AllowsCallouts {\r\n\u00a0 \u00a0 private List&lt;Id&gt; accountIds;\r\n\r\n\u00a0 \u00a0 public AccountUpdateQueueable(List&lt;Id&gt; accountIds) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 this.accountIds = accountIds;\r\n\u00a0 \u00a0 }\r\n\r\n\u00a0 \u00a0 public void execute(QueueableContext context) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \/\/ work here\r\n\u00a0 \u00a0 \u00a0 \u00a0 \/\/ System.enqueueJob(new NextJob());\u00a0 \/\/ chaining\r\n\u00a0 \u00a0 }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p><b>One rule that catches people:<\/b><span style=\"font-weight: 400;\"> you cannot make a callout directly from a trigger. Callouts must be asynchronous &#8211; use Queueable with <\/span><span style=\"font-weight: 400;\">Database.AllowsCallouts<\/span><span style=\"font-weight: 400;\">.<\/span><\/p>\n<h2><b>Exception Handling<\/b><\/h2>\n<pre class=\"theme:github nums:false lang:default decode:true \">try {\r\n\u00a0 \u00a0 update accounts;\r\n} catch (DmlException e) {\r\n\u00a0 \u00a0 Logger.error('Account update failed', e);\r\n\u00a0 \u00a0 throw new AccountServiceException('Unable to update accounts', e);\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400;\">Catch specific exception types, not bare <\/span><span style=\"font-weight: 400;\">Exception<\/span><span style=\"font-weight: 400;\">. <\/span><span style=\"font-weight: 400;\">DmlException<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">QueryException<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">CalloutException<\/span><span style=\"font-weight: 400;\">, <\/span><span style=\"font-weight: 400;\">NullPointerException<\/span><span style=\"font-weight: 400;\"> each tell you something different.<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Never swallow silently:<\/b><span style=\"font-weight: 400;\"> An empty catch block converts a loud failure into a silent data problem, which is strictly worse.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Log with context<\/b><span style=\"font-weight: 400;\">: record IDs, the operation, the user. \u201cUpdate failed\u201d tells the next developer nothing.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Custom exceptions are just a class extending <\/span><span style=\"font-weight: 400;\">Exception,<\/span><span style=\"font-weight: 400;\"> and they make failures readable:<\/span><\/p>\n<p><span style=\"font-weight: 400;\">public class AccountServiceException extends Exception {}<\/span><\/p>\n<p><b>Database.update(records, false)<\/b><span style=\"font-weight: 400;\"> performs a partial save, letting good records through while returning per-record results for the failures. Useful in bulk contexts where one bad record shouldn\u2019t fail 199 good ones.<\/span><\/p>\n<h2><b>Security: What Most Apex Guides Still Get Wrong<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Apex runs in system context by default. It ignores the running user\u2019s object permissions, field-level security, and sharing rules unless you tell it not to.<\/span><\/p>\n<p><b>The modern approach is one clause:<\/b><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true \">List&lt;Account&gt; accts = [\r\n\u00a0 \u00a0 SELECT Id, Name, AnnualRevenue\r\n\u00a0 \u00a0 FROM \u00a0 Account\r\n\u00a0 \u00a0 WITH \u00a0 USER_MODE\r\n];<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400;\">WITH USER_MODE<\/span><span style=\"font-weight: 400;\"> enforces object permissions, field-level security, <\/span><b>and<\/b><span style=\"font-weight: 400;\"> sharing rules in a single clause. It\u2019s clearer and more complete than the older <\/span><span style=\"font-weight: 400;\">WITH SECURITY_ENFORCED<\/span><span style=\"font-weight: 400;\">, which covered CRUD and FLS but not sharing.<\/span><\/p>\n<p><b>For DML<\/b><span style=\"font-weight: 400;\">, use the <\/span><span style=\"font-weight: 400;\">AccessLevel<\/span><span style=\"font-weight: 400;\"> parameter:<\/span><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true \">Database.insert(accounts, AccessLevel.USER_MODE);<\/pre>\n<p>&nbsp;<\/p>\n<p><b>For stripping inaccessible fields<\/b><span style=\"font-weight: 400;\"> rather than throwing:<\/span><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true \">SObjectAccessDecision decision = Security.stripInaccessible(\r\n\u00a0 \u00a0 AccessType.READABLE, accounts);\r\nList&lt;Account&gt; safe = decision.getRecords();<\/pre>\n<p>&nbsp;<\/p>\n<p><b>Class-Level Sharing:<\/b> <span style=\"font-weight: 400;\">with sharing<\/span><span style=\"font-weight: 400;\"> respects record access, <\/span><span style=\"font-weight: 400;\">without sharing<\/span><span style=\"font-weight: 400;\"> ignores it, <\/span><span style=\"font-weight: 400;\">inherited sharing<\/span><span style=\"font-weight: 400;\"> takes the caller\u2019s context. Default to <\/span><span style=\"font-weight: 400;\">with sharing<\/span><span style=\"font-weight: 400;\"> and document any exception.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Related: <\/span><span style=\"font-weight: 400;\">Salesforce compliance and data security<\/span><\/p>\n<h2><b>Testing Beyond the 75% Rule<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Salesforce requires 75% Apex coverage to deploy to production. That\u2019s a gate, not a quality measure &#8211; coverage records which lines executed, not whether your assertions proved anything. A test that runs your code and asserts nothing passes.<\/span><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true \">@isTest\r\nprivate class AccountServiceTest {\r\n\r\n\u00a0 \u00a0 @testSetup\r\n\u00a0 \u00a0 static void setup() {\r\n\u00a0 \u00a0 \u00a0 \u00a0 TestDataFactory.createAccounts(200);\r\n\u00a0 \u00a0 }\r\n\r\n\u00a0 \u00a0 @isTest\r\n\u00a0 \u00a0 static void ratingSetForHighRevenueAccounts() {\r\n\u00a0 \u00a0 \u00a0 \u00a0 List&lt;Account&gt; accounts = [SELECT Id, AnnualRevenue FROM Account];\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 Test.startTest();\r\n\u00a0 \u00a0 \u00a0 \u00a0 AccountService.updateRating(accounts);\r\n\u00a0 \u00a0 \u00a0 \u00a0 Test.stopTest();\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 List&lt;Account&gt; updated = [SELECT Rating FROM Account\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 WHERE AnnualRevenue &gt; 1000000];\r\n\u00a0 \u00a0 \u00a0 \u00a0 Assert.areEqual('Hot', updated[0].Rating,\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 'High-revenue accounts should be rated Hot');\r\n\u00a0 \u00a0 }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p><b>What matters more than coverage:<\/b><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>@testSetup<\/b><span style=\"font-weight: 400;\"> builds shared data once per class rather than per method<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>A test data factory<\/b><span style=\"font-weight: 400;\"> keeps setup consistent and maintainable<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>200+ records<\/b><span style=\"font-weight: 400;\"> in bulk tests, because that\u2019s what production sends<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Negative tests<\/b><span style=\"font-weight: 400;\"> for validation failures and permission denials<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Test.startTest()<\/b><b> \/ <\/b><b>stopTest()<\/b><span style=\"font-weight: 400;\"> resets governor limits and forces async work to complete<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Callout mocks<\/b><span style=\"font-weight: 400;\"> via <\/span><span style=\"font-weight: 400;\">HttpCalloutMock<\/span><span style=\"font-weight: 400;\">, so your tests don\u2019t depend on someone else\u2019s uptime<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Never <\/b><b>SeeAllData=true<\/b><b>.<\/b><span style=\"font-weight: 400;\"> It makes tests dependent on org data, and they\u2019ll fail in a fresh sandbox.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Assert<\/b><b> class methods<\/b><span style=\"font-weight: 400;\"> with meaningful messages &#8211; the newer <\/span><span style=\"font-weight: 400;\">Assert.areEqual()<\/span><span style=\"font-weight: 400;\"> is preferred over <\/span><span style=\"font-weight: 400;\">System.assertEquals()<\/span><\/li>\n<\/ul>\n<div style=\"background: #EEF2FE; border: 1px solid #DBE2FB; border-radius: 14px; padding: 28px 32px; margin: 38px 0;\">\n<h4 style=\"color: #1b3fae; font-size: 22px; line-height: 1.3; font-weight: bold; margin: 0 0 10px;\"><span style=\"font-weight: 400;\"><b>Build Better With Salesforce Development Expertise <\/b><\/span><\/h4>\n<p style=\"color: #4b5563; font-size: 16px; line-height: 1.6; margin: 0 0 22px;\"><span style=\"font-weight: 400;\">Get expert support for Apex development, modernization, testing, and complex Salesforce engineering needs.<\/span><\/p>\n<p><a style=\"display: inline-block; background: #2563EB; color: #ffffff; text-decoration: none; font-size: 15px; font-weight: 600; padding: 13px 26px; border-radius: 8px;\" href=\"https:\/\/dianapps.com\/contact\">Contact Us Now \u2192<\/a><\/p>\n<\/div>\n<h2><b>Apex Anti-Patterns<\/b><\/h2>\n<ol>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>SOQL or DML Inside A Loop<\/b><span style=\"font-weight: 400;\"> &#8211; The most common cause of production failure<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Multiple Triggers On One Object<\/b><span style=\"font-weight: 400;\"> &#8211; Non-deterministic execution order<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Logic In The Trigger Body<\/b><span style=\"font-weight: 400;\"> &#8211; Untestable in isolation<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>No Recursion Guard<\/b><span style=\"font-weight: 400;\"> &#8211; Cascading updates that are hard to trace<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Trigger.new[0]<\/b><span style=\"font-weight: 400;\"> &#8211; Assuming a single record<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Hardcoded IDs<\/b><span style=\"font-weight: 400;\"> &#8211; Works in the sandbox, breaks in production<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Without Sharing<\/b><b> As A Default<\/b><span style=\"font-weight: 400;\"> &#8211; A security decision made by accident<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Empty Catch Blocks<\/b><span style=\"font-weight: 400;\"> &#8211; Silent failure, the worst kind<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>God Classes<\/b><span style=\"font-weight: 400;\"> &#8211; One class doing everything, testable by nobody<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Chasing Coverage<\/b><span style=\"font-weight: 400;\"> Instead of writing assertions<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>SeeAllData=true<\/b><span style=\"font-weight: 400;\"> &#8211; Tests that depend on org data<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Ignoring Null Returns From Get\/Query Results<\/b><span style=\"font-weight: 400;\"> &#8211; A query that finds nothing returns an empty list, not an error<\/span><\/li>\n<\/ol>\n<div style=\"background: #EEF2FE; border: 1px solid #DBE2FB; border-radius: 14px; padding: 28px 32px; margin: 38px 0;\">\n<h4 style=\"color: #1b3fae; font-size: 22px; line-height: 1.3; font-weight: bold; margin: 0 0 10px;\"><span style=\"font-weight: 400;\"><b>Need Salesforce Developers for Your Team? <\/b><\/span><\/h4>\n<p style=\"color: #4b5563; font-size: 16px; line-height: 1.6; margin: 0 0 22px;\"><span style=\"font-weight: 400;\">Add experienced Salesforce developers to your team for ongoing development, technical debt, and project delivery.<\/span><\/p>\n<p><a style=\"display: inline-block; background: #2563EB; color: #ffffff; text-decoration: none; font-size: 15px; font-weight: 600; padding: 13px 26px; border-radius: 8px;\" href=\"https:\/\/dianapps.com\/hire-salesforce-developers\">Hire Salesforce Developers\u00a0\u2192<\/a><\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<h2><b>Apex in 2026: Invocable Methods and Agentforce<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Apex is increasingly the <\/span><i><span style=\"font-weight: 400;\">backend<\/span><\/i><span style=\"font-weight: 400;\"> for declarative and AI features rather than the front line.<\/span><\/p>\n<p><b>Invocable methods<\/b><span style=\"font-weight: 400;\"> expose Apex to Flow and now to Agentforce as custom agent actions:<\/span><\/p>\n<pre class=\"theme:github nums:false lang:default decode:true \">public class OrderActions {\r\n\u00a0 \u00a0 @InvocableMethod(label='Calculate Discount'\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 description='Applies tiered discount logic')\r\n\u00a0 \u00a0 public static List&lt;Decimal&gt; calculateDiscount(List&lt;Id&gt; orderIds) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \/\/ logic\r\n\u00a0 \u00a0 \u00a0 \u00a0 return new List&lt;Decimal&gt;();\r\n\u00a0 \u00a0 }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400;\">That single annotation makes your business logic callable from a Flow, from an Agentforce agent action, and from an LWC without rewriting it. It\u2019s the reason the service-layer pattern matters more now than it did five years ago: logic in a service class is reusable everywhere; logic in a trigger body isn\u2019t.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Related: W<\/span><span style=\"font-weight: 400;\">hat is Agentforce<\/span><span style=\"font-weight: 400;\"> \u00b7 L<\/span><span style=\"font-weight: 400;\">ightning Web Components<\/span><\/p>\n<p>&nbsp;<\/p>\n<h2><b>Where to Go Next<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">For the queries inside your Apex, see the <\/span><a href=\"https:\/\/dianapps.com\/blog\/soql-and-sosl-in-salesforce\/\"><span style=\"font-weight: 400;\">SOQL and SOSL guide<\/span><\/a><span style=\"font-weight: 400;\">. For the limits shaping every design decision, see <\/span><span style=\"font-weight: 400;\">governor limits<\/span><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">For when to use code at all, <\/span><span style=\"font-weight: 400;\">Flow vs Apex<\/span><span style=\"font-weight: 400;\"> and <\/span><span style=\"font-weight: 400;\">configuration vs customization<\/span><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">For the front end calling your Apex, <\/span><span style=\"font-weight: 400;\">Lightning Web Components<\/span><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">For deploying it safely, <\/span><a href=\"https:\/\/dianapps.com\/blog\/salesforce-devops-guide\/\"><span style=\"font-weight: 400;\">Salesforce DevOps<\/span><\/a><span style=\"font-weight: 400;\"> and <\/span><a href=\"https:\/\/dianapps.com\/blog\/salesforce-sandbox-explained\/\"><span style=\"font-weight: 400;\">sandboxes<\/span><\/a><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">The <\/span><a href=\"https:\/\/dianapps.com\/blog\/salesforce-development-guide\/\"><span style=\"font-weight: 400;\">complete Salesforce development guide<\/span><\/a><span style=\"font-weight: 400;\"> is the hub.<\/span><\/p>\n<h2><b>When the Apex Is the Problem<\/b><\/h2>\n<p><b>There\u2019s A Recognisable State an Org Reaches<\/b><span style=\"font-weight: 400;\">: Nobody wants to touch the Apex. Coverage sits just above 75%. Changes break things nobody predicted. The person who wrote it left in 2022.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">That\u2019s remediation work, and it\u2019s routine for our team. Our <\/span><a href=\"https:\/\/dianapps.com\/salesforce-development-services\"><span style=\"font-weight: 400;\">Salesforce Development Services<\/span><\/a><span style=\"font-weight: 400;\"> can help map what the code actually does, add meaningful test coverage, restructure triggers into a proper framework, and leave your team with code they can safely maintain and extend.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">We\u2019re a certified Salesforce Consulting Partner with developers and architects across the US, UK, Australia, Canada, UAE, and India.<\/span><\/p>\n<div style=\"background: #EEF2FE; border: 1px solid #DBE2FB; border-radius: 14px; padding: 28px 32px; margin: 38px 0;\">\n<h4 style=\"color: #1b3fae; font-size: 22px; line-height: 1.3; font-weight: bold; margin: 0 0 10px;\"><span style=\"font-weight: 400;\"><b>Have an Apex Challenge to Solve? <\/b><\/span><\/h4>\n<p style=\"color: #4b5563; font-size: 16px; line-height: 1.6; margin: 0 0 22px;\"><span style=\"font-weight: 400;\">Tell us what you\u2019re working on and our Salesforce experts will help you determine the right next step.<\/span><\/p>\n<p><a style=\"display: inline-block; background: #2563EB; color: #ffffff; text-decoration: none; font-size: 15px; font-weight: 600; padding: 13px 26px; border-radius: 8px;\" href=\"https:\/\/dianapps.com\/contact\">Contact Our Salesforce Experts \u2192<\/a><\/p>\n<\/div>\n<style>.elementor-20242 .elementor-element.elementor-element-2932a52{text-align:left;}.elementor-20242 .elementor-element.elementor-element-2932a52 > .elementor-widget-container{margin:0px 0px 0px 0px;}.elementor-20242 .elementor-element.elementor-element-0b767d1 .elementor-tab-title{border-width:1px;border-color:#00000014;}.elementor-20242 .elementor-element.elementor-element-0b767d1 .elementor-tab-content{border-width:1px;border-bottom-color:#00000014;}.elementor-20242 .elementor-element.elementor-element-0b767d1 > .elementor-widget-container{margin:0px 0px 0px 0px;}<\/style><div class=\"porto-block elementor elementor-20242\">\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-27707ca elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"27707ca\" data-element_type=\"section\">\r\n\t\t\t\r\n\t\t\t\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\r\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-0163611\" data-id=\"0163611\" data-element_type=\"column\">\r\n\r\n\t\t\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\r\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-03a2969 elementor-widget elementor-widget-text-editor\" data-id=\"03a2969\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<style>\/*! elementor - v3.14.0 - 26-06-2023 *\/\n.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:#69727d;color:#fff}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap{color:#69727d;border:3px solid;background-color:transparent}.elementor-widget-text-editor:not(.elementor-drop-cap-view-default) .elementor-drop-cap{margin-top:8px}.elementor-widget-text-editor:not(.elementor-drop-cap-view-default) .elementor-drop-cap-letter{width:1em;height:1em}.elementor-widget-text-editor .elementor-drop-cap{float:left;text-align:center;line-height:1;font-size:50px}.elementor-widget-text-editor .elementor-drop-cap-letter{display:inline-block}<\/style>\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2932a52 elementor-widget elementor-widget-heading\" data-id=\"2932a52\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<style>\/*! elementor - v3.14.0 - 26-06-2023 *\/\n.elementor-heading-title{padding:0;margin:0;line-height:1}.elementor-widget-heading .elementor-heading-title[class*=elementor-size-]>a{color:inherit;font-size:inherit;line-height:inherit}.elementor-widget-heading .elementor-heading-title.elementor-size-small{font-size:15px}.elementor-widget-heading .elementor-heading-title.elementor-size-medium{font-size:19px}.elementor-widget-heading .elementor-heading-title.elementor-size-large{font-size:29px}.elementor-widget-heading .elementor-heading-title.elementor-size-xl{font-size:39px}.elementor-widget-heading .elementor-heading-title.elementor-size-xxl{font-size:59px}<\/style><h2 class=\"elementor-heading-title elementor-size-large\">FAQs <\/h2>\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0b767d1 elementor-widget elementor-widget-toggle\" data-id=\"0b767d1\" data-element_type=\"widget\" data-widget_type=\"toggle.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<style>\/*! elementor - v3.14.0 - 26-06-2023 *\/\n.elementor-toggle{text-align:left}.elementor-toggle .elementor-tab-title{font-weight:700;line-height:1;margin:0;padding:15px;border-bottom:1px solid #d5d8dc;cursor:pointer;outline:none}.elementor-toggle .elementor-tab-title .elementor-toggle-icon{display:inline-block;width:1em}.elementor-toggle .elementor-tab-title .elementor-toggle-icon svg{-webkit-margin-start:-5px;margin-inline-start:-5px;width:1em;height:1em}.elementor-toggle .elementor-tab-title .elementor-toggle-icon.elementor-toggle-icon-right{float:right;text-align:right}.elementor-toggle .elementor-tab-title .elementor-toggle-icon.elementor-toggle-icon-left{float:left;text-align:left}.elementor-toggle .elementor-tab-title .elementor-toggle-icon .elementor-toggle-icon-closed{display:block}.elementor-toggle .elementor-tab-title .elementor-toggle-icon .elementor-toggle-icon-opened{display:none}.elementor-toggle .elementor-tab-title.elementor-active{border-bottom:none}.elementor-toggle .elementor-tab-title.elementor-active .elementor-toggle-icon-closed{display:none}.elementor-toggle .elementor-tab-title.elementor-active .elementor-toggle-icon-opened{display:block}.elementor-toggle .elementor-tab-content{padding:15px;border-bottom:1px solid #d5d8dc;display:none}@media (max-width:767px){.elementor-toggle .elementor-tab-title{padding:12px}.elementor-toggle .elementor-tab-content{padding:12px 10px}}.e-con-inner>.elementor-widget-toggle,.e-con>.elementor-widget-toggle{width:var(--container-widget-width);--flex-grow:var(--container-widget-flex-grow)}<\/style>\t\t<div class=\"elementor-toggle\">\n\t\t\t\t\t\t\t<div class=\"elementor-toggle-item\">\n\t\t\t\t\t<h3 id=\"elementor-tab-title-1201\" class=\"elementor-tab-title\" data-tab=\"1\" role=\"button\" aria-controls=\"elementor-tab-content-1201\" aria-expanded=\"false\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon elementor-toggle-icon-left\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-closed\"><i class=\"fas fa-caret-right\"><\/i><\/span>\n\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-opened\"><i class=\"elementor-toggle-icon-opened fas fa-caret-up\"><\/i><\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-toggle-title\" tabindex=\"0\">What is Apex in Salesforce?<\/a>\n\t\t\t\t\t<\/h3>\n\n\t\t\t\t\t<div id=\"elementor-tab-content-1201\" class=\"elementor-tab-content elementor-clearfix\" data-tab=\"1\" role=\"region\" aria-labelledby=\"elementor-tab-title-1201\"><p><span style=\"font-weight: 400;\">Salesforce\u2019s proprietary server-side programming language &#8211; strongly typed, object-oriented, with Java-like syntax and SOQL and DML built into the language. It runs on Salesforce servers inside strict per-transaction governor limits and handles logic the declarative tools can\u2019t express.<\/span><\/p><\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-toggle-item\">\n\t\t\t\t\t<h3 id=\"elementor-tab-title-1202\" class=\"elementor-tab-title\" data-tab=\"2\" role=\"button\" aria-controls=\"elementor-tab-content-1202\" aria-expanded=\"false\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon elementor-toggle-icon-left\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-closed\"><i class=\"fas fa-caret-right\"><\/i><\/span>\n\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-opened\"><i class=\"elementor-toggle-icon-opened fas fa-caret-up\"><\/i><\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-toggle-title\" tabindex=\"0\">Is Apex the same as Java?<\/a>\n\t\t\t\t\t<\/h3>\n\n\t\t\t\t\t<div id=\"elementor-tab-content-1202\" class=\"elementor-tab-content elementor-clearfix\" data-tab=\"2\" role=\"region\" aria-labelledby=\"elementor-tab-title-1202\"><p><span style=\"font-weight: 400;\">The syntax is similar, but the behaviour isn\u2019t. Apex has hard per-transaction resource limits, no threading, static state that resets each transaction, built-in database access, a runtime-enforced sharing model and mandatory test coverage before deployment.<\/span><\/p><\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-toggle-item\">\n\t\t\t\t\t<h3 id=\"elementor-tab-title-1203\" class=\"elementor-tab-title\" data-tab=\"3\" role=\"button\" aria-controls=\"elementor-tab-content-1203\" aria-expanded=\"false\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon elementor-toggle-icon-left\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-closed\"><i class=\"fas fa-caret-right\"><\/i><\/span>\n\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-opened\"><i class=\"elementor-toggle-icon-opened fas fa-caret-up\"><\/i><\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-toggle-title\" tabindex=\"0\">How do I write an Apex trigger?<\/a>\n\t\t\t\t\t<\/h3>\n\n\t\t\t\t\t<div id=\"elementor-tab-content-1203\" class=\"elementor-tab-content elementor-clearfix\" data-tab=\"3\" role=\"region\" aria-labelledby=\"elementor-tab-title-1203\"><p><span style=\"font-weight: 400;\">Create one trigger per object containing no logic, just a call to a handler class. Put event routing and the recursion guard in the handler, business logic in a service class, and SOQL in a selector class. Always write for 200 records, never one.<\/span><\/p><\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-toggle-item\">\n\t\t\t\t\t<h3 id=\"elementor-tab-title-1204\" class=\"elementor-tab-title\" data-tab=\"4\" role=\"button\" aria-controls=\"elementor-tab-content-1204\" aria-expanded=\"false\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon elementor-toggle-icon-left\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-closed\"><i class=\"fas fa-caret-right\"><\/i><\/span>\n\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-opened\"><i class=\"elementor-toggle-icon-opened fas fa-caret-up\"><\/i><\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-toggle-title\" tabindex=\"0\">What is bulkification in Apex?<\/a>\n\t\t\t\t\t<\/h3>\n\n\t\t\t\t\t<div id=\"elementor-tab-content-1204\" class=\"elementor-tab-content elementor-clearfix\" data-tab=\"4\" role=\"region\" aria-labelledby=\"elementor-tab-title-1204\"><p><span style=\"font-weight: 400;\">Writing code that processes collections rather than individual records. Collect IDs into a <\/span><span style=\"font-weight: 400;\">Set<\/span><span style=\"font-weight: 400;\">, query once outside the loop into a <\/span><span style=\"font-weight: 400;\">Map<\/span><span style=\"font-weight: 400;\">, then loop to apply. Queries or DML inside a loop are the most common cause of governor limit failures in production.<\/span><\/p><\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-toggle-item\">\n\t\t\t\t\t<h3 id=\"elementor-tab-title-1205\" class=\"elementor-tab-title\" data-tab=\"5\" role=\"button\" aria-controls=\"elementor-tab-content-1205\" aria-expanded=\"false\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon elementor-toggle-icon-left\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-closed\"><i class=\"fas fa-caret-right\"><\/i><\/span>\n\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-opened\"><i class=\"elementor-toggle-icon-opened fas fa-caret-up\"><\/i><\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-toggle-title\" tabindex=\"0\">What\u2019s the difference between Queueable and Batch Apex?<\/a>\n\t\t\t\t\t<\/h3>\n\n\t\t\t\t\t<div id=\"elementor-tab-content-1205\" class=\"elementor-tab-content elementor-clearfix\" data-tab=\"5\" role=\"region\" aria-labelledby=\"elementor-tab-title-1205\"><p><span style=\"font-weight: 400;\">Queueable handles chainable asynchronous work with sObject parameters and a monitorable job Id &#8211; the default choice for async. Batch processes up to 50 million records in chunks of 200, with fresh governor limits per chunk, making it the option for large data volumes.<\/span><\/p><\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-toggle-item\">\n\t\t\t\t\t<h3 id=\"elementor-tab-title-1206\" class=\"elementor-tab-title\" data-tab=\"6\" role=\"button\" aria-controls=\"elementor-tab-content-1206\" aria-expanded=\"false\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon elementor-toggle-icon-left\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-closed\"><i class=\"fas fa-caret-right\"><\/i><\/span>\n\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-opened\"><i class=\"elementor-toggle-icon-opened fas fa-caret-up\"><\/i><\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-toggle-title\" tabindex=\"0\">Why does Salesforce require 75% test coverage?<\/a>\n\t\t\t\t\t<\/h3>\n\n\t\t\t\t\t<div id=\"elementor-tab-content-1206\" class=\"elementor-tab-content elementor-clearfix\" data-tab=\"6\" role=\"region\" aria-labelledby=\"elementor-tab-title-1206\"><p><span style=\"font-weight: 400;\">Because your code runs on shared infrastructure and Salesforce needs assurance it won\u2019t destabilise the platform during upgrades. It\u2019s a deployment gate, not a quality measure &#8211; coverage records which lines ran, not whether the assertions proved anything meaningful.<\/span><\/p><\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-toggle-item\">\n\t\t\t\t\t<h3 id=\"elementor-tab-title-1207\" class=\"elementor-tab-title\" data-tab=\"7\" role=\"button\" aria-controls=\"elementor-tab-content-1207\" aria-expanded=\"false\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon elementor-toggle-icon-left\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-closed\"><i class=\"fas fa-caret-right\"><\/i><\/span>\n\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-opened\"><i class=\"elementor-toggle-icon-opened fas fa-caret-up\"><\/i><\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-toggle-title\" tabindex=\"0\">How do I enforce field-level security in Apex?<\/a>\n\t\t\t\t\t<\/h3>\n\n\t\t\t\t\t<div id=\"elementor-tab-content-1207\" class=\"elementor-tab-content elementor-clearfix\" data-tab=\"7\" role=\"region\" aria-labelledby=\"elementor-tab-title-1207\"><p><span style=\"font-weight: 400;\">Add <\/span><span style=\"font-weight: 400;\">WITH USER_MODE<\/span><span style=\"font-weight: 400;\"> to your SOQL, which enforces object permissions, field-level security, and sharing rules in one clause. For DML, use <\/span><span style=\"font-weight: 400;\">Database.insert(records, AccessLevel.USER_MODE)<\/span><span style=\"font-weight: 400;\">. <\/span><span style=\"font-weight: 400;\">Security.stripInaccessible()<\/span><span style=\"font-weight: 400;\"> removes inaccessible fields rather than throwing an exception.<\/span><\/p><\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-toggle-item\">\n\t\t\t\t\t<h3 id=\"elementor-tab-title-1208\" class=\"elementor-tab-title\" data-tab=\"8\" role=\"button\" aria-controls=\"elementor-tab-content-1208\" aria-expanded=\"false\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon elementor-toggle-icon-left\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-closed\"><i class=\"fas fa-caret-right\"><\/i><\/span>\n\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-opened\"><i class=\"elementor-toggle-icon-opened fas fa-caret-up\"><\/i><\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-toggle-title\" tabindex=\"0\">What is a recursion guard in Apex?<\/a>\n\t\t\t\t\t<\/h3>\n\n\t\t\t\t\t<div id=\"elementor-tab-content-1208\" class=\"elementor-tab-content elementor-clearfix\" data-tab=\"8\" role=\"region\" aria-labelledby=\"elementor-tab-title-1208\"><p><span style=\"font-weight: 400;\">A static Boolean in the trigger handler that prevents the trigger from re-firing when its own updates trigger it again. It works because static variables reset at the end of each transaction, so the guard is fresh for every new save.<\/span><\/p><\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-toggle-item\">\n\t\t\t\t\t<h3 id=\"elementor-tab-title-1209\" class=\"elementor-tab-title\" data-tab=\"9\" role=\"button\" aria-controls=\"elementor-tab-content-1209\" aria-expanded=\"false\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon elementor-toggle-icon-left\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-closed\"><i class=\"fas fa-caret-right\"><\/i><\/span>\n\t\t\t\t\t\t\t\t<span class=\"elementor-toggle-icon-opened\"><i class=\"elementor-toggle-icon-opened fas fa-caret-up\"><\/i><\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"elementor-toggle-title\" tabindex=\"0\">Can Apex make callouts from a trigger?<\/a>\n\t\t\t\t\t<\/h3>\n\n\t\t\t\t\t<div id=\"elementor-tab-content-1209\" class=\"elementor-tab-content elementor-clearfix\" data-tab=\"9\" role=\"region\" aria-labelledby=\"elementor-tab-title-1209\"><p><span style=\"font-weight: 400;\">Not directly. Callouts must be asynchronous when initiated from a trigger &#8211; use a Queueable class implementing <\/span><span style=\"font-weight: 400;\">Database.AllowsCallouts<\/span><span style=\"font-weight: 400;\">, enqueued from the trigger handler.<\/span><\/p><\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t\t\t<script type=\"application\/ld+json\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@type\":\"FAQPage\",\"mainEntity\":[{\"@type\":\"Question\",\"name\":\"What is Apex in Salesforce?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"<p><span style=\\\"font-weight: 400;\\\">Salesforce\\u2019s proprietary server-side programming language &#8211; strongly typed, object-oriented, with Java-like syntax and SOQL and DML built into the language. It runs on Salesforce servers inside strict per-transaction governor limits and handles logic the declarative tools can\\u2019t express.<\\\/span><\\\/p>\"}},{\"@type\":\"Question\",\"name\":\"Is Apex the same as Java?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"<p><span style=\\\"font-weight: 400;\\\">The syntax is similar, but the behaviour isn\\u2019t. Apex has hard per-transaction resource limits, no threading, static state that resets each transaction, built-in database access, a runtime-enforced sharing model and mandatory test coverage before deployment.<\\\/span><\\\/p>\"}},{\"@type\":\"Question\",\"name\":\"How do I write an Apex trigger?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"<p><span style=\\\"font-weight: 400;\\\">Create one trigger per object containing no logic, just a call to a handler class. Put event routing and the recursion guard in the handler, business logic in a service class, and SOQL in a selector class. Always write for 200 records, never one.<\\\/span><\\\/p>\"}},{\"@type\":\"Question\",\"name\":\"What is bulkification in Apex?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"<p><span style=\\\"font-weight: 400;\\\">Writing code that processes collections rather than individual records. Collect IDs into a <\\\/span><span style=\\\"font-weight: 400;\\\">Set<\\\/span><span style=\\\"font-weight: 400;\\\">, query once outside the loop into a <\\\/span><span style=\\\"font-weight: 400;\\\">Map<\\\/span><span style=\\\"font-weight: 400;\\\">, then loop to apply. Queries or DML inside a loop are the most common cause of governor limit failures in production.<\\\/span><\\\/p>\"}},{\"@type\":\"Question\",\"name\":\"What\\u2019s the difference between Queueable and Batch Apex?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"<p><span style=\\\"font-weight: 400;\\\">Queueable handles chainable asynchronous work with sObject parameters and a monitorable job Id &#8211; the default choice for async. Batch processes up to 50 million records in chunks of 200, with fresh governor limits per chunk, making it the option for large data volumes.<\\\/span><\\\/p>\"}},{\"@type\":\"Question\",\"name\":\"Why does Salesforce require 75% test coverage?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"<p><span style=\\\"font-weight: 400;\\\">Because your code runs on shared infrastructure and Salesforce needs assurance it won\\u2019t destabilise the platform during upgrades. It\\u2019s a deployment gate, not a quality measure &#8211; coverage records which lines ran, not whether the assertions proved anything meaningful.<\\\/span><\\\/p>\"}},{\"@type\":\"Question\",\"name\":\"How do I enforce field-level security in Apex?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"<p><span style=\\\"font-weight: 400;\\\">Add <\\\/span><span style=\\\"font-weight: 400;\\\">WITH USER_MODE<\\\/span><span style=\\\"font-weight: 400;\\\"> to your SOQL, which enforces object permissions, field-level security, and sharing rules in one clause. For DML, use <\\\/span><span style=\\\"font-weight: 400;\\\">Database.insert(records, AccessLevel.USER_MODE)<\\\/span><span style=\\\"font-weight: 400;\\\">. <\\\/span><span style=\\\"font-weight: 400;\\\">Security.stripInaccessible()<\\\/span><span style=\\\"font-weight: 400;\\\"> removes inaccessible fields rather than throwing an exception.<\\\/span><\\\/p>\"}},{\"@type\":\"Question\",\"name\":\"What is a recursion guard in Apex?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"<p><span style=\\\"font-weight: 400;\\\">A static Boolean in the trigger handler that prevents the trigger from re-firing when its own updates trigger it again. It works because static variables reset at the end of each transaction, so the guard is fresh for every new save.<\\\/span><\\\/p>\"}},{\"@type\":\"Question\",\"name\":\"Can Apex make callouts from a trigger?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"<p><span style=\\\"font-weight: 400;\\\">Not directly. Callouts must be asynchronous when initiated from a trigger &#8211; use a Queueable class implementing <\\\/span><span style=\\\"font-weight: 400;\\\">Database.AllowsCallouts<\\\/span><span style=\\\"font-weight: 400;\\\">, enqueued from the trigger handler.<\\\/span><\\\/p>\"}}]}<\/script>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t\t\t<\/div>\r\n\t\t\t\t<\/section>\r\n\t\t<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Apex is easy to learn and hard to write well, as the syntax takes about a week while the constraints take a quarter. Most Apex problems in production aren\u2019t syntax errors; they\u2019re code that worked perfectly against ten records and collapsed against fifty thousand, or a trigger that fires twice because nobody added a recursion [&hellip;]<\/p>\n","protected":false},"author":15,"featured_media":20250,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_yoast_wpseo_meta-robots-noindex":"","_yoast_wpseo_meta-robots-nofollow":"","_yoast_wpseo_canonical":"","_yoast_wpseo_opengraph-title":"","_yoast_wpseo_opengraph-description":"","_yoast_wpseo_opengraph-image":"","_yoast_wpseo_twitter-title":"","_yoast_wpseo_twitter-description":"","_yoast_wpseo_twitter-image":"","_wp_applaud_exclude":false,"footnotes":""},"categories":[85],"tags":[2606,2607],"class_list":["post-20233","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-salesforce","tag-apex-programming-guide","tag-what-is-apex"],"featured_image_src":{"landsacpe":["https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80-1140x445.webp",1140,445,true],"list":["https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80-463x348.webp",463,348,true],"medium":["https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80-300x169.webp",300,169,true],"full":["https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80.webp",1536,864,false]},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Apex Programming Guide: Triggers, Testing and Best Practices<\/title>\n<meta name=\"description\" content=\"Apex programming guide covering triggers, bulkification, Queueable Apex, testing, governor limits, security, and best practices for scalable Salesforce development.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/dianapps.com\/blog\/apex-programming-guide\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Apex Programming Guide: Triggers, Testing and Best Practices\" \/>\n<meta property=\"og:description\" content=\"Apex programming guide covering triggers, bulkification, Queueable Apex, testing, governor limits, security, and best practices for scalable Salesforce development.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/dianapps.com\/blog\/apex-programming-guide\/\" \/>\n<meta property=\"og:site_name\" content=\"Learn About Digital Transformation &amp; Development | DianApps Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-19T09:15:47+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-19T11:03:02+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"1536\" \/>\n\t<meta property=\"og:image:height\" content=\"864\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/webp\" \/>\n<meta name=\"author\" content=\"Vaibhav Sharma\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Vaibhav Sharma\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Apex Programming Guide: Triggers, Testing and Best Practices","description":"Apex programming guide covering triggers, bulkification, Queueable Apex, testing, governor limits, security, and best practices for scalable Salesforce development.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/","og_locale":"en_US","og_type":"article","og_title":"Apex Programming Guide: Triggers, Testing and Best Practices","og_description":"Apex programming guide covering triggers, bulkification, Queueable Apex, testing, governor limits, security, and best practices for scalable Salesforce development.","og_url":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/","og_site_name":"Learn About Digital Transformation &amp; Development | DianApps Blog","article_published_time":"2026-08-19T09:15:47+00:00","article_modified_time":"2026-08-19T11:03:02+00:00","og_image":[{"width":1536,"height":864,"url":"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80.webp","type":"image\/webp"}],"author":"Vaibhav Sharma","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Vaibhav Sharma","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/#article","isPartOf":{"@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/"},"author":{"name":"Vaibhav Sharma","@id":"https:\/\/dianapps.com\/blog\/#\/schema\/person\/e0ee1e2a6cfb8681a3cc69154f8d8bcf"},"headline":"Apex Programming Guide: Triggers, Testing and Best Practices","datePublished":"2026-08-19T09:15:47+00:00","dateModified":"2026-08-19T11:03:02+00:00","mainEntityOfPage":{"@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/"},"wordCount":1889,"commentCount":0,"image":{"@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80.webp","keywords":["Apex Programming Guide","what is apex"],"articleSection":["Salesforce"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/dianapps.com\/blog\/apex-programming-guide\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/","url":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/","name":"Apex Programming Guide: Triggers, Testing and Best Practices","isPartOf":{"@id":"https:\/\/dianapps.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/#primaryimage"},"image":{"@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80.webp","datePublished":"2026-08-19T09:15:47+00:00","dateModified":"2026-08-19T11:03:02+00:00","author":{"@id":"https:\/\/dianapps.com\/blog\/#\/schema\/person\/e0ee1e2a6cfb8681a3cc69154f8d8bcf"},"description":"Apex programming guide covering triggers, bulkification, Queueable Apex, testing, governor limits, security, and best practices for scalable Salesforce development.","breadcrumb":{"@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/dianapps.com\/blog\/apex-programming-guide\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/#primaryimage","url":"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80.webp","contentUrl":"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/08\/Untitled-design-80.webp","width":1536,"height":864,"caption":"Apex Programming guide"},{"@type":"BreadcrumbList","@id":"https:\/\/dianapps.com\/blog\/apex-programming-guide\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/dianapps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Apex Programming Guide: Triggers, Testing and Best Practices"}]},{"@type":"WebSite","@id":"https:\/\/dianapps.com\/blog\/#website","url":"https:\/\/dianapps.com\/blog\/","name":"Learn About Digital Transformation &amp; Development | DianApps Blog","description":"Dianapps","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/dianapps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/dianapps.com\/blog\/#\/schema\/person\/e0ee1e2a6cfb8681a3cc69154f8d8bcf","name":"Vaibhav Sharma","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-Vaibhav-Sharma-Salesforce-Specialist-96x96.jpg","url":"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-Vaibhav-Sharma-Salesforce-Specialist-96x96.jpg","contentUrl":"https:\/\/dianapps.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-Vaibhav-Sharma-Salesforce-Specialist-96x96.jpg","caption":"Vaibhav Sharma"},"description":"A Salesforce specialist with over 15 years in the field, Vaibhav Sharma has built his career at the intersection of CRM strategy and enterprise transformation. As the driving force behind Salesforce innovation at DianApps, he architects solutions across Sales Cloud, Service Cloud, and Experience Cloud that turn fragmented customer data into unified growth engines. His work spans a 360-degree perspective in BFSI, healthcare, and retail, where he's known for engineering platforms that don't just go live, they move revenue and retention numbers. Vaibhav's edge lies in reading where Salesforce is headed next, from AI-powered automation, AgentExchange to Agentforce, and translating that foresight into roadmaps enterprise leaders can act on today.","url":"https:\/\/dianapps.com\/blog\/author\/vaibhav-sharma\/"}]}},"_links":{"self":[{"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/posts\/20233","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/comments?post=20233"}],"version-history":[{"count":3,"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/posts\/20233\/revisions"}],"predecessor-version":[{"id":20280,"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/posts\/20233\/revisions\/20280"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/media\/20250"}],"wp:attachment":[{"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/media?parent=20233"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/categories?post=20233"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dianapps.com\/blog\/wp-json\/wp\/v2\/tags?post=20233"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}