Demo

Quick Start

Install

The installer downloads the right release artifact for your OS and adds jdbg to your user-level PATH. Open a new terminal afterwards so the command is visible.

Windows:

powershell -ExecutionPolicy Bypass -c "irm https://github.com/PieceOfFall/jdbg/releases/latest/download/java-agent-debugger-installer.ps1 | iex"

macOS / Linux:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/PieceOfFall/jdbg/releases/latest/download/java-agent-debugger-installer.sh | sh
# Install to ~/.cargo/bin/jdbg
cargo install --git https://github.com/PieceOfFall/jdbg.git

# Build from source
git clone https://github.com/PieceOfFall/jdbg.git
cd jdbg
# Source builds need JDK 17+ for the Gradle sidecar build.
cargo build --release

Register With Your Agent

jdbg setup

Use non-interactive setup when provisioning machines:

jdbg setup --target claude,codex,opencode,pi --yes
jdbg setup --backend jdi --target codex --yes
jdbg setup --target codex --print
jdbg setup --target opencode --print
jdbg setup --target pi --print

Debug Something

# Compile with debug info for locals and line breakpoints
javac -g Main.java

# Launch a debug session. The daemon starts automatically.
jdbg launch Main --classpath .

# Set a breakpoint and run
jdbg break-at Main 9
jdbg run

# Inspect the stopped program
jdbg locals
jdbg where
jdbg print myVar

# Move execution forward
jdbg step
jdbg cont

# Clean up
jdbg kill
jdbg daemon stop

In Claude Code, Codex, or OpenCode, ask the agent to debug the Java program. It drives the same flow through mcp__jdbg__* tools. In Pi, the installed skill drives the jdbg CLI.

Why jdbg

Agent Setup

jdbg setup installs only the target-specific configuration that belongs to jdbg. Removal is surgical and preserves sibling servers, user settings, and unrelated skill directories. Interactive setup also asks which backend the installed skills should prefer. Use --backend jdb|jdi for non-interactive provisioning; the preference is written into the skill guidance, while jdb remains the CLI/MCP runtime default when no backend is passed on session creation.

jdbg setup --remove
jdbg setup --remove --target codex
jdbg update

When --sourcepath is omitted, jdbg uses the current working directory as the source root and sends it to the daemon as an absolute path. Relative --sourcepath values are absolutized before session creation.

JDI source snippets decode strict UTF-8 first and automatically fall back to GBK for legacy Chinese Java sources. For JDI sessions, status reflects an incoming stop as soon as the sidecar event arrives; pending_stops > 0 means one or more stops are waiting to be consumed by an execution-control command. When several thread-suspended stops coexist, last_event identifies the current inspectable stop and pending_stops includes that stop plus every queued stop. resume without a thread id explicitly discards all such outstanding stops and clears last_event.

jdbg update stops any running jdbg daemon before replacing the binary. It detects which agents already had jdbg configured, installs the latest release, installs the official JDI sidecar jar next to the jdbg binary, then re-registers the same targets.

MCP Server

jdbg __mcp runs an rmcp-based stdio MCP server exposing the debugger as 37 native tools. The MCP layer is a thin daemon client: it maps tool calls to the same command protocol used by the CLI, then renders the same results.

Manual Claude-style MCP config for a development build:

{
  "mcpServers": {
    "jdbg": {
      "command": "target/debug/jdbg",
      "args": ["__mcp"]
    }
  }
}

Codex config:

[mcp_servers.jdbg]
command = "target/debug/jdbg"
args = ["__mcp"]

OpenCode config:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "jdbg": {
      "type": "local",
      "command": ["target/debug/jdbg", "__mcp"],
      "enabled": true
    }
  }
}

Command Surface

# Session lifecycle
jdbg launch <MainClass> [--backend jdb|jdi] [--classpath CP] [--sourcepath SP] [--name N] [-- app-args...]
jdbg attach [--backend jdb|jdi] [--host H] [--port P] [--sourcepath SP] [--name N]
jdbg status | list | kill [--session ID]
jdbg daemon start | stop | status

# Breakpoints and watchpoints
jdbg break-at <Class> <line> [-c <condition>] [-s thread|all]
jdbg break-in <Class> <method> [--event entry|exit|both] [--args types] [-c <condition>] [-s thread|all]
jdbg catch <Exception> [--mode caught|uncaught|all]
jdbg watch <Class.field> [--mode access|modification|all]
jdbg unwatch <Class.field> [--mode access|modification|all]
jdbg breakpoints | clear <spec>
jdbg ignore <Exception> [--mode caught|uncaught|all]

# Runtime discovery
jdbg classes [pattern]
jdbg methods <Class>

# Execution control
jdbg run | cont | step | next | step-out

# Inspection
jdbg where [--all] | locals | print <expr> | dump <obj> | eval <expr>
jdbg inspect <expr> [--max-elements N]
jdbg threads | thread <id> | frame <up|down> [n] | list-source [line]
jdbg suspend [thread-id] | resume [thread-id]
jdbg set <lvalue> <value>
jdbg force-return <value>
jdbg lock <expr> | thread-locks [thread-id]
jdbg raw <jdb command...>

# Setup and maintenance
jdbg setup [--remove] [--print] [--target claude,codex,opencode,pi|auto|all|none] [--backend jdb|jdi] [--yes]
jdbg update

Global flags:

Flag Purpose
--session <id> Target a specific debug session. Defaults to the sole live session when unambiguous.
--json Emit machine-readable JSON instead of human-readable text.
--timeout <secs> Override per-command timeout.
--jdb-path <path> Use an explicit jdb executable. For JDI, this also selects the sidecar JDK.

Backend selection is made only when creating a session. The default jdb backend is the compatibility path and supports the full command surface. The jdi backend can launch or attach through a local Java sidecar for structured runtime data; it supports threads, line break-at, method break-in entry/exit events, field watch/unwatch, exception catch/ignore, breakpoint listing/clearing, run for launched sessions, cont, step, next, step-out, where, frame, locals, thread, classes, methods, list-source, thread suspend/resume, lock inspection, safe JSON inspect, expression print/eval/dump, set, and non-void force-return. JDI raw dispatches supported jdb-style aliases through the sidecar instead of writing to a literal jdb stdin.

JDI method breakpoints accept --event entry|exit|both. Method exit stops include the rendered return value when the target VM provides it. The jdb backend keeps method-entry behavior; --event exit and both fail with an explicit unsupported-backend error.

On JDI sessions, inspect is intentionally safe and reads fields directly without invoking getters. JDI print, eval, dump, set, and force-return are executable capabilities: method calls may run in the target JVM and can have side effects. set assigns locals, fields, or array elements by evaluating the right hand side as a Java expression. force-return evaluates its value expression and forces the current non-void method to return it; void force-return is reported as unsupported. These executable JDI operations require a suspended stop site; running, dead, or exited sessions fail explicitly.

The daemon can hold multiple JDI sessions at once. Each session serializes its own in-flight command, while separate sessions continue independently so multiple agents can debug different Java projects concurrently.

The JDI sidecar message protocol is length-prefixed JSON over platform-local byte streams: two one-way Named Pipes on Windows, or an AF_UNIX socketpair on Linux/macOS. The Unix socket is handed to the Java 8 sidecar as an inherited fd because Java 8 has no pathname UDS client API. gRPC, protobuf, and direct Rust JDWP are not part of the roadmap.

Architecture

Two clients feed one daemon. The daemon owns live backend sessions and the in-memory session map.

flowchart LR
    CLI["CLI: jdbg <cmd>"]
    MCP["MCP: jdbg __mcp"]
    Daemon["Daemon: session manager"]
    JdbA["jdb child A"]
    Jdi["JDI sidecar"]
    JvmA["JVM A"]
    JvmB["JVM B"]

    CLI --> Daemon
    MCP --> Daemon
    Daemon --> JdbA --> JvmA
    Daemon -->|length-prefixed JSON| Jdi --> JvmB

The internal dependency direction stays simple:

bin -> cli / output -> client / daemon -> backend -> session / jdi -> jdb / jdkpath -> error / protocol / registry

See DESIGN.md for the full design reference.

Requirements

Requirement Notes
Debug target JDK JDK 8-21+ with jdb on PATH or discoverable via JAVA_HOME.
Debug info Compile Java with javac -g for locals and reliable line breakpoints.
Rust Rust 1.85+ only when installing through cargo or building from source.
Source build JDK JDK 17+ is required to run Gradle and build the JDI sidecar fat jar. Debug targets still support JDK 8+.

For JDWP attach on JDK 8, start the target with address=5005 or address=localhost:5005. address=*:5005 is JDK 9+ syntax.

When building from source, cargo build runs the Gradle wrapper in sidecar/jdi, builds the fat jar jdbg-jdi-sidecar.jar, and copies it next to the jdbg binary. Set JDBG_GRADLE_JAVA_HOME when the build JDK is different from the target/debuggee JDK. Override sidecar discovery with JDBG_JDI_SIDECAR_JAR or the Java runtime with JDBG_JDI_JAVA. For JDI sessions, --jdb-path also selects the sidecar JDK and lets JDK 8 tools.jar be found next to that JDK. Set JDBG_SKIP_JDI_SIDECAR_BUILD only when a suitable sidecar jar is already available through JDBG_JDI_SIDECAR_JAR or next to the jdbg binary.

classes works without a pattern, but that lists every loaded class; pass a pattern in real application servers. watch --mode all creates separate access and modification watchpoints on both backends, so unwatch --mode modification removes only the write watchpoint and leaves access watchpoints active. JDI structured inspect covers common list, deque, set, and map implementations without invoking getters.

Building And Testing

cargo build
cargo build --release
cargo test
cargo test -- --test-threads=1   # mirrors Windows CI when investigating JDI fixture contention

Tests cover parser fixtures from real jdb transcripts, reader behavior, protocol mapping, MCP tools, sessions, watchpoints, JDI fixture flows, JDI watchpoint flows, expression eval/set/force-return, MCP JDI smoke coverage, sidecar death handling, Java sidecar self-tests, and end-to-end flows where the environment has a JDK. CI runs the matrix on Windows, Linux, and macOS across JDK 8, 11, 17, and 21; Windows runs tests serially to avoid JDWP/JDI fixture process contention.

License

Licensed under the Apache License 2.0.