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 07Lxx0000000001

37.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