Blog

Debug Apex in VS Code with real breakpoints, not log replay

How to debug Apex in VS Code with live breakpoints: install the renmy runtime extension, click the gutter, press F5 and pause a running test, with no org.

Nimbus was renamed renmy runtime in October 2026; the VS Code extension published under the Nimbus name is the same extension and updates in place. The record of the rename is Nimbus is now renmy runtime.

Short version

renmy runtime is a local Salesforce Apex runtime: it executes real Apex, SOQL, DML and Salesforce Flows on your machine against an embedded PostgreSQL, with no Salesforce org, no scratch org, no Docker and no JVM. Because the code runs on your machine, VS Code can attach a real debugger to it. Install the renmy runtime extension from the Microsoft Marketplace or Open VSX, click the gutter next to a line in a class, a trigger or a test, and press F5: the test pauses on that line with every local variable and the call stack in the Debug panel, and Step Over, Step In and Step Out work as they do for every other language. It is a live debugger over the Debug Adapter Protocol, not a replay of a downloaded log. Stepping and coverage gutters are Pro, free during the public beta; running the test and recording a trace are free.

How do you debug Apex today?

Most Salesforce developers have never attached a debugger to Apex. The platform offers two org-based options, and they are not the same thing.

The Apex Replay Debugger is free and is not a live debugger. The cycle: enable debug logging in the org, deploy and run the test, download the debug log from Setup, load it into the Replay Debugger, set checkpoints (not breakpoints), and replay. Repeat for every change, because the log describes an execution that has already finished; it is stale the moment you change a line. Five to ten minutes of setup per session is typical. It is a log viewer with a debugger’s interface; Debugging Apex with debug logs covers what you can and cannot read out of one.

The Apex Interactive Debugger is 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.

So the everyday fallback is System.debug(): add a line, deploy, run, read the log, repeat. It costs a deploy per question.

Why can’t VS Code debug Apex the way it debugs Java or JavaScript?

When a Java or JavaScript developer presses F5, the program starts on their laptop and the editor attaches to that process. A breakpoint is an instruction to the process: stop here and let me look. That works because the process is theirs.

Apex has never run on the developer’s machine. The compiler, the database and the test runner live inside a Salesforce org, in a multi-tenant runtime nobody can attach to, so VS Code has nothing to stop. The best the platform could offer was a record of what happened, which is what a debug log is.

A local Apex runtime changes the premise. renmy runtime executes Apex, SOQL and DML on your machine against an embedded PostgreSQL it ships with: classes, interfaces, triggers, flows, governor limits, Test.startTest/stopTest, @testSetup, per-test transaction isolation. The test is now a process on your laptop, so a debugger can pause it like any other. It is not a mock and not a transpiler; the parity record is public at renmy.net/parity, and What is a local Apex runtime? explains the idea from zero.

Who this is for: Apex developers in VS Code, Cursor or Windsurf who debug with System.debug() and a log; teams without an Interactive Debugger licence; anyone who has stepped through Java or Node code and wondered why Apex could not do that.

How to debug Apex in VS Code with breakpoints

1. Install renmy runtime

bash
curl -fsSL https://renmy.net/install.sh | sh          # macOS, Linux
irm https://renmy.net/install.ps1 | iex              # Windows PowerShell
cd your-sfdx-project
renmy test "*"

One binary, no JVM, no Docker. The last line runs all your Apex tests locally and proves the project is picked up before you involve the editor. The quickstart is at renmy.net/quickstart.

2. Install the extension

The extension is listed as Renmy - Local Apex Runtime on the Microsoft Marketplace, extension id NimbusSolutions.testnimbus. For Cursor, Windsurf and VSCodium, install the same extension from Open VSX.

It finds the CLI on your PATH as renmy or nimbus (the nimbus.binaryPath setting), connects to the renmy daemon in your project, and adds breakpoints, step controls and variable inspection to the built-in Debug panel. The rest of the extension (inline results, trace viewer, governor-limit tracking, Test Explorer, watch mode) is at renmy.net/vscode; the day-to-day loop is in An Apex workflow in VS Code without an org.

3. Set a breakpoint and press F5

Open a test class. Every @IsTest class and method has code lenses above it: Run Test, Debug Test, View Trace. Click the gutter next to the line you want to stop on, in the test, in the class under test or in a trigger. Then press F5, which without a launch.json debugs the test class in the active editor, or click Debug Test above a single method.

The test runs and pauses at the line. From save to paused at a breakpoint is under five seconds. There is no trace flag, no log level to pick, no download.

4. Optional: a launch.json you can commit

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

The first configuration prompts for ClassName.methodName; the second debugs whatever test file is open. stopOnEntry: true pauses at the first statement instead of waiting for a breakpoint.

Underneath, the extension is a client of renmy dap, a Debug Adapter Protocol server the editor launches over stdin and stdout. Its launch arguments are the whole configuration surface: program (a test pattern such as "CalculatorTest.addsPositive" or "*Test"), projectPath, orgAlias and stopOnEntry. Any DAP-capable editor can drive it.

What you see at the breakpoint

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

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 first breakpoint the Variables panel shows every local in scope:

bash
Variables
└── acc: Account {
│     Name: "Acme Corp",
│     Industry: null,        ← not set yet
│     Id: null
│   }
├── name: "Acme Corp"
└── industry: null           ← passed as null

Call Stack
AccountService.createWithDefaults   line 4
AccountServiceTest.testCreate       line 12

Primitives, SObjects, collections and class instances all appear, and nested objects expand to any depth. The call stack shows how execution reached the line, with line numbers; click a frame to inspect its scope.

industry arrived as null, so the else branch is about to run. Step In on getDefaultIndustry():

bash
Variables
└── cs: CustomSetting__c {
│     Default_Industry__c: null   ← root cause
│   }

The custom setting exists but its field is empty: nothing seeded it for the test. The fix is one seed entry in the project settings file, found without a deploy:

bash
# renmy.properties
renmy.seed.org-default.CustomSetting__c=Default_Industry__c=Technology

Step Over moves one statement at a time without descending into calls; Step Out returns to the caller and resumes from there.

Can you put a breakpoint inside a trigger?

Yes, and this is where a live debugger pays for itself. A trigger has no test method of its own; it fires because a test did DML, and in an org the only view into it is the log. Locally, the insert acc above fires the Account triggers inside the running test, so a breakpoint in the .trigger file or in the handler it calls pauses there, with the test method, the service method and the trigger all on the call stack. Step In follows calls into trigger handlers and utility methods through the full call graph; Step Out brings you back to the statement that caused the DML.

Coverage gutters after every run

After each run the editor margin updates: a green circle on an executed line, a red one on a missed line, an orange one on a branch where only one side was taken. Enable it with nimbus.coverage.enabled. Method-level percentages appear inline after each signature, // 75% (3/4 lines), so a thin spot is visible without opening a report.

The orange marks are branch coverage: both sides of every if, switch and ternary are tracked, which the org’s built-in metric does not do. After stepping through the else branch of createWithDefaults, the gutter tells you whether any test ever took the if branch. Reports and renmy coverage diff for PR deltas are at renmy.net/coverage.

When you want a recording instead

A failure that only happens in CI cannot be paused. For that case renmy runtime records a trace locally and replays it through the same debugger UI:

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

Record at verbose or higher so statement-level steps are captured; a trace recorded below that has nothing to step through, and replay says so instead of hanging. Then launch a replay session:

json
{
  "name": "Replay Renmy Trace",
  "type": "nimbus-apex",
  "request": "launch",
  "mode": "replay",
  "traceFile": "${workspaceFolder}/.nimbus/traces/…/trace.jsonl"
}

Leave out traceFile to replay the newest run. Nothing re-runs, so replay is immediate and deterministic, and it adds two controls a live session cannot have: Step Back, and Reverse Continue to the previous breakpoint.

This is not the Apex Replay Debugger under another name. There is no org, no log download and no checkpoints; it is a recording of an execution that happened on your machine, and you move through it in both directions. The side-by-side is at renmy runtime vs the Apex Replay Debugger.

I already have a sandbox. Why would I need this?

Keep the sandbox. It is where you deploy, where the UI lives, where integrations are checked. What it does not give you is a debugger: the Replay Debugger reads a finished log, and the Interactive Debugger is a separate licence most teams do not hold. The runtime gives you a pause in running Apex on the machine you are typing on, and takes the hundred edit-run-look cycles before a deploy off the org. When the code is right, renmy deploy (Free) snapshots the payload, runs the local gates, validates the same bytes in the org with a real check-only validation, and deploys.

Speed makes this a habit rather than a last resort. The published benchmark runs four open-source suites, 837 tests, in about 1 min 46 s in a Salesforce org (deploy and run) against 2.4 s locally, roughly 45×, every test passing in both: renmy.net/benchmarks. A debugger you reach in under five seconds is one you use for small questions.

What is free and what is Pro?

Running the test from the editor and recording a trace are free. Stepping in the debugger and the coverage gutters are Pro, which is free during the public beta; the list price afterwards is $19 a month. The Free tier for individual developers is $0, no card, one machine. Pricing is at renmy.net/#pricing.

The same debugger is available in IntelliJ IDEA through the JetBrains plugin, Renmy Runtime - Salesforce IDE, which drives IntelliJ’s native debugger UI; the product page is renmy.net/jetbrains. Both editors are clients of the same renmy dap.

Try it

bash
curl -fsSL https://renmy.net/install.sh | sh          # macOS, Linux
irm https://renmy.net/install.ps1 | iex              # Windows PowerShell
cd your-sfdx-project
renmy test "*"

Then install the extension, open a test, click the gutter, press F5.

Frequently asked questions

Can you debug Apex in VS Code without a Salesforce org?

Yes. renmy runtime executes Apex, SOQL and DML on your machine against an embedded PostgreSQL, so the test is a local process and the VS Code debugger can pause it. Install renmy runtime and the Renmy - Local Apex Runtime extension (NimbusSolutions.testnimbus), set a breakpoint by clicking the gutter, and press F5. No org, no scratch org and no debug log are involved.

Is this the Apex Replay Debugger?

No. The Apex Replay Debugger reads a debug log downloaded from an org after the code has finished running, and it uses checkpoints, not breakpoints. The renmy runtime debugger attaches to the running test over the Debug Adapter Protocol and pauses it at real breakpoints, in classes, triggers and test methods, with live variables and a call stack. It can also replay a locally recorded trace with Step Back, which is still not a log from an org.

Does the debugger work in Cursor and Windsurf?

Yes. The extension is published to Open VSX as well as the Microsoft Marketplace, so it installs in Cursor, Windsurf, VSCodium and other VS Code-based editors. The debugger uses the built-in Debug panel of the editor, so breakpoints, F5, Step Over, Step In, Step Out and the Variables view work the same way.

Is the renmy runtime debugger free?

Running a test from the editor and recording a trace are free. Stepping in the debugger and the coverage gutters are Pro, which is free during the public beta and has a list price of $19 a month afterwards. The Free tier for individual developers is $0 with no card, covering unlimited local test runs, gated deploys and IDE integration on one machine.

Breakpoints in Apex, on your machine

renmy runtime runs your Apex tests against an embedded PostgreSQL on your machine, so the VS Code debugger can pause them. Install the extension, click the gutter, press F5.