Java Debugging
GitHubOfficialFirst committed by jduboisDistributed by GitHub
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured, hypothesis-driven approach to debugging Java applications. It covers the configuration of JDWP for remote debugging, attaching debuggers via VS Code and IntelliJ, and debugging specific scenarios like Maven/Gradle tests, Spring Boot, and Quarkus. It emphasizes safety by recommending loopback-only binding and provides guidance on interpreting stack traces and handling common debugging pitfalls like Heisenbugs and async code.
What it does
- Explains how to attach debuggers to local and remote JVMs using JDWP.
- Provides configuration patterns for VS Code and IntelliJ IDEA.
- Details how to debug Maven and Gradle test suites.
- Offers best practices for debugging Spring Boot and Quarkus applications.
- Provides safety guidelines for secure remote debugging.
- Includes techniques for reading and interpreting Java stack traces.
When to use it
- When you need to identify the root cause of a Java bug or exception.
- When logs are insufficient to diagnose failing tests or incorrect behavior.
- When you need to attach a debugger to a running JVM process.
- When debugging complex issues in containerized Java environments.
Advantages
- Promotes a systematic, hypothesis-driven debugging loop.
- Provides secure, production-safe configuration patterns.
- Covers a wide range of modern Java build tools and frameworks.
- Addresses common challenges like async stack traces and Heisenbugs.
Usage examples
- “How do I attach a debugger to a remote Spring Boot application?”
- The agent provides the necessary JVM arguments to start the application with JDWP enabled on a secure port and shows how to configure your IDE to attach to that specific host and port.
- “My Maven test is failing; how can I debug it?”
- The agent provides the specific Maven command flags (e.g., -Dmaven.surefire.debug) to suspend the test execution until you attach your IDE's debugger.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/github/java-debugging/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip java-debugging.zip -d .agents/skills
Documentation by jdubois
Java Debugging
Find the root cause of a Java defect with a reproducible, hypothesis-driven loop, and attach a debugger safely when logs are not enough.
Safety rules
Bind JDWP to loopback only (
127.0.0.1). Never bind*:5005on a reachable network; use an SSH tunnel orkubectl port-forwardfor remote hosts.Use
suspend=nfor running applications.suspend=yis acceptable only for short local test runs bound to loopback (waiting for the IDE to attach). Never leave a debug agent enabled on production; JDWP gives full code execution to whoever connects.Do not attach a debugger to production without explicit user instruction. Prefer reproducing locally or use read-only diagnostics (see the
java-diagnosticsskill).JAVA_TOOL_OPTIONSis picked up by every JVM started in that environment (build tools, forked test JVMs, child processes), causingAddress already in useclashes. Prefer scoping the flag to the one process you want to debug.
Method
Reproduce deterministically, minimize, read the evidence (stack traces), test one falsifiable hypothesis with the least intrusive tool (log line or logpoint before breakpoints), then add a regression test and remove debug flags and temporary logging.
Version-dependent behavior (verified)
Helpful NullPointerException messages (JEP 358) are off by default in JDK 14 (
-XX:+ShowCodeDetailsInExceptionMessages) and on by default from JDK 15 (JEP 358 documents the message format). Source: https://openjdk.org/jeps/358.JDWP bare
address=5005binds all interfaces before JDK 9 and loopback from JDK 9 (see below).Gradle
--debug-jvmpasses a bare address, so on JDK 8 it listens on all interfaces.
Attach a debugger (JDWP)
Start the JVM:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:5005 -jar app.jarAlways write the explicit 127.0.0.1:5005 on every JDK version: a bare address=5005 listens on all interfaces before JDK 9 and only on loopback from JDK 9. On JDK 8 and older, the host:port form may not be honored, so also restrict access with a firewall or tunnel.
Attach with jdb:
jdb -attach 127.0.0.1:5005
# stop in com.example.OrderService.place | stop at com.example.OrderService:42
# cont | next | step | locals | print order | where | catch java.lang.NullPointerExceptionVS Code (Extension Pack for Java)
.vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Attach to JVM",
"request": "attach",
"hostName": "127.0.0.1",
"port": 5005
}
]
}IntelliJ IDEA
Run | Edit Configurations | + | Remote JVM Debug; host 127.0.0.1, port 5005, debugger mode Attach. The dialog shows the matching JVM argument string.
Debug tests and frameworks
| Scenario | Command (port 5005) |
|---|---|
| Maven Surefire unit test | mvn test -Dmaven.surefire.debug -Dtest=OrderServiceTest |
| Maven Failsafe IT | mvn verify -Dmaven.failsafe.debug -Dit.test=OrderIT |
| Gradle test | gradle test --debug-jvm --tests '*OrderServiceTest' |
| Spring Boot (Maven) | mvn spring-boot:run -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:5005" |
| Spring Boot (Gradle) | Configure bootRun as below, then gradle bootRun (loopback, no suspend) |
| Quarkus dev mode | mvn quarkus:dev (debug port 5005 open by default; -Ddebug=5006 changes it, -Ddebug=false disables) |
Surefire/Failsafe debug mode and --debug-jvm suspend until a debugger attaches, which is fine for a local run. Surefire and Failsafe accept a custom agent string instead of the bare flag, e.g. -Dmaven.surefire.debug="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:5005". Verified listeners on 127.0.0.1 (via lsof -nP -iTCP:5005 -sTCP:LISTEN) for Gradle 9.7 --debug-jvm, Maven Surefire 3.5 -Dmaven.surefire.debug and Quarkus 3.40 dev mode on JDK 26; Gradle 7.6.4, 8.0.2 (JDK 17), 8.5 (JDK 21) and 8.14.3 also listened on 127.0.0.1. Gradle passes a bare address=5005 (no host), so the bind address comes from the JDK: loopback on JDK 9+, but all interfaces (0.0.0.0:5005, checked in an eclipse-temurin:8-jdk container with Gradle 7.6.4 and 8.14.3) on JDK 8. Older Gradle/plugin versions were not tested; run the same lsof check and use an explicit loopback agent on shared networks or JDK 8.
Explicit Gradle configuration (Gradle 7.6+; debugOptions.host was added in 7.6; Kotlin DSL):
tasks.withType<Test> {
debugOptions { enabled.set(true); host.set("127.0.0.1"); port.set(5005); server.set(true); suspend.set(true) }
}
// BootRun extends JavaExec, not Test; configure it separately when the Boot plugin is applied.
tasks.named<JavaExec>("bootRun") {
debugOptions { enabled.set(true); host.set("127.0.0.1"); port.set(5005); server.set(true); suspend.set(false) }
}Then run gradle test --tests '*OrderServiceTest' or gradle bootRun, without --debug-jvm (which enables suspension). Configure only the task you are debugging; do not run both listeners on port 5005 at once. For Gradle before 7.6, use jvmArgs("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:5005") on the target task instead (suspend=y for local tests), without also enabling debugOptions or --debug-jvm. On JDK 8 and older, retain the firewall/tunnel precaution above.
Containers
Publish the port to loopback only and let the JVM listen inside the container:
docker run --rm -p 127.0.0.1:5005:5005 \
-e JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" my-appInside the container the agent must bind * for the port mapping to work, so JDWP (full code execution) is reachable by every other container on the same Docker network; the 127.0.0.1: mapping only blocks access from other hosts. Use this only on a developer machine where no untrusted containers share the network: run it on a dedicated user-defined network (not the shared default bridge) containing only this container, or keep the agent on 127.0.0.1 inside the container and reach it with docker exec/kubectl port-forward style forwarding instead of publishing it. For Kubernetes use kubectl port-forward pod/<name> 5005:5005 against a dev/staging pod only.
Common pitfalls
Hot code replace only swaps method bodies; adding/removing methods or fields or changing signatures needs a restart (or Quarkus dev mode / Spring DevTools).
Missing debug info: code compiled without
-gshows unavailable variables (Maven and Gradle include it by default). In lambdas, step into the lambda body.Heisenbugs/races: pausing changes timing. Prefer logpoints, thread dumps, or JFR; suspend only the current thread, not all threads.
Async and reactive code: stack traces show scheduler frames, not the caller. Use IDE async stack traces, correlation IDs, or Reactor
Hooks.onOperatorDebug()in dev only.Virtual threads (JDK 21+): there can be thousands; use
jcmd <pid> Thread.dump_to_file -format=json file.jsonfor an overview.Timeouts: long pauses can trip HTTP, DB, or heartbeat timeouts; raise them locally.
toString() side effects: debugger evaluation calls
toString(), which may trigger lazy loading or mutate state.
Optional code-navigation aid
If the Copilot CLI lsp tool is configured (see the lsp-setup skill), go-to-definition, find-references, and call hierarchy help trace the execution path. It is optional; grep and reading code work as well.
References
For runtime symptoms (CPU, memory, deadlocks, slow startup) use the
java-diagnosticsskill.
Official tool docs (prefer them over this skill for the JDK in use; change 21 to your version): jcmd https://docs.oracle.com/en/java/javase/21/docs/specs/man/jcmd.html, JDK troubleshooting guide https://docs.oracle.com/en/java/javase/21/troubleshoot/.
Files
- ATTRIBUTION.mdAdded by AutoGineer859 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md8.3 KiB
- references/stack-traces.md1.6 KiB
references/stack-traces.md
Reading Java stack traces
Anatomy
java.lang.IllegalStateException: Order 42 already shipped
at com.example.OrderService.ship(OrderService.java:88)
at com.example.OrderController.ship(OrderController.java:31)
...
Caused by: java.sql.SQLException: Connection refused
at ...
... 12 moreFirst line: exception type and message. Read the message literally.
Top frame is where it was thrown; walk down to the first frame in your own code.
Caused by:chains: the deepest (last) cause is usually the root cause.... N moremeans frames shared with the enclosing trace.Suppressed:entries come from try-with-resources close failures.
Practical tips
Open the first frame in your own packages and check which reference could be null or which argument is invalid.
NullPointerException: helpful messages (on by default since JDK 15) name the null variable or call.Wrapper exceptions (
BeanCreationException,CompletionException,InvocationTargetException,ExecutionException): skip to the deepestCaused by.OutOfMemoryError: use thejava-diagnosticsskill.StackOverflowError: look for the repeating frame pattern (unbounded recursion).Missing traces on hot repeated exceptions come from
OmitStackTraceInFastThrow; check earlier log occurrences or run locally with-XX:-OmitStackTraceInFastThrow.Proxy/generated frames (
$$SpringCGLIB$$,$Proxy, lambdas) are noise; focus on neighbouring frames.Reactive/async: the trace points to scheduler threads; use correlation IDs or operator debug in dev.
Include the full trace, Java version, and dependency versions when asking for help or filing issues.
Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs a command-line tool
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- First committed by
jdubois
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/java-debugging · commit 797337f
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- jdubois
AutoGineer is not affiliated with or endorsed by jdubois or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download java-debugging.zip