Debugging Go Services in GoLand with Delve: Breakpoints, Goroutines, and Run Configurations
Learn how to set up a GoLand run/debug configuration, use Delve to inspect variables and goroutine stacks, and avoid common pitfalls when debugging Go services locally.
03 Jun 2026, 16:07 UTC

Problem: reproducing a concurrency bug locally
When a Go service exhibits intermittent deadlocks or leaked goroutines, the fastest way to understand what is happening is to pause execution and inspect the state of each goroutine. Doing this with print statements adds noise and can change timing, while attaching a debugger after the fact often fails if the binary was built with optimizations.
Setting up a repeatable run/debug configuration
- Open
Run → Edit Configurations…. - Click the
+button, chooseGo Build. - Fill in:
Package path: the module path to yourmainpackage, e.g.example.com/myservice/cmd/serverWorking directory: the directory that contains thego.modfile (or leave blank to use the project root).Program arguments: any flags your binary expects, e.g.-config=config/local.yamlEnvironment variables: addGOLOG=debugor similar as needed.- Click
OKto save. GoLand creates a.runfile in.idea/runConfigurations/that can be committed to version control.
Inspecting variables and goroutine stacks
With the configuration selected, click the debug icon (or press Shift+F9). Execution stops at the first breakpoint you set.
Setting a breakpoint
Click in the gutter next to a line of code, for example:
func handler(w http.ResponseWriter, r *http.Request) {
// <- breakpoint here
ctx := r.Context()
if err := process(ctx); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
When the breakpoint hits, the Debug tool window shows:
- Frames: the current goroutine’s call stack, letting you step through functions.
- Variables: locals, parameters, and globals; you can evaluate expressions by typing them in the
Evaluate Expressiondialog. - Goroutines tab: a list of all active goroutines with their current stack traces. Selecting a different entry switches the Frames view to that goroutine, which is invaluable for spotting a goroutine stuck in a channel send or a mutex lock.
Trade‑off: optimized binaries and Delve compatibility
GoLand’s built‑in debugger relies on Delve. If you run go build without disabling optimizations, variables may appear as <optimized out> or stepping can skip lines. For local debugging GoLand automatically adds -gcflags='all=-N -l' to the build flags, but if you attach to an already‑running process you must rebuild the binary with those flags yourself.
Additionally, after a Go toolchain upgrade the bundled Delve version may lag behind the new Go release, leading to attachment failures. Verify compatibility by checking Help → About → Debugger (or the bundled Delve version in plugins/goLand/lib/dlv) against your go version.
Actionable closing
- Commit the generated
.runfile so teammates can launch the same configuration. - Always start a debugging session from this configuration rather than a raw
go runcommand. - When you need to attach to a long‑running service, rebuild it with
-gcflags='all=-N -l'and useRun → Attach to Process. - Periodically check the Delve version after upgrading Go; if the IDE warns about a mismatch, download the matching Delve release from
github.com/go-delve/delveand point GoLand to it viaSettings → Build, Execution, Deployment → Debugger → Delve.
With these steps you can pause execution, examine variable state, and walk through each goroutine’s stack without leaving the editor—turning elusive concurrency bugs into a tractable debugging session.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.