How Spring Debugger Enables Agent‑Free Remote Debugging of Java Apps
The article explains how JetBrains' Spring Debugger plugin provides remote debugging for Spring applications without using Java agents, detailing the underlying thread‑model tricks, supported containers, practical features like bean inspection and SQL execution, and step‑by‑step JDWP configuration.
Background
JetBrains released the Spring Debugger plugin in September 2025. The community asked whether the plugin could debug remote Spring applications, which led to a design that avoids using Java debug agents.
Java agents
In the Java ecosystem an agent is a piece of code attached to the JVM at startup or runtime, typically via the -javaagent flag:
java -javaagent:/path/to/agent.jar -jar your-app.jarAgents enable byte‑code manipulation, performance monitoring, etc., but they have drawbacks:
Complex JVM startup parameters and the need to specify the agent JAR path.
Version‑matching requirements between the agent, application, and JVM.
Potential interference with normal debugging because the agent modifies bytecode.
Remote debugging without an agent
Local debugging can attach a breakpoint during Spring context initialization, but in a remote scenario the JVM is already running, so there is no opportunity to inject an agent and capture the ApplicationContext. The solution relies on the thread‑pool models of embedded servlet containers.
Container thread models
Tomcat
Tomcat creates a worker thread pool at server startup. Idle worker threads are always present, allowing Spring Debugger to seize a free worker thread after the application has started and read the ApplicationContext immediately, without waiting for an HTTP request.
Jetty and Undertow
Both Jetty and Undertow separate I/O threads from worker threads. Worker threads are created lazily—only when the first request arrives. Consequently, Spring Debugger cannot load the context until the first HTTP request triggers worker‑thread creation.
Features available once the Spring context is obtained
Access any bean : Enter a bean name (e.g., categoryRepository) in the expression window; the plugin retrieves the bean from the context, allowing direct method invocation.
categoryRepository.deleteAll();
categoryRepository.findAll();Execute arbitrary SQL : Run JPQL or native queries directly from the debugger.
entityManager.createQuery("SELECT c FROM Category c").getResultList();Read configuration properties : Query runtime values, e.g.,
environment.getProperty("spring.datasource.url");Publish events manually : Trigger custom Spring events for testing listeners.
applicationEventPublisher.publishEvent(new CustomEvent("test"));Transaction inspection : While paused inside a transaction, view isolation level, propagation status, cached entities, and entity states (MANAGED, DETACHED, etc.) via the transaction node.
Runtime bean information : "Show runtime info" reveals which classes inject a bean and its dependencies, useful for navigating legacy code.
Effective property values : The plugin shows the actual runtime value of properties defined in .properties files and links to all definition sources.
server.port=8080 # actual: 8081 (overridden by env)Remote debugging configuration
The setup follows the standard JDWP remote‑debugging pattern. Add JDWP options to the JVM (for example, in a Docker‑Compose file):
http-server:
image: 'jb/http-server:latest'
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgresql:5432/db
- SPRING_DATASOURCE_USERNAME=user
- SPRING_DATASOURCE_PASSWORD=secret
- JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
ports:
- '8080:8080'
- '5005:5005'The crucial line is:
JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 server=y: JVM acts as a debug server waiting for connections. suspend=n: Do not pause the JVM on start (set to y to wait for a debugger). address=*:5005: Listen on port 5005 on all network interfaces.
In IntelliJ IDEA create a Remote JVM Debug configuration, specify the target IP and port, and set the module classpath so the IDE can locate source files. After connecting:
Tomcat provides the Spring context immediately.
Jetty/Undertow require a first HTTP request to load the context.
Once the context is available you can set breakpoints, evaluate expressions, inspect properties, and analyze transactions exactly as with local debugging.
Limitations
Supported only for embedded containers (Tomcat, Jetty, Undertow). Stand‑alone WAR deployments are not covered.
The remote debug configuration must specify the module classpath; otherwise source code is invisible.
The database structure view is unavailable – connections are shown but table schemas are not.
Key benefits
Zero extra configuration: only a standard JDWP port is required.
No maintenance burden: no agents to version or keep compatible.
Non‑intrusive: the application’s runtime behavior remains unchanged.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
