How-to

How to debug Apex with breakpoints

Not checkpoints in a downloaded log — breakpoints, in a running program. Set one in a class or a trigger, run the test, and execution pauses there while you read the variables. Works in VS Code and IntelliJ, on your machine, with no org in the loop.

Short version

Install the Nimbus extension for VS Code or the plugin for JetBrains IDEs, click the gutter next to the line you care about, and start a debug session on a test. The editor's normal debug UI — variables, call stack, step over / in / out — works against Apex the way it does against every other language.

What the platform gives you

Two org-based options, and it is worth being precise about what each one is.

  • Apex Replay Debugger. Free, and not a live debugger. You enable debug logging, deploy, run the code, download the log, load it into the Replay Debugger, and set checkpoints to replay against the recording. It is a log viewer with a debugger's interface. Every code change invalidates the log and you do the whole loop again — typically five to ten minutes per session.
  • Apex Interactive Debugger. A genuine live debugger, licensed separately, and limited to sandboxes and scratch orgs. If your team has it, it is the real thing; most teams do not.

The fallback, then, is System.debug(): add a line, deploy, run, read the log, repeat. That works, and it costs a deploy per question.

What live local debugging changes

Nimbus runs Apex on your machine, so the debugger is a debugger on a running process, not a reader of something that already finished. The loop is:

  1. Set a breakpoint in your editor (click the gutter)
  2. Run the debug session (F5)
  3. Execution pauses at your breakpoint
  4. Inspect locals, step through code
  5. Fix, save, re-run

No debug-level configuration, no log download, no checkpoint dance — and under five seconds from save to paused at a breakpoint. What you get at the pause:

  • Breakpoints on any line — in triggers, service classes, test methods, anywhere.
  • Step over to move statement by statement without descending into calls.
  • Step in to follow a call into a handler or utility method.
  • Step out to return to the caller and resume.
  • Variables — every local in scope: primitives, SObjects, collections, class instances, expandable to any depth.
  • Call stack — how execution reached this line, with line numbers; click any frame to inspect its scope.

VS Code

Install the Nimbus extension from the Marketplace. It connects to the Nimbus daemon in your project and enables breakpoints, step controls, and variable inspection in the built-in Debug panel. Open a test class, click the gutter, then use Run & Debug or press F5 and pick the test method.

If you want the configuration checked into the repo:

json
// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Nimbus: Debug Test",
      "type": "nimbus",
      "request": "launch",
      "test": "${input:testMethod}",
      "stopOnEntry": false
    },
    {
      "name": "Nimbus: Debug Current Test",
      "type": "nimbus",
      "request": "launch",
      "test": "${file}",
      "stopOnEntry": false
    }
  ],
  "inputs": [
    {
      "id": "testMethod",
      "type": "promptString",
      "description": "Test to debug (ClassName.methodName)",
      "default": "AccountServiceTest.testCreateAccount"
    }
  ]
}

Coverage gutter decorations update after each run, in the editor margin, so the lines you never reached are visible without opening a report.

IntelliJ and other JetBrains IDEs

Install the Nimbus plugin from the JetBrains Marketplace. It drives IntelliJ's built-in debugger UI — the same panels you use for Java. Set breakpoints in the gutter, right-click a test method and choose Debug with Nimbus, or use the debug run configuration. Variables, call stack, and stepping all behave as expected.

It works alongside Illuminated Cloud: keep that for editing and deployment, and let Nimbus handle the local run and debug loop.

Under the hood: it is just DAP

The editor integrations are clients of nimbus dap, a Debug Adapter Protocol server that reads DAP messages on stdin and writes to stdout. It is meant to be launched by an IDE rather than typed into a terminal — but because it speaks a standard protocol, any DAP-capable editor can drive it.

The launch arguments are the whole configuration surface: program (a test pattern such as "CalculatorTest.addsPositive" or "*Test"), projectPath (defaults to the working directory), orgAlias, and stopOnEntry to pause at the first statement.

A typical session

A test fails with an unexpected null. Instead of adding System.debug() calls and redeploying, set a breakpoint and step through:

apex
public class AccountService {
    public static Account createWithDefaults(String name, String industry) {
        Account acc = new Account(Name = name);

        // ← breakpoint here: inspect 'acc' before the condition
        if (industry != null) {
            acc.Industry = industry;
        } else {
            acc.Industry = getDefaultIndustry();  // ← or here, to step in
        }

        insert acc;
        return acc;
    }

    private static String getDefaultIndustry() {
        CustomSetting__c cs = CustomSetting__c.getOrgDefaults();
        return cs?.Default_Industry__c;  // ← is this null?
    }
}

At the breakpoint the Variables panel shows industry arriving as null; stepping into getDefaultIndustry() shows the custom setting present but its field empty. The root cause is a setting that was never seeded for the test — which is a two-line fix, found without a single deploy.

When you do want a recording

Live debugging is the default, but a failure that only happens in CI is not something you can pause. For that, Nimbus records traces locally and replays them through the same debugger UI — with stepBack and reverse-continue, since nothing is re-running:

bash
nimbus test "CalculatorTest.*" --trace --trace-level verbose

Record at verbose or higher so statement-level steps are captured, then launch a replay session against the trace file. This is not the Apex Replay Debugger: no org, no log download, no checkpoints — and it is deterministic, because it is a recording of an execution that already happened on your machine.

Next steps

Pick the test you most recently debugged with System.debug() and put a breakpoint where the first debug line was. If the value you needed is in the Variables panel, you can delete the rest of them.