Debugging & Static Analysis
Free, step-through debugging using a captured debug log:
37.1 Debug logs
System.debug(LoggingLevel.WARN, 'message') writes to the debug log.
Enable logging with trace flags (*Setup → Debug Logs*, or sf apex tail log, or Developer Console automatically for your user).
Log levels per category: NONE, ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST.
Categories: Apex Code, Apex Profiling, Callout, Database, System, Validation, Visualforce, Workflow, NBA, Wave.
Limits: each log is capped at 20 MB (older content is truncated); logs are kept for 24 hours by default; the org can store up to 1,000 MB of logs.
Read logs with the Developer Console Log Inspector (Execution Overview, Timeline, Stack Tree) or VS Code (*SFDX: Get Apex Debug Logs*). Look for CUMULATIVE_LIMIT_USAGE, SOQL_EXECUTE_BEGIN, DML_BEGIN, EXCEPTION_THROWN, FATAL_ERROR.
sf apex tail log --color # stream logs for your user
sf apex list log
sf apex get log --log-id 07Lxx000000000137.2 Apex Replay Debugger (VS Code)
Free, step-through debugging using a captured debug log:
*SFDX: Turn On Apex Debug Log for Replay Debugger* (sets a FINEST trace flag).
Set breakpoints; optionally add checkpoints (*SFDX: Toggle Checkpoint*, up to 5) and *SFDX: Update Checkpoints in Org* to capture heap snapshots.
Reproduce the issue (UI action, test, or anonymous Apex).
*SFDX: Get Apex Debug Logs*, open the log, right-click → *SFDX: Launch Apex Replay Debugger with Current File*.
Step through code, inspect variables and the call stack.
Other debuggers: the Interactive Apex Debugger (live breakpoints in sandboxes/scratch orgs; requires debugger licenses/sessions) and the ISV Customer Debugger for managed-package code in subscriber sandboxes.
37.3 Common errors and fixes
System.LimitException: Too many SOQL queries: 101 SOQL in a loop / recursion Bulkify; recursion guard
System.NullPointerException: Attempt to de-reference a null object
Uninitialized variable or missing map key Initialize; use ?., ??, containsKey
System.QueryException: List has no rows for assignment to SObject Single-row query returned 0 rows Assign to a list and check isEmpty()
SObject row was retrieved via SOQL without querying the requested field Field missing from SELECT
Setup + non-setup DML in one transaction @future/Queueable, System.runAs in tests
Concurrent updates to same/parent records Reduce batch size, order updates, FOR UPDATE, retry You have uncommitted work pending Callout after DML Callout first or go async Apex CPU time limit exceeded
Nested loops, heavy processing, recursion Maps instead of nested loops; async Maximum trigger depth exceeded
Non-selective query against large object type Unindexed filter on 200k+ rows in a trigger Selective filters, custom index
37.4 Static analysis with PMD and Salesforce Code Analyzer
PMD includes an Apex rule set; Salesforce Code Analyzer wraps PMD, ESLint, RetireJS, a security-focused data-flow Graph Engine, and more:
Important Apex PMD rules: ApexCRUDViolation, ApexSharingViolations, ApexSOQLInjection, ApexOpenRedirect, ApexXSSFromURLParam, OperationWithLimitsInLoop (formerly AvoidSoqlInLoops/AvoidDmlStatementsInLoops), AvoidHardcodingId, ApexUnitTestClassShouldHaveAsserts, ApexUnitTestShouldNotUseSeeAllDataTrue, CyclomaticComplexity, CognitiveComplexity, EmptyCatchBlock, AvoidGlobalModifier. Configure a custom ruleset XML and run it in CI to block merges on violations. The VS Code Apex PMD extension highlights issues as you type.
# Code Analyzer v5 (GA 2025)
sf plugins install code-analyzer
sf code-analyzer run --target force-app --output-file results.html
# Older v4 plugin syntax (still seen in many pipelines)
sf scanner run --target force-app --format table --engine pmd