<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sebastian Aburto</title>
    <description>The latest articles on DEV Community by Sebastian Aburto (@saburto).</description>
    <link>https://dev.to/saburto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F391316%2F6d3dd4f0-9e6a-43a7-bd1d-b4c144f8dab3.jpeg</url>
      <title>DEV Community: Sebastian Aburto</title>
      <link>https://dev.to/saburto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saburto"/>
    <language>en</language>
    <item>
      <title>I'm Not Coding Anymore, and That Is Ok</title>
      <dc:creator>Sebastian Aburto</dc:creator>
      <pubDate>Tue, 14 Jul 2026 19:51:02 +0000</pubDate>
      <link>https://dev.to/saburto/im-not-coding-anymore-and-that-is-ok-173h</link>
      <guid>https://dev.to/saburto/im-not-coding-anymore-and-that-is-ok-173h</guid>
      <description>&lt;p&gt;I am a hands-on software engineer with more than 17 years of experience, and I no longer write code. Yet, I still deliver software on a daily basis. I fix bugs, do analysis, design systems, implement new features, and test my changes.&lt;/p&gt;

&lt;p&gt;For the actual coding, I have become the guide for my agentic coding tools. My responsibility is to give them the necessary context to make the changes. I define the acceptance criteria using deterministic verification, and I review the evidence to check the completeness and accuracy of the results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guiding like a junior programmer, but forever
&lt;/h2&gt;

&lt;p&gt;Some people say that LLMs are like junior programmers because you have to give them proper guidance about everything. This analogy is true up to a certain point. The main difference is that a human junior developer becomes an expert over time. They make better decisions after making several mistakes, they generate trust within the team, they develop pride in their work, and eventually, they mentor and coach other developers.&lt;/p&gt;

&lt;p&gt;But LLMs are in a perpetual juniorhood. You need to make sure their agents always have access to the necessary context, and that this context is real and updated. The agent needs access to the right tools in a secure environment, and it might need your assistance at any minute during the task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing the context
&lt;/h2&gt;

&lt;p&gt;As a human, I need to retrieve the necessary information to start working on a task. Because this task is part of a system, I need to understand how the system works and what its functionality is. If I am already familiar with the system, I don't need to do much analysis. I just confirm the outcome I need to accomplish, understand the acceptance criteria, and make sure I have enough information to start the implementation.&lt;/p&gt;

&lt;p&gt;Sadly, agent tools do not yet have the skill to read minds, so I must facilitate the discovery of the context. This context could be in a &lt;code&gt;spec.md&lt;/code&gt; or &lt;code&gt;agent.md&lt;/code&gt; file, a ticket that the agent can access using an MCP server, a &lt;code&gt;README.md&lt;/code&gt;, or just directly in your prompt.&lt;/p&gt;

&lt;p&gt;The environment where the agent runs is also important. It could be your laptop, a server, a Docker container, or a sandbox. Wherever it runs, the environment must mitigate security risks. You need to provide deterministic restrictions, like blocking internet access, restricting it from reading other folders, and managing repository access (for example, denying pushes to &lt;code&gt;main&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Finally, it needs access to the right tools, MCPs, and skills. If you are working on a backend task, you probably don’t need a Playwright MCP. Try to avoid bloating the context with too many skills; make sure you only enable the right ones for the specific task.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Avoid MCP servers as much as possible and prefer CLI tools. For example, use &lt;code&gt;playwright-cli&lt;/code&gt;, &lt;code&gt;jira-cli&lt;/code&gt;, &lt;code&gt;gh&lt;/code&gt;, &lt;code&gt;glab&lt;/code&gt;, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  From human trust to hard evidence
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhlctnzlgvh1i0nvo980u.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhlctnzlgvh1i0nvo980u.webp" alt="Engineer validating evidence" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Verify everything from LLMs&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can’t trust LLM results. They are non-deterministic. Even if you give them the perfect context, you can’t assume everything was done following the rules. Mistakes happen, and they will keep happening.&lt;/p&gt;

&lt;p&gt;The way you verify the results depends on the context. It can vary from running automated tests and Playwright tests, to looking at screenshots and test results. Maybe you need ad-hoc reports showing the impact of the change in a development environment, or you need to run some manual steps. It changes depending on the nature of the task.&lt;/p&gt;

&lt;p&gt;I only review the code after the agent does its own code review. If there is something I can really improve in the implementation, I leave a comment to guide the agent to make it better.&lt;/p&gt;

&lt;p&gt;Later, you need to validate the results live in production, exactly the same way you would if you changed the code manually. You can also use the agent to help monitor that.&lt;/p&gt;

&lt;p&gt;The last step is doing a retro. Save the important learnings. Maybe there was a common error that should become a new rule or a new deterministic tool to verify next time. This makes your next session faster and more token-cost effective.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhg3uwhqrmuky5joxhszp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhg3uwhqrmuky5joxhszp.webp" alt="Agent workflow overview" width="799" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Overview of a common workflow for a development task&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  You can't blame the LLMs
&lt;/h2&gt;

&lt;p&gt;Good software design, low coupling, high cohesion, high-quality code, and simplicity are still vital. Everything you already know about software engineering is still important. You are the human in charge. You make the decisions. Maybe you don’t craft the code line by line, and maybe you don’t review every single line, but you still need to ensure your output has high quality.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
LLMs are not stable yet. Some days you get good results, but some days, especially right before a new model release, you can't believe how dumb they become. I hope model providers improve transparency about the real status of their services. The key is to have multiple providers ready as backups.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Since the start of 2026, I have not produced a single line of code by hand. I rely completely on agentic coding tools powered by LLMs. I focus my effort on making sure these tools have enough context, and I take care to never trust the results they give me. Producing code now is cheaper, but producing high-quality software faster is not yet cheap. I still spend significant time verifying and validating the results. There is no silver bullet yet. I am still learning, and I am still trying to find better ways to develop software.  &lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Java Command-Line Debugging with AI Agent</title>
      <dc:creator>Sebastian Aburto</dc:creator>
      <pubDate>Sun, 07 Jun 2026 10:36:46 +0000</pubDate>
      <link>https://dev.to/saburto/java-command-line-debugging-with-ai-agent-4c4m</link>
      <guid>https://dev.to/saburto/java-command-line-debugging-with-ai-agent-4c4m</guid>
      <description>&lt;p&gt;When you need to track down a complex bug, your first instinct is to reach for a visual debugger. You set a breakpoint, step through the logic, and inspect the stack trace. But in an era of agentic coding, IDEs and visual debuggers are a bottleneck.&lt;/p&gt;

&lt;p&gt;IDEs and their visual debuggers in the agentic coding era are not an option anymore 🙅 &lt;/p&gt;

&lt;p&gt;The best next option is adding some logs as breadcrumbs to try to understand what is happening&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Entering processPayment()"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isValid&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Processing valid payment: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;paymentId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a natural approach, but the feedback loop is slow in &lt;code&gt;Java&lt;/code&gt;. Each change means compiling, packaging, and running again, or at best, a hot-reload. You make many code modifications just to understand what happens, but those print statements only work in your own code.&lt;/p&gt;

&lt;p&gt;I need something my agent can use, the same way I use a visual debugger.&lt;/p&gt;

&lt;h2&gt;
  
  
  JDB: The built-in command-line debugger for Java
&lt;/h2&gt;

&lt;p&gt;I gave &lt;code&gt;jdb&lt;/code&gt; a try, the command-line debugger that was always there (since jdk 1).&lt;/p&gt;

&lt;p&gt;You can connect to a jvm that is running with the debugger options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-agentlib&lt;/span&gt;:jdwp&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dt_socket,server&lt;span class="o"&gt;=&lt;/span&gt;y,suspend&lt;span class="o"&gt;=&lt;/span&gt;n,address&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;:5005 MyClass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can connect using that port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jdb &lt;span class="nt"&gt;-attach&lt;/span&gt; 5005
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an interactive tool where you send commands like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add breakpoints: &lt;code&gt;stop at com.saburto.Bar:46&lt;/code&gt; or by method &lt;code&gt;stop at com.saburto.Bar.getAllLedgers&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;steps: &lt;code&gt;step&lt;/code&gt;, &lt;code&gt;step up&lt;/code&gt;,  &lt;code&gt;stepi&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;cont&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;get variables info: &lt;code&gt;print&lt;/code&gt;, &lt;code&gt;dump&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt;, &lt;code&gt;locals&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;threads: &lt;code&gt;where&lt;/code&gt;, &lt;code&gt;threadgroups&lt;/code&gt;, &lt;code&gt;up&lt;/code&gt;, &lt;code&gt;down&lt;/code&gt;, &lt;code&gt;kill&lt;/code&gt;, &lt;code&gt;interrupt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;show source code: &lt;code&gt;use&lt;/code&gt; to add the source code path, &lt;code&gt;list&lt;/code&gt; to show the code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source code looks clean in the output of the coding agent&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;  &lt;span class="mi"&gt;42&lt;/span&gt;    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;
  &lt;span class="mi"&gt;43&lt;/span&gt;    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PageResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LedgerResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getAllLedgers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="mi"&gt;44&lt;/span&gt;            &lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"20"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;45&lt;/span&gt;
  &lt;span class="mi"&gt;46&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;     &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ledgers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ledgerService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAllLedgers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="mi"&gt;47&lt;/span&gt;
  &lt;span class="mi"&gt;48&lt;/span&gt;        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="mi"&gt;49&lt;/span&gt;                &lt;span class="n"&gt;ledgers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContent&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;mapper:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;toLedgerResponse&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="mi"&gt;50&lt;/span&gt;
  &lt;span class="mi"&gt;51&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PageResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ledgers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTotalElements&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ledgers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTotalPages&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Let your agent know the tools
&lt;/h3&gt;

&lt;p&gt;Frontier models are intelligent enough to figure out how to call &lt;code&gt;jdb&lt;/code&gt;, but if you want to lend a hand, create a skill using &lt;code&gt;man jdb&lt;/code&gt; to document the key commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenarios
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Dump a request payload&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Attach jdb to my app at port 5005, set a breakpoint at &lt;code&gt;LedgerController.getAllLedgers&lt;/code&gt;, trigger &lt;code&gt;curl localhost:8080/api/ledgers&lt;/code&gt;, and dump every field of the &lt;code&gt;ledgers&lt;/code&gt; Page object. I want to see the actual database records returned.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Trace a variable's mutations&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Set a breakpoint in &lt;code&gt;PaymentService.process&lt;/code&gt;, step through line by line with &lt;code&gt;next&lt;/code&gt;, and dump the &lt;code&gt;payment&lt;/code&gt; variable after each line. I want to see how the status field changes as it moves through validation, enrichment, and persistence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Inspect concurrency at a checkpoint&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Break at the top of my scheduled job in &lt;code&gt;ReconciliationScheduler.run&lt;/code&gt;, trigger a full thread dump with &lt;code&gt;where all&lt;/code&gt;, and show me what every other thread is doing at that moment. Any thread blocked on a lock?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Evaluate an expression in place&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Set a breakpoint at line 89 of &lt;code&gt;InvoiceCalculator&lt;/code&gt;, and when it hits, run &lt;code&gt;eval invoice.getLineItems().stream().mapToDouble(LineItem::getTotal).sum()&lt;/code&gt;. I want to verify the line-item math without adding a log line, rebuilding, and waiting for Maven.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Debug exception state without re-deploying&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Break inside the catch block of &lt;code&gt;PaymentGateway.submit&lt;/code&gt; (line 203), send a payment request with a bad card number, and when the breakpoint fires, dump the exception's message, cause chain, and the local variables. I want to see exactly what the gateway rejected and what state the request was in.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These prompts share a common shape: pick a breakpoint, trigger the code path, and let the agent extract the data. The agent handles the timing, the jdb commands, and the output parsing, and you get the answer in your terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Checking variables in a request
&lt;/h3&gt;

&lt;p&gt;For a Java application, you need to start the system with the proper arguments: &lt;code&gt;java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;spring-boot&lt;/strong&gt; application using &lt;code&gt;mvn&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn spring-boot:run &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.run.jvmArguments&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then connect by attaching to the port: &lt;code&gt;jdb -attach 5005&lt;/code&gt;, and that is it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;jdb&lt;/code&gt; is interactive by design, but in an agentic workflow the session must run hands-off, with the HTTP request firing while the debugger is attached and waiting.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
This is only an example of how my agent generated the script to use the debugger. Depending on your case it may be different; let your agent create the right script.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because the agent cannot type commands interactively into a debugger prompt, we pipeline the commands through standard input instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/projects/my-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /tmp/jdb-output.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"stop in com.saburto.ledger.controller.LedgerController.getAllLedgers"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cont"&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;5
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"next"&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;1
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"dump ledgers.content.elementData"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"dump ledgers.content.elementData[0]"&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;2
&lt;span class="o"&gt;)&lt;/span&gt; | &lt;span class="nb"&gt;timeout &lt;/span&gt;30 jdb &lt;span class="nt"&gt;-attach&lt;/span&gt; 5005 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/jdb-output.txt 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;span class="nv"&gt;JDB_PID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;2
curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:8080/api/v1/ledgers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;./scripts/get-token.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nv"&gt;$JDB_PID&lt;/span&gt; 2&amp;gt;/dev/null
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"JDB_EXIT=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;( echo ... echo ... )&lt;/code&gt; subshell pipes a sequence of &lt;code&gt;jdb&lt;/code&gt; commands into the debugger. Each command goes in at the right time: set the breakpoint, continue, wait 5 seconds for the HTTP request to arrive and hit the breakpoint, then step over the assignment and dump the ledger data.&lt;/li&gt;
&lt;li&gt;The whole thing runs in the &lt;strong&gt;background&lt;/strong&gt; (&lt;code&gt;&amp;amp;&lt;/code&gt;), with its PID captured.&lt;/li&gt;
&lt;li&gt;After a 2-second head start, &lt;code&gt;curl&lt;/code&gt; triggers the REST endpoint that runs &lt;code&gt;getAllLedgers()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The script then waits for jdb to finish and reports the exit code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent reads &lt;code&gt;/tmp/jdb-output.txt&lt;/code&gt; afterward.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;next&lt;/code&gt; command steps past the &lt;code&gt;var ledgers = ...&lt;/code&gt; assignment before the dumps run. The &lt;code&gt;sleep&lt;/code&gt; calls pace the commands so they arrive after the breakpoint fires. The agent reads the output file and reconstructs the data.&lt;/p&gt;

&lt;p&gt;Now we see each &lt;code&gt;LedgerEntity&lt;/code&gt; instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ledgers.content.elementData[0] = {
    id: instance of java.util.UUID(id=15654)
    name: "Basic Transactions Ledger"
    createdAt: instance of java.time.Instant(id=15670)
    isNew: false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
Use &lt;strong&gt;&lt;code&gt;tmux&lt;/code&gt;&lt;/strong&gt; as the bridge between agent and debugger. Tell the agent to open a new panel or window in tmux and send all debugging commands there while keeping your coding agent open in the original pane. This is the pattern we use throughout this post, including the SQL extraction later, to keep the agent and &lt;code&gt;jdb&lt;/code&gt; running in parallel.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Printing the Header of the Http Request
&lt;/h3&gt;

&lt;p&gt;Here is an example. Without touching a single line of code, I asked the agent to show me the HTTP request headers hitting a controller method. After a few tries, it delivered exactly what I needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request Headers at LedgerController.getAllLedgers() (line 46, bci=0)

 ┌───┬───────────────┬─────────────────────────────────────────────────────────────┐
 │ # │ Header        │ Value                                                       │
 ├───┼───────────────┼─────────────────────────────────────────────────────────────┤
 │ 0 │ Host          │ localhost:8080                                              │
 ├───┼───────────────┼─────────────────────────────────────────────────────────────┤
 │ 1 │ User-Agent    │ curl/8.5.0                                                  │
 ├───┼───────────────┼─────────────────────────────────────────────────────────────┤
 │ 2 │ Accept        │ */*                                                         │
 ├───┼───────────────┼─────────────────────────────────────────────────────────────┤
 │ 3 │ Authorization │ Bearer                                                      │
 │   │               │ eyJraWQiOiJkZWZhdWx0IiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ. │
 │   │               │ ..                                                          │
 └───┴───────────────┴─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How I got there (wrapper chain)
&lt;/h4&gt;

&lt;p&gt;The request object at &lt;code&gt;DispatcherServlet.doDispatch&lt;/code&gt; frame 15 goes through 5 nested &lt;code&gt;.request&lt;/code&gt; fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SecurityContextHolderAwareRequestWrapper
  → HeaderWriterFilter$HeaderWriterRequest
    → (firewall wrapper)
      → RequestFacade
        → Request
          → coyoteRequest.headers  (MimeHeaders, count=4)
            → headers[0..3]        (MimeHeaderField with nameB/valueB MessageBytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No code changes, no rebuilds, no restart. Just the debugger, the agent, and a &lt;code&gt;tmux&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced debugging: chasing the SQL
&lt;/h2&gt;

&lt;p&gt;Sometimes you need to dig deeper. I wanted to see the exact SQL that Spring Data JDBC sends, straight from the PostgreSQL wire. I started at the high level (&lt;code&gt;LedgerController.getAllLedgers&lt;/code&gt;) and worked down through the stack, trying breakpoints until the right one stuck:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────────┬────────┐
│ Layer                                                │ Breakpoint tried                                                                   │ Result │
├──────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┼────────┤
│ java.sql.Connection.prepareStatement(String)         │ Failed — Connection is an interface, jdb can't break on interfaces                 │        │
├──────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┼────────┤
│ org.postgresql.jdbc.PgConnection.prepareStatement    │ Never hit — HikariCP proxies the connection                                        │        │
├──────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┼────────┤
│ com.zaxxer.hikari.pool.ProxyPreparedStatement.&amp;lt;init&amp;gt; │ ✅ Hit — the Hikari wrapper constructor receives the real statement as an argument │        │
└──────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────────┴────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The key breakpoint
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stop in com.zaxxer.hikari.pool.ProxyPreparedStatement.&amp;lt;init&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fires every time HikariCP creates a prepared statement. The constructor signature accepts a statement parameter, the real &lt;code&gt;PgPreparedStatement&lt;/code&gt; from the PostgreSQL driver.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extract the SQL from the driver internals
&lt;/h3&gt;

&lt;p&gt;At the breakpoint, the statement variable is a &lt;code&gt;PgPreparedStatement&lt;/code&gt; which has a &lt;code&gt;preparedQuery&lt;/code&gt; field of type &lt;code&gt;CachedQuery&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;print&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;preparedQuery&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nc"&gt;CachedQuery&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="n"&gt;executeCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s"&gt;"ledger_ledgers"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isFunction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CachedQuery.toString()&lt;/code&gt; conveniently includes the full SQL string.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending commands to a tmux pane
&lt;/h3&gt;

&lt;p&gt;Since jdb ran interactively in a tmux pane, I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; 3 &lt;span class="s1"&gt;'print statement.preparedQuery'&lt;/span&gt; Enter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary of the chain
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl → Tomcat → Spring MVC → LedgerController.getAllLedgers()
  → LedgerService → PagingAndSortingRepository.findAll(Pageable)
    → Spring Data JDBC → JdbcTemplate
      → HikariCP → ProxyPreparedStatement.&amp;lt;init&amp;gt;(conn, PgPreparedStatement)
                                                   ↑
                                         statement.preparedQuery = the SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Final Response
&lt;/h4&gt;

&lt;p&gt;This is what it looks like the final response:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SQL Queries Executed for &lt;code&gt;GET /api/v1/ledgers&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From the &lt;code&gt;ProxyPreparedStatement.&amp;lt;init&amp;gt;&lt;/code&gt; breakpoint on &lt;code&gt;PgPreparedStatement&lt;/code&gt;:&lt;/p&gt;
&lt;h5&gt;
  
  
  Query 1: COUNT (for pagination metadata)
&lt;/h5&gt;


&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"ledger_ledgers"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;em&gt;(Earlier runs captured this: Spring Data JDBC executes the count first to populate &lt;code&gt;PageImpl.totalElements&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  Query 2: SELECT data (with LIMIT)
&lt;/h5&gt;


&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"ledger_ledgers"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="nv"&gt;"ledger_ledgers"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="nv"&gt;"ledger_ledgers"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"created_at"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"created_at"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"ledger_ledgers"&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Agentic debugging is powerful. You get deep insights into your Java system's state in seconds, entirely hands-off. The days of manually hitting F10 to step through line by line are behind us.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;jdb&lt;/code&gt; is just for &lt;code&gt;Java&lt;/code&gt;, the same pattern applies to &lt;code&gt;gdb&lt;/code&gt; for C/C++, &lt;code&gt;pdb&lt;/code&gt; for Python, &lt;code&gt;dlv&lt;/code&gt; for Go, or any debugger that exposes a command interface. If you can use in a terminal, the agent can use it.&lt;/p&gt;

&lt;p&gt;So, do you still prefer hitting F9, F10, F11 to step through your code?&lt;/p&gt;

</description>
      <category>java</category>
      <category>ai</category>
      <category>code</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
