Oct 9, 2026 · @gionata_legrottaglie
Right-click a Gradle task in Eclipse, choose Debug, and stop at your breakpoints: no Remote Java Application, no fixed port. Building it taught me two things about Eclipse that are worth knowing even if you never install the plugin.
The problem
Buildship, the Gradle integration of Eclipse, runs Gradle tasks but cannot debug them. To stop at a breakpoint in code a task starts (run, bootRun, a custom JavaExec, a test), you go through the same routine every time:
- Run the task with
--debug-jvm, so that its JVM waits for a debugger on port 5005. - Create a Remote Java Application configuration that attaches to
localhost:5005. - Launch the two in the right order, or tie them in a Launch Group that waits for
Listening for transport dt_socketin the console.
It works, but it is a configuration per project, a fixed port that clashes when two apps run, and two launches to stop. In 2016 the Buildship team answered on the Gradle forum that debugging builds was "on our roadmap". IntelliJ IDEA has always had it: right-click the task, Debug.
One right-click
Gradle Task Debugger for Eclipse adds Debug Gradle Tasks to the context menu of the Gradle Tasks view. Choose it on run, bootRun or test, and the debugger is attached to every JVM the build starts: breakpoints, stepping and variables as in a Java application.
- Run configurations: every Gradle Task configuration of Buildship can also be launched in debug mode, from the Debug toolbar button.
- Debug view: a Gradle build entry shows the build; Terminate on it stops the build and its JVMs.
- Hot code replace: saving a Java file updates the running code, including classes with lambdas (more on that below).
- No port to configure: each session uses a free port, so two apps can be debugged at once.
Screenshots: the menu and a debug session of the run task.
How it works
The trick is to turn the connection around: Eclipse listens, and the JVMs Gradle starts connect to it.
- Eclipse starts listening for JVMs on a free port, with the Socket Listen connector of JDT.
- Buildship runs the build exactly as in run mode, plus an init script, added through Buildship's public
invocationcustomizersextension point. - The init script sets
debugOptionson every task that forks a JVM: JDWP in client mode (server=n), suspended until the debugger is there. - Each JVM connects to Eclipse when it starts; when the build ends, Eclipse stops listening.
The heart of the init script:
gradle.allprojects { project ->
project.tasks.withType(JavaForkOptions).configureEach { task ->
def options = task.debugOptions
options.enabled.set(true)
options.server.set(false) // the JVM connects to Eclipse
options.suspend.set(true)
options.port.set(project.providers.systemProperty('gradleTaskDebugger.port').map { it as Integer })
}
}
Two details matter. set() wins over the conventions a build plugin may have configured for its own debug options. And the port is read lazily from a system property, so the configuration cache is reused from one debug session to the next, even though the port changes every time.
Lesson 1: Eclipse stops compiling while a Gradle task runs
The first version debugged fine, but hot code replace did nothing. I saved a change, and there was no error and no out of synch marker. The class file Eclipse compiles into bin/main still had the timestamp of before the save.
The cause is in Buildship: it runs every build inside a workspace operation (IWorkspace.run), and Eclipse's auto-build waits for that operation to end. A task like bootRun, which runs a server, ends only when you stop it. Until then Eclipse does not compile what you save, so there is no new class for hot code replace to send. Plain Run behaves the same; you only notice it when you debug.
An explicit incremental build is not held back, and that is the way out. I first proved it with a test that saves a file and relies on Build Automatically: it failed with Buildship's behavior and passed once the plugin built on save.
Lesson 2: javac and ECJ do not agree on lambda names
With Eclipse compiling again, the next save of a one-line change to a println gave Hot code replace failed - Delete method not implemented. No method had been deleted.
The JVM was running the class Gradle compiled, with javac. Hot code replace sends the class Eclipse compiled, with its own compiler, ECJ. The two compile lambdas into synthetic methods with different names. In a REST endpoint with two lambdas in a method findCustomer:
| Compiler | Synthetic methods of the two lambdas |
|---|---|
| javac (Gradle): the class in the JVM |
lambda$findCustomer$0, lambda$findCustomer$1
|
| ECJ (Eclipse): the class hot code replace sends |
lambda$1, lambda$2
|
To the JVM, the new class deletes two methods and adds two, and class redefinition allows neither. So any class with a lambda fails this way, whatever you change in it; enum switches and anonymous classes can do the same. In a Java Application launch you never see it, because the JVM runs ECJ's classes too. You can check any class yourself with javap -p on build/classes and on bin/main.
The fix: reload what Gradle compiles
If the JVM runs javac's classes, hot code replace must send javac's classes. So during a debug session, saving a Java file does this:
- The plugin runs the
classestask of that file's project with Gradle, in the background. - It collects the class files Gradle rewrote under
build/classes. - It hands them to JDT's own hot code replace, which redefines them in the JVMs, reinstalls the breakpoints and drops the obsolete frames, as it does for the classes Eclipse compiles.
Same compiler on both sides, so lambdas, enum switches and anonymous classes reload like any other change. The cost is one Gradle compilation per save: about 4 seconds in the plugin's integration test, a little more on a large module. The limits of the JVM stay: adding or removing methods or fields still needs a restart, and JDT says so as usual. It is the same trade-off IntelliJ IDEA makes when it delegates the build to Gradle.
Try it
The plugin is open source (EPL 2.0) and needs Eclipse 2024-06 or later with Buildship 3.1, which the Eclipse IDE packages include.
- Install: search for Gradle Task Debugger in Help → Eclipse Marketplace…, or install from the Marketplace page.
-
Update site:
https://legrottagliegionata.github.io/eclipse-gradle-debug/, signed with PGP. -
Demo: the repository has a small demo project whose
runtask starts an HTTP server: import it, put a breakpoint, right-clickrun. - Feedback: bugs and ideas go to the GitHub issues; a star on the repository helps others find it.
One limit to know: only the JVMs the build starts are debugged. Build logic (plugins, buildSrc) runs in the Gradle daemon; for that, Gradle's own -Dorg.gradle.debug=true still applies.
Sources
- Gradle forum: Gradle buildship plugin with eclipse (hard time finding debug option), the 2016 roadmap answer
- Gradle forum: Debugging tests with Buildship
- Fabian Lee: interactive JDWP debugging of bootRun gradle task in Eclipse IDE, the manual routine
-
Gradle: Troubleshooting builds, on
org.gradle.debug - Gradle: Configuration cache
- Gradle Task Debugger for Eclipse on GitHub
Top comments (0)