java-debugging

Par github · awesome-copilot

Déboguez les applications Java de manière systématique : débogage distant JDWP, jdb, attachement VS Code et IntelliJ, débogage de tests Maven/Gradle, mode dev Spring Boot et Quarkus, conteneurs, points d'arrêt/logpoints et lecture des stack traces. À utiliser lorsque l'utilisateur doit identifier la cause d'un bug Java, d'une exception, d'un comportement incorrect ou d'un test en échec, ou demande comment attacher un débogueur.

npx skills add https://github.com/github/awesome-copilot --skill java-debugging

Débogage Java

Trouvez la cause racine d'un défaut Java avec une boucle reproductible et axée sur les hypothèses, et attachez un débogueur de manière sécurisée quand les logs ne suffisent pas.

Règles de sécurité

  • Liez JDWP à la boucle locale uniquement (127.0.0.1). Ne liez jamais *:5005 sur un réseau accessible ; utilisez un tunnel SSH ou kubectl port-forward pour les hôtes distants.
  • Utilisez suspend=n pour les applications en cours d'exécution. suspend=y n'est acceptable que pour les courts tests locaux liés à la boucle locale (en attente de l'attachement de l'IDE). Ne laissez jamais un agent de débogage activé en production ; JDWP donne l'exécution de code complète à quiconque se connecte.
  • N'attachez pas un débogueur à la production sans instruction explicite de l'utilisateur. Préférez reproduire localement ou utilisez les diagnostics en lecture seule (voir la skill java-diagnostics).
  • JAVA_TOOL_OPTIONS est récupéré par chaque JVM démarrée dans cet environnement (outils de build, JVMs de tests forkés, processus enfants), causant des conflits Address already in use. Préférez limiter le flag à l'unique processus que vous voulez déboguer.

Méthode

Reproduisez de manière déterministe, minimisez, lisez les preuves (stack traces), testez une hypothèse falsifiable avec l'outil le moins intrusif (ligne de log ou logpoint avant les breakpoints), puis ajoutez un test de régression et supprimez les flags de débogage et la logging temporaire.

Comportement dépendant de la version (vérifié)

  • Les messages NullPointerException utiles (JEP 358) sont désactivés par défaut dans JDK 14 (-XX:+ShowCodeDetailsInExceptionMessages) et activés par défaut à partir de JDK 15 (JEP 358 documente le format du message). Source : https://openjdk.org/jeps/358.
  • JDWP sans address=5005 lie toutes les interfaces avant JDK 9 et la boucle locale à partir de JDK 9 (voir ci-dessous).
  • Gradle --debug-jvm passe une adresse nue, donc sur JDK 8 elle écoute sur toutes les interfaces.

Attacher un débogueur (JDWP)

Démarrez la JVM :

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:5005 -jar app.jar

Écrivez toujours l'127.0.0.1:5005 explicite sur toutes les versions JDK : un address=5005 nu écoute sur toutes les interfaces avant JDK 9 et uniquement sur la boucle locale à partir de JDK 9. Sur JDK 8 et les versions antérieures, la forme host:port peut ne pas être respectée, donc limitez aussi l'accès avec un firewall ou tunnel.

Attachez avec 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.NullPointerException

VS 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. La boîte de dialogue affiche la chaîne d'arguments JVM correspondante.

Déboguer les tests et frameworks

Scénario Commande (port 5005)
Test unitaire Maven Surefire mvn test -Dmaven.surefire.debug -Dtest=OrderServiceTest
Maven Failsafe IT mvn verify -Dmaven.failsafe.debug -Dit.test=OrderIT
Test Gradle 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) Configurez bootRun comme ci-dessous, puis gradle bootRun (boucle locale, sans suspension)
Quarkus dev mode mvn quarkus:dev (port de débogage 5005 ouvert par défaut ; -Ddebug=5006 le change, -Ddebug=false le désactive)

Le mode débogage Surefire/Failsafe et --debug-jvm suspendent jusqu'à l'attachement d'un débogueur, ce qui convient pour un run local. Surefire et Failsafe acceptent une chaîne d'agent personnalisée au lieu du flag nu, p. ex. -Dmaven.surefire.debug="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:5005". Auditeurs vérifiés sur 127.0.0.1 (via lsof -nP -iTCP:5005 -sTCP:LISTEN) pour Gradle 9.7 --debug-jvm, Maven Surefire 3.5 -Dmaven.surefire.debug et Quarkus 3.40 dev mode sur JDK 26 ; Gradle 7.6.4, 8.0.2 (JDK 17), 8.5 (JDK 21) et 8.14.3 écoutaient aussi sur 127.0.0.1. Gradle passe un address=5005 nu (pas d'host), donc l'adresse de liaison provient du JDK : boucle locale sur JDK 9+, mais toutes les interfaces (0.0.0.0:5005, vérifiées dans un conteneur eclipse-temurin:8-jdk avec Gradle 7.6.4 et 8.14.3) sur JDK 8. Les versions antérieures de Gradle/plugin n'ont pas été testées ; exécutez la même vérification lsof et utilisez un agent de boucle locale explicite sur les réseaux partagés ou JDK 8.

Configuration Gradle explicite (Gradle 7.6+ ; debugOptions.host a été ajouté dans 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 étend JavaExec, pas Test ; configurez-le séparément quand le plugin Boot est appliqué.
tasks.named<JavaExec>("bootRun") {
    debugOptions { enabled.set(true); host.set("127.0.0.1"); port.set(5005); server.set(true); suspend.set(false) }
}

Puis exécutez gradle test --tests '*OrderServiceTest' ou gradle bootRun, sans --debug-jvm (qui active la suspension). Configurez uniquement la tâche que vous déboglez ; ne lancez pas les deux auditeurs sur le port 5005 à la fois. Pour Gradle avant 7.6, utilisez jvmArgs("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:5005") sur la tâche cible à la place (suspend=y pour les tests locaux), sans aussi activer debugOptions ou --debug-jvm. Sur JDK 8 et les versions antérieures, conservez la précaution firewall/tunnel ci-dessus.

Conteneurs

Publiez le port uniquement vers la boucle locale et laissez la JVM écouter à l'intérieur du conteneur :

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-app

À l'intérieur du conteneur, l'agent doit lier * pour que le mappage de port fonctionne, donc JDWP (exécution de code complète) est accessible par chaque autre conteneur sur le même réseau Docker ; le mappage 127.0.0.1: bloque uniquement l'accès depuis d'autres hôtes. Utilisez ceci seulement sur une machine développeur où aucun conteneur non fiable ne partage le réseau : lancez-le sur un réseau défini par l'utilisateur dédié (pas le bridge par défaut partagé) contenant uniquement ce conteneur, ou maintenez l'agent sur 127.0.0.1 à l'intérieur du conteneur et accédez-y avec un forwarding de style docker exec/kubectl port-forward au lieu de le publier. Pour Kubernetes, utilisez kubectl port-forward pod/<name> 5005:5005 contre un pod dev/staging uniquement.

Pièges courants

  • Hot code replace ne remplace que les corps de méthodes ; ajouter/supprimer des méthodes ou champs ou changer les signatures nécessite un redémarrage (ou Quarkus dev mode / Spring DevTools).
  • Info de débogage manquante : le code compilé sans -g affiche les variables indisponibles (Maven et Gradle l'incluent par défaut). Dans les lambdas, avancez dans le corps de la lambda.
  • Heisenbugs/races : la pause change le timing. Préférez les logpoints, les thread dumps, ou JFR ; suspendez uniquement le thread courant, pas tous les threads.
  • Code asynchrone et réactif : les stack traces affichent les frames du scheduler, pas l'appelant. Utilisez les stack traces asynchrones de l'IDE, les IDs de corrélation, ou Reactor Hooks.onOperatorDebug() en dev uniquement.
  • Virtual threads (JDK 21+) : il peut y en avoir des milliers ; utilisez jcmd <pid> Thread.dump_to_file -format=json file.json pour un aperçu.
  • Timeouts : les longues pauses peuvent déclencher des timeouts HTTP, DB ou heartbeat ; relevez-les localement.
  • Effets secondaires de toString() : l'évaluation du débogueur appelle toString(), ce qui peut déclencher le chargement lazy ou muter l'état.

Aide optionnelle à la navigation de code

Si l'outil lsp de la CLI Copilot est configuré (voir la skill lsp-setup), go-to-definition, find-references et call hierarchy aident à tracer le chemin d'exécution. C'est optionnel ; grep et la lecture de code fonctionnent aussi.

Références

  • Lecture des stack traces
  • Pour les symptômes d'exécution (CPU, mémoire, deadlocks, démarrage lent), utilisez la skill java-diagnostics.

Docs des outils officiels (préférez-les à cette skill pour le JDK utilisé ; changez 21 par votre version) : jcmd https://docs.oracle.com/en/java/javase/21/docs/specs/man/jcmd.html, Guide de dépannage JDK https://docs.oracle.com/en/java/javase/21/troubleshoot/.

Skills similaires