<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://feeds.feedblitz.com/feedblitz_rss.xslt"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:webfeeds="http://webfeeds.org/rss/1.0"  xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
<channel>
	<title>Baeldung</title>
	<atom:link href="https://www.baeldung.com/feed" rel="self" type="application/rss+xml" />
	<link>https://www.baeldung.com</link>
	<description>Java, Spring and Web Development tutorials</description>
	<lastBuildDate>Mon, 20 Jul 2026 15:55:06 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
<meta xmlns="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
<item>
<feedburner:origLink>https://www.baeldung.com/java-selenium-webdriver-submit-vs-click</feedburner:origLink>
		<title>Selenium Webdriver submit() vs click()</title>
		<link>https://feeds.feedblitz.com/~/961067897/0/baeldung~Selenium-Webdriver-submit-vs-click</link>
					<comments>https://feeds.feedblitz.com/~/961067897/0/baeldung~Selenium-Webdriver-submit-vs-click#respond</comments>
		
		<dc:creator><![CDATA[Aidela Karamyan]]></dc:creator>
		<pubDate>Mon, 20 Jul 2026 15:55:06 +0000</pubDate>
				<category><![CDATA[Java Web]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[JUnit]]></category>
		<category><![CDATA[Selenium]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-selenium-webdriver-submit-vs-click</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" fetchpriority="high" /><p>Learn how the Selenium <em>click()</em> and <em>submit()</em> methods differ, how they work under the hood, and why <em>click()</em> is the recommended approach for modern web automation.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/961067897/0/baeldung~Selenium-Webdriver-submit-vs-click">Selenium Webdriver <em>submit()</em> vs <em>click()</em></a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/961067897/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/961067897/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2ffeatured-images-selenium-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/961067897/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/961067897/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/961067897/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-selenium-webdriver-submit-vs-click#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-selenium-webdriver-submit-vs-click/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium.jpg 1200w" sizes="(max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>It&#8217;s hard to imagine doing web application automation without needing to click on anything or submit a form. Although these actions appear to be simple, there are nuances to be aware of.</p>
<p>In this tutorial, we&#8217;ll look into how <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/selenium-automated-browser-testing">Selenium</a> handles <em>click()</em> and <em>submit()</em>, and which one we should choose when working with forms.</p>
<h2 id="bd-prerequisites-and-setup" data-id="prerequisites-and-setup">2. Prerequisites and Setup</h2>
<div class="bd-anchor" id="prerequisites-and-setup"></div>
<p>First, let&#8217;s set up a simple Selenium test.</p>
<p>To that end, <strong>we assume basic familiarity with several concepts</strong>:</p>
<ul>
<li>Java development environment</li>
<li>initializing a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/maven">Maven</a> project</li>
<li>running tests</li>
</ul>
<p>For the example code, we use a quickstart Maven project and add Selenium WebDriver, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/selenium-webdrivermanager">WebDriver Manager</a>, and JUnit packages to the <em>pom.xml</em> file:</p>
<pre><code class="xml">&lt;dependencies&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.seleniumhq.selenium&lt;/groupId&gt;
    &lt;artifactId&gt;selenium-java&lt;/artifactId&gt;
    &lt;version&gt;4.46.0&lt;/version&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
      &lt;groupId&gt;io.github.bonigarcia&lt;/groupId&gt;
      &lt;artifactId&gt;webdrivermanager&lt;/artifactId&gt;
      &lt;version&gt;6.3.0&lt;/version&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
    &lt;artifactId&gt;junit-jupiter&lt;/artifactId&gt;
    &lt;version&gt;5.11&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
  &lt;/dependency&gt;
&lt;/dependencies&gt;
</code></pre>
<p><strong>Let&#8217;s leverage WebDriver Manager to automatically download and set up the correct version of the browser driver we use in the tests</strong>. Furthermore, we employ JUnit to annotate and organize the sample code.</p>
<p>Next, we create a sample test file in the <em>/test/java</em> directory of the project. Let&#8217;s name it <em>ContactTest.java</em> and add setup code to get started:</p>
<pre><code class="java">public class ContactTest {
    private WebDriver driver;
    @BeforeEach
    void setup() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
    }
    @AfterEach
    void teardown() {
        if (driver != null) {
            driver.quit();
        }
    }
    // add tests here
}
</code></pre>
<p>Here in the setup and teardown functions, we initialize the Chrome driver before and quit the driver after each test.</p>
<h2 id="bd-how-selenium-works" data-id="how-selenium-works">3. How Selenium Works</h2>
<div class="bd-anchor" id="how-selenium-works"></div>
<p><strong>Selenium WebDriver executes commands by forwarding them to the respective browser driver</strong> (e.g., <em>chromedriver</em> for Chrome, or <em>geckodriver</em> for Firefox). In essence, it&#8217;s a messenger sending HTTP requests.</p>
<p>On the other hand, <strong>browsers guard sensitive actions, also known as <em>activation behavior</em>, like <em>click()</em> and <em>submit()</em></strong>. The goal is, for example, to prevent a malicious script from triggering a click multiple times in an attempt to exploit user actions. This security measure is implemented with the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted"><em>isTrusted</em></a> flag. All events have this flag to differentiate between human and script interactions. Events dispatched directly by JavaScript (such as those via <em>dispatchEvent()</em>) have <em>isTrusted</em> set to false, whereas WebDriver user interaction commands such as <em>click()</em> generate trusted browser events.</p>
<p><strong>Since Selenium uses the WebDriver protocol, it has elevated privileges</strong>, including triggering a click multiple times without getting blocked. Essentially, events Selenium triggers have their <em>isTrusted</em> flag set to <em>true</em> when the browser ystem processes them.</p>
<h2 id="bd-exploring-click" data-id="exploring-click">4. Exploring <em>click()</em></h2>
<div class="bd-anchor" id="exploring-click"></div>
<p>Just like when operating manually, we can call <em>click()</em> on any clickable element.</p>
<h3 id="bd-1-implementation-details" data-id="1-implementation-details">4.1. Implementation Details</h3>
<div class="bd-anchor" id="1-implementation-details"></div>
<p>By design, it mimics human behavior. Therefore, <strong>the element needs to be enabled, visible, and have a non-zero size for a click to work</strong>.</p>
<p><strong>When Selenium sends the click message, the browser driver executes a series of actions</strong>:</p>
<ol>
<li>ensures the element is clickable</li>
<li>scrolls to make the element visible</li>
<li>calculates the element&#8217;s central coordinates for dispatching the events</li>
<li>dispatches <em>mouse down</em> and <em>mouse up</em> events</li>
<li>dispatches a <em>click</em> event</li>
</ol>
<p>Let&#8217;s look at the <em>click()</em> implementation in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/SeleniumHQ/selenium/blob/trunk/java/src/org/openqa/selenium/remote/RemoteWebElement.java">Selenium WebDriver code</a>:</p>
<pre><code class="java">@Override
public void click() {
  execute(DriverCommand.CLICK_ELEMENT(id));
}
</code></pre>
<p>According to the documentation, <em>click()</em> can throw several exceptions:</p>
<ul>
<li><em>NoSuchElementException</em> when the element is missing</li>
<li><em>ElementNotInteractableException</em> when the element is hidden, disabled, or has zero size</li>
<li><em>ElementClickInterceptedException</em> when another element obscures the target</li>
<li><em>StaleElementReferenceException</em> when a reference is no longer valid</li>
</ul>
<p>For a more controlled click, we can use the Selenium <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.selenium.dev/documentation/webdriver/actions_api/">Actions API</a>:</p>
<pre><code class="java">new Actions(driver).moveToElement(submitButton).click().perform();
</code></pre>
<p>Nonetheless, unlike the regular <em>click()</em>, it doesn&#8217;t throw an exception when another element obscures the target element. Notably,<strong> <em>Actions</em> always performs a click at the specified coordinates regardless of whether the click ends up on a different element</strong>.</p>
<h3 id="bd-2-example" data-id="2-example">4.2. Example</h3>
<div class="bd-anchor" id="2-example"></div>
<p>To see <em>click()</em> in action, <strong>we implement and run a simple test that fills a form, then clicks the submit button</strong>:</p>
<pre><code class="java">@Test
void submitFormWithClick() throws InterruptedException {
    try {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        driver.get("https://www.baeldung.com/contact");
        WebElement nameField = wait.until(
            ExpectedConditions.presenceOfElementLocated(By.name("your-name"))
        );
        nameField.sendKeys("Jane Doe");
        TimeUnit.SECONDS.sleep(1);
        WebElement emailField = driver.findElement(By.name("your-email"));
        emailField.sendKeys("janedoe@mail.com");
        TimeUnit.SECONDS.sleep(1);
        WebElement messageField = driver.findElement(By.name("your-message"));
        messageField.sendKeys("Hello, this is an automated test message.");
        TimeUnit.SECONDS.sleep(1);
        WebElement submitButton = wait.until(
          ExpectedConditions.elementToBeClickable(By.cssSelector("input[type='submit']"))
        ); 
        submitButton.click();
        TimeUnit.SECONDS.sleep(2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
</code></pre>
<p>In this case, we use <em>TimeUnit.SECONDS.sleep(1)</em> to follow along the test execution. After filling the form, the page scrolls down to show the submit button before clicking it:</p>
<img decoding="async" class="alignleft size-full wp-image-254939" src="https://www.baeldung.com/wp-content/uploads/2026/07/selenium-form-click-animation.gif" alt="Selenium form animation with click" />
<p>Unfortunately, the submit button doesn&#8217;t have a click animation to demonstrate UI effects.</p>
<h2 id="bd-exploring-submit" data-id="exploring-submit">5. Exploring <em>submit()</em></h2>
<div class="bd-anchor" id="exploring-submit"></div>
<p>As expected, <em>submit()</em> is more limited than <em>click()</em> as its only purpose is to submit a form.</p>
<h3 id="bd-1-implementation-details-1" data-id="1-implementation-details-1">5.1. Implementation Details</h3>
<div class="bd-anchor" id="1-implementation-details-1"></div>
<p>We can call <i>submit()</i> on the form element to trigger the form submit functionality. In addition, <strong>we can call <em>submit()</em> on any element within the form</strong>. In the latter case, the parent form element is found automatically, and Selenium calls submit on the form.</p>
<p>When no form exists, the function should throw a <em>NoSuchElementException</em>. In reality, it often throws a different exception — <em>UnsupportedOperationException</em>. Why that happens is fairly obvious when we look at the <em>submit()</em> implementation:</p>
<pre><code class="java">@Override
public void submit() {
  try {
    execute(DriverCommand.SUBMIT_ELEMENT(id));
  } catch (JavascriptException ex) {
    String message = "To submit an element, it must be nested inside a form element";
    throw new UnsupportedOperationException(message);
  }
}
</code></pre>
<p>The code looks similar to the <em>click()</em> code. However, <strong>when calling <em>submit()</em>, there&#8217;s no visual feedback, since we&#8217;re directly executing submit on the form</strong>. No submit or button click events trigger. As a result, any additional functionality relying on those event handlers is completely bypassed.</p>
<p>Furthermore, <strong>the submit button or another form element on which we called <em>submit()</em> doesn&#8217;t need to be visible or clickable</strong>.</p>
<h3 id="bd-2-example-1" data-id="2-example-1">5.2. Example</h3>
<div class="bd-anchor" id="2-example-1"></div>
<p>Let&#8217;s add another test by copying the previous test, renaming it, changing <em>click()</em> to <em>submit()</em>, and running the result:</p>
<pre><code class="java">@Test
void submitFormWithSubmit() throws InterruptedException {
    try {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        driver.get("https://www.baeldung.com/contact");
        WebElement nameField = wait.until(
            ExpectedConditions.presenceOfElementLocated(By.name("your-name"))
        );
        nameField.sendKeys("Jane Doe");
        TimeUnit.SECONDS.sleep(1);
        WebElement emailField = driver.findElement(By.name("your-email"));
        emailField.sendKeys("janedoe@mail.com");
        TimeUnit.SECONDS.sleep(1);
        WebElement messageField = driver.findElement(By.name("your-message"));
        messageField.sendKeys("Hello, this is an automated test message.");
        TimeUnit.SECONDS.sleep(1);
        WebElement submitButton = wait.until(
            ExpectedConditions.elementToBeClickable(By.cssSelector("input[type='submit']"))
        ); 
        submitButton.submit();
        TimeUnit.SECONDS.sleep(2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
</code></pre>
<p>As expected, the page didn&#8217;t scroll to show the submit button:
<br>
<img decoding="async" class="alignleft size-full wp-image-254938" src="https://www.baeldung.com/wp-content/uploads/2026/07/selenium-form-submit-animation.gif" alt="Selenium form animation with submit" /></p>
<p>In essence, the <em>submit()</em> behavior doesn&#8217;t mimic normal user interaction at all. The end result is the same as if we called <em>click()</em> on the submit button. However, the interaction is purely programmatic.</p>
<h2 id="bd-submit-deprecation" data-id="submit-deprecation">6. <em>submit()</em> Deprecation</h2>
<div class="bd-anchor" id="submit-deprecation"></div>
<p>In modern Selenium (version 4 and later), <em>submit()</em> is discouraged, although it&#8217;s not yet obsolete and the code doesn&#8217;t flag it as deprecated. In fact, <strong>the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.selenium.dev/documentation/webdriver/elements/interactions/">official Selenium documentation</a> advises against using <em>submit()</em></strong>. The reason is the lack of support by browser drivers.</p>
<p>Earlier versions of Selenium used a dedicated endpoint for this. However, starting from Selenium 3, browser vendors handle the driver implementations. Some immediately started to strictly adhere to the new official <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://w3c.github.io/webdriver/">W3C WebDriver Protocol</a>, which has no <em>submit()</em> specification. That broke backward compatibility, forcing Selenium to implement a workaround.</p>
<p>Currently, instead of the old submit message, <i>submit()</i> sends a message to a different endpoint to execute a custom script. Because the browser drivers don&#8217;t handle this code, the event has the <em>isTrusted</em> flag set to <em>false</em>.</p>
<h2 id="bd-click-vs-submit" data-id="click-vs-submit">7. <em>click()</em> vs <em>submit()</em></h2>
<div class="bd-anchor" id="click-vs-submit"></div>
<p>Obviously, <strong>since <em>submit()</em> is no longer recommended, we should use <em>click()</em> to submit forms</strong>. However, let&#8217;s ignore this fact for a moment and analyze the reasons why <em>click()</em> is a better choice.</p>
<p><strong>At the core of the Selenium philosophy, and UI automation in general, lies the goal to automate what a real user would do as closely as possible</strong>. If a real user won&#8217;t be able to interact with the element to submit the form, then a passing test that programmatically achieves submitting hides a potential usability issue. On the other hand, <em>click()</em> achieves the core goal perfectly.</p>
<p>Moreover, modern web applications don&#8217;t always follow a traditional form structure and have a root <em>&lt;form&gt;</em> element. Unlike <em>submit()</em>, <em>click()</em> is more flexible and works with any UI structure.</p>
<p>Lastly, the security advantage. <strong>Because <em>click()</em> action has its <em>isTrusted</em> flag set to <em>true</em>, it&#8217;s reliable, correctly propagates a linked series of events, and runs no risk of getting flagged by security tools</strong>.</p>
<h2 id="bd-conclusion" data-id="conclusion">8. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we looked at how Selenium operates under the hood. Also, we saw how <em>click()</em> and <em>submit()</em> work, and why we should use <em>click()</em>.</p>
<p>Although there&#8217;s practically no scenario where we would want to use it with modern Selenium, <strong><em>submit()</em> serves an educational goal of highlighting important nuances in automation implementation</strong>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-selenium-webdriver-submit-vs-click">Selenium Webdriver <em>submit()</em> vs <em>click()</em></a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/961067897/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/961067897/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/961067897/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2ffeatured-images-selenium-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/961067897/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/961067897/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/961067897/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-selenium-webdriver-submit-vs-click#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-selenium-webdriver-submit-vs-click/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/961067897/0/baeldung~Selenium-Webdriver-submit-vs-click/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/featured-images-selenium-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-655</feedburner:origLink>
		<title>Java Weekly, Issue 655</title>
		<link>https://feeds.feedblitz.com/~/960937190/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/960937190/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sun, 19 Jul 2026 12:18:31 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204377</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" /><p>A Java movie? Cool. And Spring Cloud Contract has a new home.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/960937190/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 655</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960937190/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960937190/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960937190/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960937190/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960937190/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-655#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-655/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/archaeologist-copilot.html">&gt;&gt; The Archaeologist’s Copilot</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>
<p>Legacy systems don&#8217;t really come mapped. Not usually at least.</p>
<p>This writeup shows how a coding assistant can help investigate an unfamiliar codebase, reconstruct its architecture, and turn scattered evidence into useful documentation. A developer is a central part of this work.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/serviceloader-provider-factory/" target="_blank" rel="noopener"><strong>Making ServiceLoader usable: a provider factory</strong></a> [<span style="color: #800000;">frankel.ch</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/virtual-thread-pinning-field-guide/" target="_blank" rel="noopener"><strong>Your Loom App Quietly Became a Thread Pool Again: A Field Guide to Virtual Thread Pinning</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/i-asked-github-copilot-to-profile-a-java-app-it-found-a-bug-in-my-heap-sizing-and-offered-to-fix-it/" target="_blank" rel="noopener"><strong>I Asked GitHub Copilot to Profile a Java App. It Found a Bug in My Heap Sizing, and Offered to Fix It</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-langchain4j-opa-guardrails/" target="_blank" rel="noopener"><strong>Guardrails with OPA Policies in Quarkus LangChain4j</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/temporal-is-to-your-code-what-a-database-is-to-your-data/" target="_blank" rel="noopener"><strong>Temporal Is to Your Code What a Database Is to Your Data</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/when-not-to-use-event-driven-architecture-eda/" target="_blank" rel="noopener"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f691.png" alt="🚑" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> When NOT TO USE Event-Driven Architecture (EDA)</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/this-cant-possibly-work-what-i-learned-at-a-temporal-io-workshop-on-durable-execution/" target="_blank" rel="noopener"><strong>“This Can’t Possibly Work”: What I Learned at a Temporal.io Workshop</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/foojay-podcast-100-when-a-podcaster-interviews-podcasters-and-what-they-all-have-in-common/" target="_blank" rel="noopener"><strong>Foojay Podcast #100: When a Podcaster Interviews Podcasters, and What They All Have in Common</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://nipafx.dev/talk-java-valhalla/" target="_blank" rel="noopener"><strong>Valhalla, Now!</strong></a> [<span style="color: #800000;">nipafx.dev</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-insights-254-ddd-hexagonal-architecture-part2/" target="_blank" rel="noopener"><strong>Quarkus Insights #254: Domain-Driven Design and Hexagonal Architecture &#8211; Part 2</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/13/spring-office-hours-podcast-S5E18/" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E18 &#8211; The Latest from OpenAI, Anthropic and Spring AI 2.0</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/boxlang-1-15-0-released-blazing-fast-strings-runtime-portability-and-much-more/" target="_blank" rel="noopener"><strong>BoxLang 1.15.0 Released: Blazing Fast Strings, Runtime Portability, and much more</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-37-3-released/" target="_blank" rel="noopener"><strong>Quarkus 3.37.3 &#8211; Maintenance release</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jhipster/generator-jhipster/releases/tag/v9.2.0" target="_blank" rel="noopener"><strong>JHipster v9.2.0</strong></a> [<span style="color: #800000;">github.com/jhipster</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.1.5" target="_blank" rel="noopener"><strong>Vert.x 5.1.5</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/4.5.30" target="_blank" rel="noopener"><strong>4.5.30</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.2.1" target="_blank" rel="noopener"><strong>Grails 7.2.1</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.1.4" target="_blank" rel="noopener"><strong>7.1.4</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.0.14" target="_blank" rel="noopener"><strong>7.0.14</strong></a> [<span style="color: #800000;">github.com/apache</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.0.6" target="_blank" rel="noopener"><strong>Micronaut Core 5.0.6</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.6" target="_blank" rel="noopener"><strong>5.1.6</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v3.10.10" target="_blank" rel="noopener"><strong>3.10.10</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/PiranhaCMS/piranha.core/releases/tag/v12.2" target="_blank" rel="noopener"><strong>Piranha CMS Version 12.2</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/PiranhaCMS/piranha.core/releases/tag/v12.1" target="_blank" rel="noopener"><strong>Version 12.1</strong></a> [<span style="color: #800000;">github.com/PiranhaCMS</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/building-service-topology-at-scale-architecture-challenges-and-lessons-learned-f4b792f3f0d8">&gt;&gt; Building Service Topology at Scale: Architecture, Challenges, and Lessons Learned</a></strong> [<span style="color: #993300;">netflixtechblog.com</span>]</p>
<p>This deep dive explains how Netflix builds a service-topology platform from multiple data sources, resolves conflicting signals, and turns the resulting graph into practical infrastructure. Interesting weekend read.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.vanillajava.blog/2026/07/which-doc-format-is-best-for-ai.html" target="_blank" rel="noopener"><strong>Which Doc Format is Best for AI Specifications?</strong></a> [<span style="color: #800000;">vanillajava.blog</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://lucumr.pocoo.org/2026/7/13/the-tower-keeps-rising/" target="_blank" rel="noopener"><strong>The Tower Keeps Rising</strong></a> [<span style="color: #800000;">pocoo.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.christianposta.com/okta-saml-and-keycloak-for-id-jag-cross-app-access/" target="_blank" rel="noopener"><strong>Okta SAML and Keycloak for ID-JAG Cross App Access</strong></a> [<span style="color: #800000;">christianposta.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/llm-and-dsls.html" target="_blank" rel="noopener"><strong>DSLs Enable Reliable Use of LLMs</strong></a> [<span style="color: #800000;">martinfowler.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/07/15/choosing-the-right-tool-safety-approach-for-coding-agents.html" target="_blank" rel="noopener"><strong>Choosing the right tool safety approach for coding agents</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.medium.com/measuring-the-impact-of-personalized-recommendations-4c26be3a4d96" target="_blank" rel="noopener"><strong>Measuring the Impact of Personalized Recommendations</strong></a> [<span style="color: #800000;">medium.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/07/15/reflections-form-our-ai-event.html" target="_blank" rel="noopener"><strong>Adopting Agentic: Talks and Reflections from Our AI Engineering Event</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p>We&#8217;re now reaching the end of our Summer Sale:</p>
<h4><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/courses">&gt;&gt; Summer Sale &#8211; 30% Off in All Access</a></strong></h4>
<p>If you&#8217;re planning to join, now is a good time to get into the platform and start exploring.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-655">Java Weekly, Issue 655</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/960937190/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960937190/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960937190/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960937190/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960937190/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960937190/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-655#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-655/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/960937190/0/baeldung~Java-Weekly-Issue/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/hibernate-annotationexception-map-non-collection</feedburner:origLink>
		<title>Solving org.hibernate.AnnotationException: Illegal Attempt to Map a Non Collection</title>
		<link>https://feeds.feedblitz.com/~/960891746/0/baeldung~Solving-orghibernateAnnotationException-Illegal-Attempt-to-Map-a-Non-Collection</link>
					<comments>https://feeds.feedblitz.com/~/960891746/0/baeldung~Solving-orghibernateAnnotationException-Illegal-Attempt-to-Map-a-Non-Collection#respond</comments>
		
		<dc:creator><![CDATA[Andrei Branza]]></dc:creator>
		<pubDate>Sun, 19 Jul 2026 01:30:22 +0000</pubDate>
				<category><![CDATA[Persistence]]></category>
		<category><![CDATA[Hibernate]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204318</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to map your entities correctly in Hibernate to avoid getting an AnnotationException</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/960891746/0/baeldung~Solving-orghibernateAnnotationException-Illegal-Attempt-to-Map-a-Non-Collection">Solving org.hibernate.AnnotationException: Illegal Attempt to Map a Non Collection</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960891746/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960891746/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960891746/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960891746/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960891746/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-annotationexception-map-non-collection#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-annotationexception-map-non-collection/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>When we map entity relationships with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/learn-jpa-hibernate">Hibernate</a>, a small mistake in a field declaration can prevent our application from booting up. A common example is the &#8220;<em>org.hibernate.AnnotationException: Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements</em>&#8221; error.</p>
<p>In this tutorial, we&#8217;ll look at why Hibernate throws this exception. Next, we&#8217;ll reproduce it with a simple mapping and walk through a couple of ways to fix it.</p>
<h2 id="bd-understanding-the-exception" data-id="understanding-the-exception">2. Understanding the Exception</h2>
<div class="bd-anchor" id="understanding-the-exception"></div>
<p>Hibernate expects any field annotated with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-one-to-many"><em>@OneToMany</em></a>, <em>@ManyToMany </em>or <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jpa-element-collection">@ElementCollection</a> </em>to be a collection-valued association. In practice, this means we must declare the field using one of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-collections">collection interfaces</a> it recognizes:</p>
<ul>
<li><em><strong>Collection</strong> </em>or <strong><em>List</em></strong>: an ordered group that allows duplicate elements</li>
<li><strong><em>Set</em></strong>: a group that contains only unique elements</li>
<li><em><strong>Map</strong></em>: a set of key-value pairs, useful for associations based on keys</li>
</ul>
<p><strong>The reason for this requirement is that Hibernate manages these collections with its own implementation. </strong>When we persist an <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-entities">entity</a>, Hibernate replaces that field value with a proxy that supports lazy loading and dirty checking. It can only do that if the field type is an interface it controls.</p>
<p>As such, when the annotation is used on a field that isn&#8217;t one of these interfaces, Hibernate can&#8217;t build that proxy. Therefore, it fails fast during startup and throws the <em>AnnotationException</em>.</p>
<h2 id="bd-reproducing-the-exception" data-id="reproducing-the-exception">3. Reproducing the Exception</h2>
<div class="bd-anchor" id="reproducing-the-exception"></div>
<p>Let&#8217;s reproduce the problem with a classic parent-child mapping. First, let&#8217;s define a <em>Comment </em>entity:</p>
<pre><code class="language-java">@Entity
public class Comment {
    @Id
    @GeneratedValue
    private Long id;
    private String text;
    // getters and setters
}</code></pre>
<p>Now, let&#8217;s create a <em>Post </em>entity that owns many comments. Here, purposely declare the field with the concrete <em>ArrayList </em>type:</p>
<pre><code class="language-java">@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;
    @OneToMany
    private ArrayList&lt;Comment&gt; comments = new ArrayList&lt;&gt;();
    // getters and setters
}</code></pre>
<p>When Hibernate scans this mapping during building of the metadata at startup, it throws: <em>org.hibernate.AnnotationException: Illegal attempt to map a non collection as a </em><em>@OneToMany, @ManyToMany or @CollectionOfElements: com.baeldung.Post.comments</em></p>
<p>Note that newer Hibernate versions phrase the same message with <em>@ElementCollection</em> instead of the legacy <em>@CollectionOfElements</em>, but the cause is identical.</p>
<p>Even though <em>ArrayList </em>is technically a list, <strong>Hibernate rejects it because it&#8217;s a concrete class rather than a collection interface.</strong></p>
<h2 id="bd-fixing-the-mapping" data-id="fixing-the-mapping">4. Fixing the Mapping</h2>
<div class="bd-anchor" id="fixing-the-mapping"></div>
<div>
<div>The fix is straightforward: we declare the field using a collection interface instead of the implementation. So, let&#8217;s change the type from <em>ArrayList</em> to <em>List</em>:</div>
<div>
<pre><code class="language-java">@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;
    @OneToMany
    private List&lt;Comment&gt; comments = new ArrayList&lt;&gt;();
    // getters and setters
}</code></pre>
<p>We should notice that we still initialize the field with <em>new ArrayList&lt;&gt;(). </em><strong>That&#8217;s not a problem, because only the type of the declared field needs to be an interface. Hibernate will switch the value for its own implementation once the entity becomes managed.</strong></p>
<p>We can apply the same rule to the other annotations. For instance, a <em>Set </em>pairs well with <em>@ManyToMany </em>when we want to avoid duplicates. <em>@ElementCollection </em>accepts any of the standard collection interfaces. The thing to remember is that the compile time type of the field must be an interface, not the class we assign to it.</p>
<p>Now, when it comes to which interface to pick, a <em>List </em>is the common default when order or duplicates matter, while a <em>Set </em>is a good fit when each element must be unique. Either way, the mapping stays valid as long as the field type remains an interface.</p>
<h2 id="bd-a-related-cause-mapping-a-single-reference" data-id="a-related-cause-mapping-a-single-reference">5. A Related Cause: Mapping a Single Reference</h2>
<div class="bd-anchor" id="a-related-cause-mapping-a-single-reference"></div>
<p><strong>We can encounter the same exception when we place a collection annotation on a single-valued field</strong>. For instance, a comment belongs to exactly one post, so we might mistakenly write:</p>
<pre><code class="language-java">@Entity
public class Comment {
    @Id
    @GeneratedValue
    private Long id;
    @OneToMany
    private Post post;
}</code></pre>
<p>In this case, <em>post </em>holds a single <em>Post</em>, not a collection, so Hibernate throws the same <em>AnnotationException. </em>However, in this case, switching to an interface type won&#8217;t help, since the relationship itself is incorrectly modeled.</p>
<p>Instead, we should choose the annotation that matches the cardinality. Considering that many comments map to a single post, <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-annotationexception-manytoone-column">@ManyToOne</a> </em>is the correct choice:</p>
<pre><code class="language-java">@Entity
public class Comment {
    @Id
    @GeneratedValue
    private Long id;
    @ManyToOne
    private Post post;
}</code></pre>
<p>Similarly, if the field represents a one-to-one association, we&#8217;d choose <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-one-to-one">@OneToOne</a> </em>instead<em>.</em></p>
<p>This also ties together the two fixes we&#8217;ve seen. In a bidirectional relationship, the <em>@ManyToOne </em>side is the owner of the association, while the <em>Post </em>entity maps the inverse side with <em>@OneToMany(mappedBy = &#8220;post&#8221;). </em>Here, the <em>@OneToMany </em>field is still a <em>List, </em>and the <em>@ManyToOne </em>field is still a single reference. As such, each annotation matches the type it is assigned to and Hibernate build the mapping without complaints.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we&#8217;ve looked at the Hibernate <em>AnnotationException </em>that warns us about mapping an improper type.</p>
<p>The root of the problem is a mismatch between an annotation and a field type. When we use <em>@OneToMany, @ManyToMany, </em>or <em>@ElementCollection, </em>the field must be a collection interface like <em>List </em>or <em>Set, </em>and not a concrete class such as <em>ArrayList. </em>And when the field actually holds a single reference, we should reach for <em>@ManyToOne </em>or <em>@OneToOne </em>instead.</p>
<p>By matching the annotation to the field&#8217;s cardinality and always declaring collections through their interface, we keep our mappings valid and let Hibernate deal with their management.</p>
<p>As always, the code is available over on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate-exceptions-2">Github</a>.</p>
</div>
</div>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-annotationexception-map-non-collection">Solving org.hibernate.AnnotationException: Illegal Attempt to Map a Non Collection</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/960891746/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960891746/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960891746/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960891746/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960891746/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960891746/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-annotationexception-map-non-collection#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-annotationexception-map-non-collection/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/960891746/0/baeldung~Solving-orghibernateAnnotationException-Illegal-Attempt-to-Map-a-Non-Collection/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-get-own-pid</feedburner:origLink>
		<title>How Does a Java Program Get Its Own Process ID</title>
		<link>https://feeds.feedblitz.com/~/960370808/0/baeldung~How-Does-a-Java-Program-Get-Its-Own-Process-ID</link>
					<comments>https://feeds.feedblitz.com/~/960370808/0/baeldung~How-Does-a-Java-Program-Get-Its-Own-Process-ID#respond</comments>
		
		<dc:creator><![CDATA[Zachary Bouhannana]]></dc:creator>
		<pubDate>Tue, 14 Jul 2026 12:19:11 +0000</pubDate>
				<category><![CDATA[Core Java]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[>= Java 8]]></category>
		<category><![CDATA[JMX]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-get-own-pid</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to retrieve the current Java process ID using <em>ProcessHandle</em>, <em>RuntimeMXBean</em>, and legacy approaches across different Java versions.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/960370808/0/baeldung~How-Does-a-Java-Program-Get-Its-Own-Process-ID">How Does a Java Program Get Its Own Process ID</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960370808/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960370808/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960370808/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960370808/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960370808/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-get-own-pid#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-get-own-pid/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>Getting the current Java process ID helps with monitoring, logging, and system administration. Before Java 9, no standard API existed for this task. The platform has since filled this gap. Java 9 introduced the <em>ProcessHandle</em> API. Java 10 added <em>getPid()</em> to <em>RuntimeMXBean</em>. However, many older methods remain useful for specific scenarios.</p>
<p>In this tutorial, we&#8217;ll explore multiple ways to get the process ID of the current Java process.</p>
<h2 id="bd-the-pre-java-9-approach-parsing-the-runtimemxbean-name" data-id="the-pre-java-9-approach-parsing-the-runtimemxbean-name">2. The Pre-Java 9 Approach: Parsing the <em>RuntimeMXBean</em> Name</h2>
<div class="bd-anchor" id="the-pre-java-9-approach-parsing-the-runtimemxbean-name"></div>
<p>When using Java 8 or earlier, we can attempt to use <em>ManagementFactory.getRuntimeMXBean().getName()</em> to get the process ID. However, the string we get should be in the format <em>PID@hostname</em>. In this case, <strong>the PID is the numeric portion before the <em>@</em> symbol</strong>.</p>
<p>So, let&#8217;s see how we can extract the PID from such a string:</p>
<pre><code class="language-java">import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
public class ProcessIdUtil {
    
    public static String getProcessIdPreJava9() {
        String runtimeName = ManagementFactory.getRuntimeMXBean().getName();
        int index = runtimeName.indexOf('@');
        if (index &lt; 1) {
            return null;
        }
        return runtimeName.substring(0, index);
    }
}</code></pre>
<p>This approach has a significant limitation. <strong>According to the Java documentation, the returned name string can be any arbitrary string</strong>. The JVM implementation chooses what information to embed. While most JVMs include the PID in this format, we can&#8217;t guarantee it. This means the parsing approach may break on some JVM implementations or future versions.</p>
<h2 id="bd-java-9-processhandle" data-id="java-9-processhandle">3. Java 9+: <em>ProcessHandle</em></h2>
<div class="bd-anchor" id="java-9-processhandle"></div>
<p>Java 9 introduced the <em>ProcessHandle</em> interface as part of JEP 102 (<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-process-api">Java Process API)</a>. <strong>It provides a direct, standardized way to access the native process ID</strong>. Moreover, we can access this interface from <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-9-module-api"><em>java.base</em></a> with no extra dependencies.</p>
<p>The <em>ProcessHandle.current()</em> method returns a handle to the current process, and its <em>pid()</em> method returns the native process ID.</p>
<p>Let&#8217;s see this in action:</p>
<pre><code class="language-java">import java.lang.ProcessHandle;
    public static long getProcessIdJava9Plus() {
        return ProcessHandle.current().pid();
    }
</code></pre>
<p>The <em>ProcessHandle</em> API provides a type-safe <em>long</em> return value instead of a string and is part of the official Java standard library. This means we don&#8217;t need any external dependencies or vendor-specific hacks. Furthermore, the API is designed to be cross-platform.</p>
<h2 id="bd-java-10-runtimemxbeangetpid" data-id="java-10-runtimemxbeangetpid">4. Java 10+: <em>RuntimeMXBean.getPid()</em></h2>
<div class="bd-anchor" id="java-10-runtimemxbeangetpid"></div>
<p>Later, Java 10 added a <em>getPid()</em> default method to the <em>RuntimeMXBean</em> interface. <em>RuntimeMXBean.getPid()</em> also returns the process ID directly as a <em>long</em> value without any string parsing.</p>
<p>Let&#8217;s see this in practice:</p>
<pre><code class="language-java">import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
 
    public static long getProcessIdJava10Plus() {
        return ManagementFactory.getRuntimeMXBean().getPid();
    }</code></pre>
<p>Here, we use one of the cleanest <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-management-extensions">JMX-based</a> approaches. This method is convenient because it uses the familiar <em>ManagementFactory</em> API. <strong>It&#8217;s also a default method, so it&#8217;s available on JVMs through the interface&#8217;s default implementation</strong>.</p>
<h2 id="bd-creating-a-version-aware-utility" data-id="creating-a-version-aware-utility">5. Creating a Version-Aware Utility</h2>
<div class="bd-anchor" id="creating-a-version-aware-utility"></div>
<p>In practice, we can create a single utility method that works across different Java versions.</p>
<p><strong>Let&#8217;s build a utility class that tries the most modern methods first and falls back to older ones as needed</strong>:</p>
<pre><code class="language-java">import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
public class ProcessIdUtil {
    
    public static long getProcessId() {
        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        
        if (isJavaVersionOrGreater(10)) {
            return runtimeMxBean.getPid();
        }
        
        if (isJavaVersionOrGreater(9)) {
            return ProcessHandle.current().pid();
        }
        
        String runtimeName = runtimeMxBean.getName();
        int index = runtimeName.indexOf('@');
        if (index &gt; 0) {
            return Long.parseLong(runtimeName.substring(0, index));
        }
        
        throw new IllegalStateException("Cannot determine process ID");
    }
    
    private static boolean isJavaVersionOrGreater(int version) {
        String javaVersion = System.getProperty("java.version");
        if (javaVersion.startsWith("1.")) {
            javaVersion = javaVersion.substring(2);
        }
        int majorVersion = Integer.parseInt(javaVersion.split("\\.")[0]);
        return majorVersion &gt;= version;
    }
}</code></pre>
<p>In this code, we demonstrate version detection and graceful fallback. However, <strong>in modern Java development, we typically target a specific Java version</strong>. Put simply, the version-aware approach is most useful if we work with libraries that need to support multiple Java versions.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored different approaches to get the process ID in Java. Specifically, we saw how the  <em>ManagementFactory.getRuntimeMXBean().getName()</em> approach usually works for older Java versions, though it requires string parsing. Further, we also looked at the <em>ProcessHandle.current().pid()</em> method, which is a simple, type‑safe API in Java 9. Next, we tried <em>RuntimeMXBean.getPid()</em> from Java 10 &#8211; another convenient option. Finally, we created a version-aware way to provide a balance of compatibility and modern methods.</p>
<p>As always, all the source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang-8">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-get-own-pid">How Does a Java Program Get Its Own Process ID</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/960370808/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960370808/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960370808/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960370808/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960370808/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960370808/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-get-own-pid#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-get-own-pid/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/960370808/0/baeldung~How-Does-a-Java-Program-Get-Its-Own-Process-ID/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-handlerinterceptor-request-response-body</feedburner:origLink>
		<title>How to Get The RequestBody and ResponseBody in HandlerInterceptor</title>
		<link>https://feeds.feedblitz.com/~/960269651/0/baeldung~How-to-Get-The-RequestBody-and-ResponseBody-in-HandlerInterceptor</link>
					<comments>https://feeds.feedblitz.com/~/960269651/0/baeldung~How-to-Get-The-RequestBody-and-ResponseBody-in-HandlerInterceptor#respond</comments>
		
		<dc:creator><![CDATA[Harpal Singh]]></dc:creator>
		<pubDate>Mon, 13 Jul 2026 15:25:40 +0000</pubDate>
				<category><![CDATA[Spring MVC]]></category>
		<category><![CDATA[Servlet]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-handlerinterceptor-request-response-body</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to log HTTP request and response bodies using a Spring MVC HandlerInterceptor.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/960269651/0/baeldung~How-to-Get-The-RequestBody-and-ResponseBody-in-HandlerInterceptor">How to Get The RequestBody and ResponseBody in HandlerInterceptor</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960269651/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960269651/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-09-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960269651/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960269651/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960269651/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-handlerinterceptor-request-response-body#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-handlerinterceptor-request-response-body/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><p><!-- obsidian --></p>
<h2 data-heading="1. Overview" id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>In this tutorial, we&#8217;ll look at several ways to log HTTP request and response bodies using a Spring MVC <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mvc-handlerinterceptor"><em>HandlerInterceptor</em></a> without disrupting normal request handling.</p>
<p>In short, we&#8217;ll use a servlet filter, in particular the <em>ContentCaching</em> wrapper, to log details of a simple REST API.</p>
<h2 data-heading="2. Spring MVC Execution Chain" id="bd-spring-mvc-execution-chain" data-id="spring-mvc-execution-chain">2. Spring MVC Execution Chain</h2>
<div class="bd-anchor" id="spring-mvc-execution-chain"></div>
<p>First, we need to learn about the stages of a request within the Spring MVC execution chain.</p>
<p>The chain starts with servlet filters, followed by the <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-dispatcherservlet">DispatcherServlet</a>,</em> the handler execution chain, and finally the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-httpmessageconverter-rest">HTTP message conversion</a> layer.</p>
<p>We&#8217;re interested in the <em>HandlerInterceptor</em>s, which are part of the handler execution chain.</p>
<h3 data-heading="2.1. Understanding *HandlerInterceptor*" id="bd-1-understanding-handlerinterceptor" data-id="1-understanding-handlerinterceptor">2.1. Understanding <em>HandlerInterceptor</em></h3>
<div class="bd-anchor" id="1-understanding-handlerinterceptor"></div>
<p>After the request reaches the <em>DispatcherServlet</em>, Spring needs to resolve a handler and run the handler execution chain, which may include zero or more <em>HandlerInterceptor</em>s. The chain contains <em>preHandle()</em>, then the controller and its related logic, followed by <em>postHandle()</em> and <em>afterCompletion()</em>.</p>
<p>The behavior of <em>postHandle()</em> depends on the controller&#8217;s return type. When the controller returns a view, the response is still uncommitted, so <em>postHandle()</em> can still modify it. With <em>@ResponseBody</em> or <em>ResponseEntity</em>, however, the HTTP message converters write and commit the body before <em>postHandle()</em> runs, so the response is effectively read-only at that point.</p>
<h3 data-heading="2.2. Request and Response Bodies Are One-Shot" id="bd-2-request-and-response-bodies-are-one-shot" data-id="2-request-and-response-bodies-are-one-shot">2.2. Request and Response Bodies Are One-Shot</h3>
<div class="bd-anchor" id="2-request-and-response-bodies-are-one-shot"></div>
<p>A characteristic of the Request and Response bodies is that both are represented as streams. Thus, once any component consumes the stream, it cannot be read again unless we explicitly save it in some buffer.</p>
<p>This is important because if a filter or interceptor reads the body before it reaches Spring&#8217;s HTTP Message conversion layer (<em>HttpMessageConverter</em>), the converter receives an empty stream, and <em>@RequestBody</em> deserialization fails:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/img_6a550221f233e.svg"><img decoding="async" class="aligncenter size-full wp-image-253828" src="https://www.baeldung.com/wp-content/uploads/2026/07/img_6a550221f233e.svg" alt="Sequence diagram highlighting the issue of early inputstream read" /></a>
<p>&nbsp;</p>
<p>Now we have all the elements to understand the underlying details and how Spring handles HTTP requests.</p>
<h2 data-heading="3. Example REST API" id="bd-example-rest-api" data-id="example-rest-api">3. Example REST API</h2>
<div class="bd-anchor" id="example-rest-api"></div>
<p>Let&#8217;s create a simple REST API that creates books using <em>@RequestBody</em>:</p>
<pre><code class="language-java">public record CreateBookRequest(String title, String author) { }
public record BookCreatedResponse(UUID id, String title, String author) { }
@RestController
@RequestMapping("/api/books")
public class BookController {
    @PostMapping
    public ResponseEntity&lt;BookCreatedResponse&gt; create(@RequestBody CreateBookRequest request) {
        BookCreatedResponse response = new BookCreatedResponse(
            UUID.randomUUID(),
            request.title(),
            request.author()
        );
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
}
</code></pre>
<p>Now, the goal here is to log both the incoming <em>CreateBookRequest</em> and <em>BookCreatedResponse</em> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-record-keyword">records</a> for every call with HTTP method, URI, and status.</p>
<h2 data-heading="4. *ContentCaching* Wrappers in a Filter" id="bd-contentcaching-wrappers-in-a-filter" data-id="contentcaching-wrappers-in-a-filter">4. <em>ContentCaching</em> Wrappers in a Filter</h2>
<div class="bd-anchor" id="contentcaching-wrappers-in-a-filter"></div>
<p>With Spring, we can use <em>ContentCachingRequestWrapper</em> and <em>ContentCachingResponseWrapper</em> to save the request and response content in a cache and expose it via <em>getContentAsByteArray()</em>. So even after we first read the stream, we don&#8217;t lose the content in subsequent layers.</p>
<p>To accomplish this, we&#8217;ll implement a servlet filter that gets registered to all requests by extending <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-onceperrequestfilter"><em>OncePerRequestFilter</em></a>:</p>
<pre><code class="language-java">@Component
public class CachingHttpFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain filterChain) throws ServletException, IOException {
        ContentCachingRequestWrapper cachingRequest = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper cachingResponse = new ContentCachingResponseWrapper(response);
        try {
            filterChain.doFilter(cachingRequest, cachingResponse);
        } finally {
            cachingResponse.copyBodyToResponse();
        }
    }
}
</code></pre>
<p>Filters run before the <em>DispatcherServlet</em>, so we save everything before reaching the controller itself. Now let&#8217;s see how to use this cached value.</p>
<p>Notably, Spring already ships <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-http-logging#using-spring-boot-built-in-request-logging">CommonsRequestLoggingFilter</a></em> for request-side logging.</p>
<h3 data-heading="4.1. Logging Cached Bodies in *HandlerInterceptor*" id="bd-1-logging-cached-bodies-in-handlerinterceptor" data-id="1-logging-cached-bodies-in-handlerinterceptor">4.1. Logging Cached Bodies in <em>HandlerInterceptor</em></h3>
<div class="bd-anchor" id="1-logging-cached-bodies-in-handlerinterceptor"></div>
<p>To log the cached values, we implement <em>HandlerInterceptor</em>. Let&#8217;s build it step by step.</p>
<p>First, we declare the class and its logger:</p>
<pre><code class="language-java">@Component
public class HttpLoggingInterceptor implements HandlerInterceptor {
    private static final Logger log =
        LoggerFactory.getLogger(HttpLoggingInterceptor.class);
}
</code></pre>
<p>This is a Spring-managed component implementing <em>HandlerInterceptor</em>, so it can be registered later via <em>WebMvcConfigurer</em>.</p>
<p>Next, in <em>preHandle()</em>, we log only lightweight metadata — method and URI — and deliberately avoid touching the body stream, since the <em>HttpMessageConverter</em> hasn&#8217;t run yet and the cache isn&#8217;t populated:</p>
<pre><code class="language-java">@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
  Object handler) {
    log.info("Incoming {} {}", request.getMethod(), request.getRequestURI());
    return true;
}
</code></pre>
<p>The right place to log the full request and response is <em>afterCompletion()</em>, because by that point, both the controller and the <em>HttpMessageConverter</em> have run, so the <em>ContentCaching</em> wrappers are fully populated:</p>
<pre><code class="language-java">@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
  Object handler, Exception ex) {
    String requestBody = extractRequestBody(request);
    String responseBody = extractResponseBody(response);
    log.info("HTTP {} {} status={} requestBody={} responseBody={}", request.getMethod(),
      request.getRequestURI(), response.getStatus(), requestBody, responseBody);
}
</code></pre>
<p>Finally, the helper methods read the cached bytes and convert them to a string. Both extract methods guard with an <em>instanceof</em> check. If we haven&#8217;t registered the filter or the wrapper isn&#8217;t present, they safely return an empty string. The encoding falls back to UTF-8 when the request or response doesn&#8217;t declare a charset:</p>
<pre><code class="language-java">private String getStringValueFromBuffer(byte[] buffer, String encoding) {
    if (buffer.length &gt; 0) {
        try {
            return new String(buffer, encoding != null ? encoding : StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException ex) {
            return "[unknown-encoding]";
      }
    }
    return "";
}
</code></pre>
<p>We use this in the request extraction:</p>
<pre><code class="language-java">private String extractRequestBody(HttpServletRequest request) {
    if (request instanceof ContentCachingRequestWrapper wrapper) {
        byte[] buf = wrapper.getContentAsByteArray();
        return getStringValueFromBuffer(buf, request.getCharacterEncoding());
    }
    return "";
}</code></pre>
<p>And we use it in the response extraction as well:</p>
<pre><code class="language-java">private String extractResponseBody(HttpServletResponse response) {
    if (response instanceof ContentCachingResponseWrapper wrapper) {
        byte[] buf = wrapper.getContentAsByteArray();
        return getStringValueFromBuffer(buf, response.getCharacterEncoding());
    }
    return "";
}</code></pre>
<p>Notably, <em>ContentCachingRequestWrapper</em> doesn&#8217;t eagerly copy the request body. It only fills its cache when something actually reads the input stream, which happens when Spring&#8217;s <em>HttpMessageConverter</em> deserializes the <em>@RequestBody</em>.</p>
<h3 data-heading="4.2. Registering the *HandlerInterceptor*" id="bd-2-registering-the-handlerinterceptor" data-id="2-registering-the-handlerinterceptor">4.2. Registering the <em>HandlerInterceptor</em></h3>
<div class="bd-anchor" id="2-registering-the-handlerinterceptor"></div>
<p>Now the final thing left to do is to register this handler for all the incoming API calls:</p>
<pre><code class="language-java">@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final HttpLoggingInterceptor loggingInterceptor;
    public WebConfig(HttpLoggingInterceptor loggingInterceptor) {
        this.loggingInterceptor = loggingInterceptor;
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggingInterceptor).addPathPatterns("/api/**");
    }
}
</code></pre>
<p>This configuration keeps logging separate from controller code and guarantees that all requests under <em>/api/** </em>pass through our <em>HttpLoggingInterceptor</em>.</p>
<h3 data-heading="4.3. Testing the implementation" id="bd-3-testing-the-implementation" data-id="3-testing-the-implementation">4.3. Testing the Implementation</h3>
<div class="bd-anchor" id="3-testing-the-implementation"></div>
<p>To verify our setup, we can write an integration test using <em>MockMvc</em> together with Spring Boot&#8217;s <em>OutputCaptureExtension</em>, which lets us assert against the log output produced during the test:</p>
<pre><code class="language-java">@SpringBootTest
@AutoConfigureMockMvc
@ExtendWith(OutputCaptureExtension.class)
class BookControllerLoggingUnitTest {
    @Autowired
    private MockMvc mockMvc;
    @Test
    void whenCreateBook_thenRequestAndResponseAreLogged(CapturedOutput output) throws Exception {
        String requestBody = """
          { "title": "Spring in Action", "author": "Craig Walls" }
        """;
        mockMvc.perform(post("/api/books")
          .contentType(MediaType.APPLICATION_JSON)
          .content(requestBody))
          .andExpect(status().isCreated());
        assertThat(output).contains("Incoming POST /api/books");
        assertThat(output).contains("HTTP POST /api/books status=201");
        assertThat(output).contains("\"title\":\"Spring in Action\"");
        assertThat(output).contains("\"author\":\"Craig Walls\"");
    }
}</code></pre>
<h2 data-heading="5. Conclusion" id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we&#8217;ve seen how to log HTTP request and response bodies in a Spring MVC application without breaking <em>@RequestBody</em> deserialization.</p>
<p>We started by reviewing the request flow through the execution chain and the one-shot nature of HTTP body streams.</p>
<p>We then used <em>ContentCachingRequestWrapper</em> and <em>ContentCachingResponseWrapper</em> within a <em>OncePerRequestFilter</em> to buffer the request and response bodies, and finally read those buffers from a <em>HandlerInterceptor</em> in <em>afterCompletion()</em>, once the message converters had run and the response was complete.</p>
<p>As always, the source code for this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-mvc-5">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-handlerinterceptor-request-response-body">How to Get The RequestBody and ResponseBody in HandlerInterceptor</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/960269651/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960269651/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960269651/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-09-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960269651/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960269651/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960269651/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-handlerinterceptor-request-response-body#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-handlerinterceptor-request-response-body/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/960269651/0/baeldung~How-to-Get-The-RequestBody-and-ResponseBody-in-HandlerInterceptor/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-embabel-ai-agent-tool-call-reasoning</feedburner:origLink>
		<title>LLM Tool Call Reasoning Using Embabel Agentic AI Framework</title>
		<link>https://feeds.feedblitz.com/~/960158825/0/baeldung~LLM-Tool-Call-Reasoning-Using-Embabel-Agentic-AI-Framework</link>
					<comments>https://feeds.feedblitz.com/~/960158825/0/baeldung~LLM-Tool-Call-Reasoning-Using-Embabel-Agentic-AI-Framework#respond</comments>
		
		<dc:creator><![CDATA[Igor Dayen]]></dc:creator>
		<pubDate>Sun, 12 Jul 2026 09:57:58 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[Embabel]]></category>
		<category><![CDATA[popular]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-embabel-ai-agent-tool-call-reasoning</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use the Embabel Agent Framework in Java to gain observability over AI agent tool-call selection reasoning.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/960158825/0/baeldung~LLM-Tool-Call-Reasoning-Using-Embabel-Agentic-AI-Framework">LLM Tool Call Reasoning Using Embabel Agentic AI Framework</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960158825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960158825/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960158825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960158825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960158825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-embabel-ai-agent-tool-call-reasoning#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-embabel-ai-agent-tool-call-reasoning/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p><strong>Tool-call reasoning improves the observability of AI agents by capturing the rationale behind LLM tool selection rather than simply recording which tool the model invokes</strong>.</p>
<p>Insight into agentic tool selection decisions helps developers debug agentic workflows, validate decision quality, and identify opportunities for prompt or tool design improvements. This transparency becomes increasingly important as agents grow more autonomous and interact with larger sets of tools.</p>
<p>In this tutorial, we&#8217;ll look at how the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~github.com/embabel/embabel-agent">Embabel</a> Agentic AI framework can be used to obtain this level of observability in our Java applications.</p>
<h2 id="bd-tool-call-reasoning-using-spring-ai" data-id="tool-call-reasoning-using-spring-ai">2. Tool Call Reasoning Using Spring AI</h2>
<div class="bd-anchor" id="tool-call-reasoning-using-spring-ai"></div>
<p>Before diving into how to use Embabel, let&#8217;s first consider how the problem is solved in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-explainable-agents-capture-llm-tool-call-reasoning">Spring AI Tool Argument Augmenter</a>.</p>
<p><strong>The Tool Argument Augmenter dynamically augments tool schemas with additional reasoning fields</strong>, such as <em>innerThought</em> or <em>confidence</em>. The result is improved observability into agent behavior without changing existing tool implementations.</p>
<p>In Spring AI, tools are defined using a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/introduction-to-json-schema-in-java">JSON Schema</a> that specifies the expected arguments. The LLM evaluates these schemas and decides which tool to invoke and which arguments to pass. Spring AI’s augmentation layer exploits this mechanism by extending the schema&#8217;s properties with additional properties visible to the model (i.e., schema augmentation).</p>
<p><strong>An essential implementation feature is that the real tool definition, applied at the time of tool invocation, remains unchanged.</strong> The augmentation layer intercepts the tool-call payload, extracts the reasoning fields, removes them, and invokes the actual tool with only the expected arguments. The tool itself remains unaware of the augmentation.</p>
<p>The user receives tool-call reasoning details via a separate DTO (with properties <em>innerThought</em> and <em>confidence</em>).</p>
<h3 id="bd-1-spring-ai-solution-design-analysis" data-id="1-spring-ai-solution-design-analysis">2.1. Spring AI Solution Design Analysis</h3>
<div class="bd-anchor" id="1-spring-ai-solution-design-analysis"></div>
<p>The design is strong in several areas.</p>
<p>First, it provides structured and machine-readable observability. Because reasoning fields are part of the schema, they are easier to evaluate, store, or feed into analytics pipelines.</p>
<p>Second, it remains largely non-invasive. Existing tools, including <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-model-context-protocol-mcp">MCP tools</a> and local <em>@Tool</em> methods, continue to work without modification.</p>
<p>Third, the solution leverages existing LLM capabilities instead of inventing proprietary reasoning APIs. Any model that supports structured tool calling can, in principle, support this approach.</p>
<p><strong>However, the design also introduces trade-offs; a key limitation is that the exposed schema is no longer the actual contract.</strong> The LLM-facing schema contains fields that the underlying tool never actually accepts. Architecturally, this means that an augmented tool schema is not the same as the runtime tool method contract.</p>
<p>The approach introduces additional complexity through:</p>
<ul>
<li>Augmented DTOs</li>
<li>Schema merging</li>
<li>Augmented fields extraction</li>
<li>Tool-wrapping infrastructure</li>
</ul>
<p>This increases both conceptual overhead and coupling with Spring AI’s internal JSON Schema tool model.</p>
<h2 id="bd-tool-call-reasoning-using-a-system-prompt" data-id="tool-call-reasoning-using-a-system-prompt">3. Tool Call Reasoning Using a System Prompt</h2>
<div class="bd-anchor" id="tool-call-reasoning-using-a-system-prompt"></div>
<p>A simpler alternative is to define a standardized system-level protocol via prompting. When invoking a tool, the prompt should request the LLM to:</p>
<ul>
<li>Explain briefly why the tool is needed</li>
<li>State what information is expected</li>
<li>Provide a confidence level</li>
</ul>
<p>This shifts reasoning capture from a schema-level contract to a prompt-level protocol—from structural enforcement to semantic guidance. In practice, schema augmentation enforces a hard contract (“fields must exist”), while prompting establishes a soft convention (“behave this way”).</p>
<p>Both approaches rely on model-generated explanations and therefore share the same variability in content and phrasing. <strong>The practical difference lies in output structure: schema augmentation enforces the presence of explicit fields, making explanations easier to capture and parse, whereas a prompt-based approach leaves them in semi-structured format.</strong></p>
<p><strong>The main advantage of the prompt-based approach is portability.</strong> Schema augmentation depends on framework-specific mechanisms (e.g., tool schema generation, callback providers, argument interception), whereas a system prompt relies only on a universal capability: sending instructions to the model. This makes it portable across other agentic frameworks such as Spring AI, LangChain4j, Semantic Kernel, OpenAI, Anthropic, and Google DeepMind.</p>
<p>Compared to the Spring AI augmentation approach, the prompt-based solution keeps the implementation simpler by avoiding synthetic schemas, augmentation DTOs, schema transformation, and custom tool-wrapping infrastructure.</p>
<p>Neither approach is universally superior—they embody different engineering trade-offs. <strong>In practice, a hybrid model often works best: use prompt-level protocols for general guidance and apply schema-level augmentation selectively where stronger guarantees are needed.</strong></p>
<h2 id="bd-tool-call-reasoning-with-embabel" data-id="tool-call-reasoning-with-embabel">4. Tool Call Reasoning with Embabel</h2>
<div class="bd-anchor" id="tool-call-reasoning-with-embabel"></div>
<p>Embabel is an Agentic AI framework founded by <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.linkedin.com/in/johnsonroda/">Rod Johnson</a>, creator of the Spring Framework. For introductory information about Embabel, please refer to the Baeldung <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-embabel-agent-framework" target="_blank" rel="noopener">article &#8220;Using the Embabel Agent Framework&#8221;</a>. You can find the official Embabel documentation <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.embabel.com/embabel-agent/guide/0.5.0/index.html">here</a>, and more resources on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://medium.com/embabel">Medium</a>.</p>
<h3 id="bd-1-quick-introduction-to-embabel-modes" data-id="1-quick-introduction-to-embabel-modes">4.1. Quick Introduction to Embabel Modes</h3>
<div class="bd-anchor" id="1-quick-introduction-to-embabel-modes"></div>
<p>Embabel is a comprehensive framework for developing Agentic AI applications on the JVM. A key feature of Embabel is the employment of a fluent API with structured outputs in all modes &#8211; an important feature in the Agentic AI landscape for the JVM:</p>
<ul>
<li>Basic mode (without &#8220;thinking&#8221; or streaming)</li>
<li>Thinking mode (details to follow)</li>
<li>Streaming mode</li>
</ul>
<p>We&#8217;ll explore the &#8220;thinking&#8221; mode, which serves as the foundation for the Tool Call Reasoning solution.</p>
<h3 id="bd-2-embabel-agentic-ai-dependencies" data-id="2-embabel-agentic-ai-dependencies">4.2. Embabel Agentic AI Dependencies</h3>
<div class="bd-anchor" id="2-embabel-agentic-ai-dependencies"></div>
<p>First, let&#8217;s define the major Embabel dependencies required for testing:</p>
<pre><code class="language-xml">&lt;!-- Embabel Agent Starter --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.embabel.agent&lt;/groupId&gt;
    &lt;artifactId&gt;embabel-agent-starter-openai&lt;/artifactId&gt;
    &lt;version&gt;${embabel-agent.version}&lt;/version&gt;
&lt;/dependency&gt;
&lt;!-- Embabel Agent Test Support --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.embabel.agent&lt;/groupId&gt;
    &lt;artifactId&gt;embabel-agent-test-internal&lt;/artifactId&gt;
    &lt;version&gt;${embabel-agent.version}&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
</code></pre>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.embabel.agent/embabel-agent-starter-openai">embabel-agent-starter-openai</a> dependency provides us with the core classes required to use OpenAI models with Embabel.</p>
<p>We also include <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.embabel.agent/embabel-agent-test">embabel-agent-test-internal</a> to allow us to use the <em>AgentTestApplication</em> test support class with our <em>@SpringBootTest</em>.</p>
<h3 id="bd-3-thinking-mode-fluent-api-concepts" data-id="3-thinking-mode-fluent-api-concepts">4.3. Thinking Mode Fluent API Concepts</h3>
<div class="bd-anchor" id="3-thinking-mode-fluent-api-concepts"></div>
<p>Let&#8217;s introduce key concepts of the Embabel &#8220;thinking&#8221; mode fluent API:</p>
<pre><code class="language-java">ThinkingResponse&lt;ParkingRecommendation&gt; result = ai.withDefaultLlm()
  .withToolObject(tools)
  .withToolLoopInspectors(callbackTracker, loggingInspector)
  .withToolLoopTransformers(systemMessageTransformer)
  .thinking()
  .createObject(prompt, ParkingRecommendation.class);</code></pre>
<p><em>Ai</em> is a gateway to LLM connectivity; we can define the default <em>Ai</em> LLM in the configuration file or supply it explicitly.</p>
<p><em>ThinkingResponse</em> is a wrapper object that includes a reference to the underlying business object, <em>ParkingRecommendation</em>, and so-called <em>ThinkingBlocks</em> with LLM reasoning.</p>
<p><em>withToolObject() </em>is a reference to the <em>ParkingTooling</em> tool object (with details later in the article).</p>
<p><em>withToolInspectors()</em> refers to optional callbacks, helpful for additional logging, metrics collection, etc. <em>CallbackTracker</em> collects statistics (counter of LLM calls, counter of tool calls, list of invoked tools), while <em>ToolLoopLoggingInspector</em> logs LLM and tool invocation details (for example: <em>ToolLoopLoggingInspector &#8211; afterLlmCall: iteration=1, toolCalls=2, contentLength=1063, usage=prompt=834, completion=260</em>)</p>
<p><em>withToolLoopTransformers()</em>  demonstrates user-controlled conversation history management via <em>ToolLoopTransformer</em>. In this example, the transformer is configured with two system messages and always positions them at the top of the conversation history before LLM calls.</p>
<p><em>thinking()</em> indicates a fluent API subtype as &#8220;thinking mode&#8221; and outputs <em>ThinkingResponse</em>, which wraps the business object, constructed by the API <em>createObject</em>().</p>
<p>Finally, the <em>prompt</em> variable contains our user message.</p>
<p>Thinking mode includes a few more APIs, including a very useful one, <em>createObjectIfPossible(). </em>In the scenario, if the LLM is not capable of creating the object, <em>ThinkingResponse</em> will still carry reasoning behind this fact.</p>
<h3 id="bd-4-use-case" data-id="4-use-case">4.4. Use Case</h3>
<div class="bd-anchor" id="4-use-case"></div>
<p>Our use case is an optimal parking solution finder subject to time and cost constraints. There would be three tools at our disposal:</p>
<ul>
<li>Free street parking locator</li>
<li>Metered parking locator</li>
<li>Garage reservation</li>
</ul>
<p>The final thinking block explains the reasoning behind the recommendation, based on information gathered through probing multiple tools.</p>
<p><strong>We will split the prompt into a generic, domain-neutral system message that can be used across a variety of business scenarios and a domain-specific user message.</strong></p>
<p>Let&#8217;s specify the system message:</p>
<pre><code class="language-java">"""
CRITICAL WORKFLOW - Two-phase decision process:
=== PHASE 1: Tool Selection (First Response) ===
1. For EACH tool you plan to call, emit a SEPARATE &lt;tool_use_reasoning&gt; block:
    &lt;tool_use_reasoning&gt;
    Tool: [TOOL_NAME]
    Why THIS tool: [explain why this specific tool is needed]
    Information expected: [what this tool will reveal]
    Advantage over alternatives: [why this tool vs others]
    Confidence in this tool selection [confidence=0.XX]
    &lt;/tool_use_reasoning&gt;
    Since you must call at least 2 tools, you must emit at least 2 separate blocks.
2. Call AT LEAST TWO tools to gather comprehensive information
    - You MUST call at least 2 tools to probe different aspects.
    - Tools are PROBES for information gathering, not final decisions.
    - Multiple probes provide better decision quality.
=== PHASE 2: Final Decision (After receiving tool results) ===
1. Emit final decision reasoning:
    &lt;final_decision_reasoning&gt;
    - What each tool probe revealed
    - How the probe results informed your analysis
    - Why you chose this option based on probe data and constraints
    Confidence: [confidence=0.XX]
    &lt;/final_decision_reasoning&gt;
2. Then provide the final structured output
    - Your final recommendation should synthesize insights from multiple probes.
    - Never copy reasoning blocks into the final structured object.
REMINDER: One &lt;tool_use_reasoning&gt; block per tool call. At least 2 tools = at least 2 blocks.
Emit reasoning in BOTH phases.
"""</code></pre>
<p>And let&#8217;s specify a user message:</p>
<pre><code class="language-java">"""
Scenario:
An advisor is driving to a client meeting in Midtown Manhattan.
Constraints:
- 30 minutes remain before the meeting starts
- arriving late is not acceptable
- the meeting is expected to last about 3 hours
Parking options:
- Street parking: free, but uncertain
- Metered parking: $5 per hour, typically limited to 2 hours
- Garage parking: $30 per hour, guaranteed availability
Important decision factors:
- available time before the meeting
- risk of arriving late
- trade-offs between street, metered, and garage parking
Recommend the best parking option.
Available tools: %s
"""</code></pre>
<p>Next, we&#8217;ll supply the available tools.</p>
<h3 id="bd-5-tool-definition" data-id="5-tool-definition">4.5. Tool Definition</h3>
<div class="bd-anchor" id="5-tool-definition"></div>
<p>Let&#8217;s define the tool object:</p>
<pre><code class="language-java">static class ParkingTooling {
    @LlmTool(description = "Find free street parking. Uncertain and may take time.")
    public String findStreetParking(String location, int maxMinutes) {
        boolean found = ThreadLocalRandom.current().nextDouble() &lt; 0.3; // low probability
        if (found) {
            return "Street parking found near " + location + " (free)";
        }
        return "No street parking found within " + maxMinutes + " minutes";
    }
    @LlmTool(description = "Find metered parking. Moderate cost and moderate availability. " +
        "May have time limits.")
    public String findMeterParking(String location, int maxMinutes) {
        boolean found = ThreadLocalRandom.current().nextDouble() &lt; 0.6; // medium probability
        if (found) {
            return "Metered parking found near " + location + " ($5/hour, 2-hour limit)";
        }
        return "No metered parking found within " + maxMinutes + " minutes";
    }
    @LlmTool(description = "Reserve guaranteed garage parking near destination.")
    public String reserveGarage(String location) {
        return "Garage reserved near " + location + " ($30/hour, guaranteed)";
    }
}
</code></pre>
<p><strong>Here, <em>LlmTool</em> is an Embabel tool annotation, which includes a tool description. </strong>Embabel allows us to expose tools to the LLM by implementing the Embabel <em>Tool</em> interface, or using annotated Java methods. Here, we see the latter approach.</p>
<h3 id="bd-6-test-results-discussion" data-id="6-test-results-discussion">4.6. Test Results Discussion</h3>
<div class="bd-anchor" id="6-test-results-discussion"></div>
<p>Our test <em>ToolCallReasoningIntegrationTest</em> invokes the LLM (which in turn invokes tools and summarizes tool selection recommendations) and logs results using custom logging functions.</p>
<p>We have a <em>reportToolCallPattern()</em> method that takes <em>CallbackTracker</em> as a parameter and logs tool pattern and statistics:</p>
<pre><code class="language-plaintext">17:26:33.876 [main] INFO  ToolCallReasoningIntegrationTest - === TOOL CALL PATTERN ANALYSIS ===
Total LLM calls: 2
Unique tools: [findStreetParking, findMeterParking]
Total invocations: 2
Tool result callbacks: 2
Iterations with tool calls: 1
17:26:33.876 [main] INFO  ToolCallReasoningIntegrationTest - PATTERN: All 2 tools called in SAME iteration (parallel)
17:26:33.876 [main] INFO  ToolCallReasoningIntegrationTest - Thinking blocks captured: 3
17:26:33.876 [main] INFO  ToolCallReasoningIntegrationTest -   Block 1: tagType=TAG, tagValue=tool_use_reasoning, contentLength=481
17:26:33.877 [main] INFO  ToolCallReasoningIntegrationTest -     Tool: functions.findStreetParking
17:26:33.877 [main] INFO  ToolCallReasoningIntegrationTest -     Attributes: {confidence=0.85}
17:26:33.877 [main] INFO  ToolCallReasoningIntegrationTest -   Block 2: tagType=TAG, tagValue=tool_use_reasoning, contentLength=494
17:26:33.877 [main] INFO  ToolCallReasoningIntegrationTest -     Tool: functions.findMeterParking
17:26:33.877 [main] INFO  ToolCallReasoningIntegrationTest -     Attributes: {confidence=0.90}
17:26:33.877 [main] INFO  ToolCallReasoningIntegrationTest -   Block 3: tagType=TAG, tagValue=final_decision_reasoning, contentLength=755
17:26:33.877 [main] INFO  ToolCallReasoningIntegrationTest -     Attributes: {confidence=0.95}
</code></pre>
<div>
<p>In addition, our <em>reportThinkingBlocks()</em> method extracts a <em>ParkingRecommendation</em> object and <em>ThinkingBlock</em> items from the result, formats them, and logs them.</p>
<p>Let&#8217;s examine the logs produced by <em>reportThinkingBlocks()</em> below:</p>
</div>
<pre><code class="language-plaintext">Recommended: ParkingRecommendation[chosenOption=GARAGE, location=Midtown Manhattan, estimatedTotalCost=90, summary=Street parking is unavailable within 30 minutes and metered parking is limited to 2 hours, insufficient for a 3-hour meeting. Garage parking offers guaranteed availability and ensures timely arrival, making it the best option despite higher cost.]
Reasoning:
  ThinkingBlock(content=Tool: functions.findStreetParking
Why THIS tool: To check the availability and likelihood of finding free street parking near Midtown Manhattan within the 30-minute time constraint.
Information expected: Whether street parking is realistically available and how long it might take to find a spot.
Advantage over alternatives: Street parking is free and the cheapest option, but highly uncertain. Testing its feasibility is key before considering costlier options.
Confidence in this tool selection [confidence=0.9], tagType=TAG, tagValue=tool_use_reasoning)
  ThinkingBlock(content=Tool: functions.findMeterParking
Why THIS tool: To evaluate availability and feasibility of metered parking given the 2-hour limit and $5/hour cost within the 30-minute timeframe.
Information expected: Confirm if metered parking spots are available nearby and potential cost and time constraints.
Advantage over alternatives: Metered parking balances moderate cost and availability but with time limits, important to check if it fits the meeting duration.
Confidence in this tool selection [confidence=0.9], tagType=TAG, tagValue=tool_use_reasoning)
  ThinkingBlock(content=- The street parking probe revealed no available free street parking within the critical 30-minute arrival window, making this option too risky given the zero tolerance for lateness.
- Metered parking is available near the destination, but limited to 2 hours and costs $5/hour. This time limit is insufficient for the 3-hour meeting, so this option creates the risk of parking violation or disruption.
- Although not probed yet, garage parking guarantees availability and can cover the full 3-hour meeting, eliminating risk of lateness or parking issues at a higher cost ($30/hour).
- Given the importance of timely arrival and the meeting length, garage parking is the most reliable and suitable choice despite the cost premium.
Confidence: [confidence=0.95], tagType=TAG, tagValue=final_decision_reasoning)
</code></pre>
<p>Embabel returned a structured object of type <em>ParkingRecommendation</em> with all expected fields, including <em>summary. </em></p>
<p>Each <em>ThinkingBlock</em> includes an explanation of the tool selection, a <em>tagType</em> specifies the LLM reasoning format &#8211; in our case, this is XML <em>TAG</em> (could be <em>PREFIX</em>, or no tag), a <em>tagValue=tool_use_reasoning</em>, and a <em>tagValue=final_decision_reasoning</em>, which are the dynamic user-defined XML tags we specified in the system prompt. Please note two <em>ThinkingBlocks</em>, one for each tool call.</p>
<p>Finally, the <em>ThinkingBlock</em> with the recommendation was emitted, with placeholder <em>confidence</em> substituted, as expected.</p>
<p>The use of placeholders permits further automated processing for tool selection reasoning. The method <em>extractAttributes()</em> (please refer to the code linked to the article) scans a <em>ThinkingBlock</em> for attributes in <em>[key=value]</em> format and returns them as a <em>Map</em> of key-value pairs. For example, it converts text like<em> [confidence=0.9]</em> into an entry such as <em>&#8220;confidence&#8221; -&gt; 0.9</em>.</p>
<p><strong>Thus, Embabel&#8217;s thinking mode output is semi-structured</strong>.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we saw how <strong>Embabel provides a solution for tool selection reasoning by employing its &#8220;thinking&#8221; APIs.</strong></p>
<p>While Spring AI provides out-of-the-box support for Tool Selection Reasoning, the solution has noticeable trade-offs. An alternative solution using Embabel is very simple and, unlike Spring AI, does not require hidden schema manipulations or the introduction of a custom DTO, and is fully built on the existing Embabel foundation APIs.</p>
<p>With Embabel, yet another solution using LLM Tool Loop callbacks is available, but it is outside the scope of this article.</p>
<p>The article code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/embabel-modules/embabel-tool-reasoning">over on GitHub</a>.</p>
<p><em>The author would like to thank <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.linkedin.com/in/johnsonroda/">Rod Johnson</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.linkedin.com/in/arjen-poutsma-288ba6/">Ajen Poutsma</a> for their reviews and suggestions.</em></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-embabel-ai-agent-tool-call-reasoning">LLM Tool Call Reasoning Using Embabel Agentic AI Framework</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/960158825/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960158825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960158825/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960158825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960158825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960158825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-embabel-ai-agent-tool-call-reasoning#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-embabel-ai-agent-tool-call-reasoning/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/960158825/0/baeldung~LLM-Tool-Call-Reasoning-Using-Embabel-Agentic-AI-Framework/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-mvc-handlerinterceptor-test</feedburner:origLink>
		<title>Testing Spring MVC HandlerInterceptor</title>
		<link>https://feeds.feedblitz.com/~/960129299/0/baeldung~Testing-Spring-MVC-HandlerInterceptor</link>
					<comments>https://feeds.feedblitz.com/~/960129299/0/baeldung~Testing-Spring-MVC-HandlerInterceptor#respond</comments>
		
		<dc:creator><![CDATA[Balamurugan Radhakrishnan]]></dc:creator>
		<pubDate>Sat, 11 Jul 2026 21:18:07 +0000</pubDate>
				<category><![CDATA[Spring MVC]]></category>
		<category><![CDATA[Spring MVC Basics]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204284</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to test Spring MVC HandlerInterceptor using @WebMvcTest and MockMvc without starting the full application.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/960129299/0/baeldung~Testing-Spring-MVC-HandlerInterceptor">Testing Spring MVC HandlerInterceptor</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960129299/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960129299/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960129299/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960129299/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960129299/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-mvc-handlerinterceptor-test#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-mvc-handlerinterceptor-test/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<div>
<p><strong>A <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mvc-handlerinterceptor"><em>HandlerInterceptor</em></a> in <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mvc-tutorial">Spring MVC</a> lets us intercept requests before and after controller execution. It&#8217;s useful for cross-cutting concerns like logging, authentication, or feature flags.</strong></p>
<p>Testing Spring MVC HandlerInterceptor can be tricky because they&#8217;re wired into the request lifecycle. In this article, we&#8217;ll explore how to test one using<em> @WebMvcTest</em> and <em>MockMvc</em>, ensuring our interceptor logic works correctly without starting the full application.</p>
</div>
<h2 id="bd-the-handlerinterceptor-interface" data-id="the-handlerinterceptor-interface">2. The HandlerInterceptor Interface</h2>
<div class="bd-anchor" id="the-handlerinterceptor-interface"></div>
<div>
<p><strong>A HandlerInterceptor allows us to execute logic before and after a controller handles a request</strong>. It operates within the Spring MVC layer, giving it access to handler metadata.</p>
<p>Spring MVC&#8217;s <em>HandlerInterceptor</em> interface defines three lifecycle methods:</p>
<ul>
<li><em>preHandle()</em> &#8211; runs before the controller method executes</li>
<li><em>postHandle()</em> &#8211; runs after the controller completes but before generating the view</li>
<li><em>afterCompletion()</em> &#8211; runs after the entire request finishes, regardless of exceptions</li>
</ul>
</div>
<div>
<h2 id="bd-application-setup" data-id="application-setup">3. Application Setup</h2>
<div class="bd-anchor" id="application-setup"></div>
<div>
<p>Let’s consider an order processing application that calculates delivery charges. We want to roll out a new pricing algorithm gradually without redeploying the application. To achieve this, let&#8217;s configure a rollout percentage that determines how many requests use the new calculation (<em>v2</em>), while the rest fall back to the original (<em>v1</em>). We&#8217;ll use a <em>HandlerInterceptor</em> to make the routing decision before the controller executes, keeping it free of feature flag logic.</p>
<p>Let&#8217;s start by defining the <em>FeatureFlagService</em> interface. This represents an external service that provides the current rollout percentage:</p>
</div>
</div>
<pre><code class="language-java">public interface FeatureFlagService {
    int rolloutPercentage();
}</code></pre>
<div>
<p>The implementation details aren&#8217;t relevant here, hence we&#8217;ll mock these in our tests.</p>
</div>
<div>
<h3 id="bd-1-the-interceptor" data-id="1-the-interceptor">3.1. The Interceptor</h3>
<div class="bd-anchor" id="1-the-interceptor"></div>
<div>
<p>Next, let&#8217;s create the <em>DeliveryChargeInterceptor</em>. <strong>It queries the rollout percentage, generates a random value, and routes to <em>v2</em> when the random value falls below the rollout threshold</strong>:</p>
<pre><code class="language-java">public class DeliveryChargeInterceptor implements HandlerInterceptor {
    static final String USE_V2_ATTRIBUTE = "useV2";
    private final FeatureFlagService featureFlagService;
    private final SecureRandom random = new SecureRandom();
    private final Logger logger = LoggerFactory.getLogger(
      DeliveryChargeInterceptor.class
    );
    public DeliveryChargeInterceptor(FeatureFlagService featureFlagService) {
        this.featureFlagService = featureFlagService;
    }
    @Override
    public boolean preHandle(
      HttpServletRequest request,
      HttpServletResponse response,
      Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            int rollout = featureFlagService.rolloutPercentage();
            boolean useV2 = rollout &gt; 0 &amp;&amp; random.nextInt(100) &lt; rollout;
            request.setAttribute(USE_V2_ATTRIBUTE, useV2);
            logger.info(
              "Delivery charge feature: rollout={}%, useV2={}",
              rollout,
              useV2
            );
        }
        return true;
    }
}</code></pre>
<div>
<p>The <em>handler</em> <em>instanceof HandlerMethod</em> check ensures our logic only applies to controller methods, skipping static resource requests. Also, we should note that the interceptor doesn&#8217;t execute business logic. <strong>It simply makes a routing decision and passes it along via a request attribute.</strong></p>
</div>
</div>
<div>
<h3 id="bd-2-the-controller" data-id="2-the-controller">3.2. The Controller</h3>
<div class="bd-anchor" id="2-the-controller"></div>
<div>
<p>Next, Let&#8217;s create the controller that reads the attribute and delegates to the appropriate version:</p>
</div>
<pre><code class="language-java">@RestController
public class DeliveryChargesController {
    private final DeliveryChargeService deliveryChargeService;
    public DeliveryChargesController(DeliveryChargeService deliveryChargeService) {
        this.deliveryChargeService = deliveryChargeService;
    }
    @PostMapping("/delivery-charges/calculate")
    public double calculate(
      @RequestParam String postcode,
      HttpServletRequest request) {
        Boolean useV2 = (Boolean) request.getAttribute(
          DeliveryChargeInterceptor.USE_V2_ATTRIBUTE
        );
        if (Boolean.TRUE.equals(useV2)) {
            return deliveryChargeService.calculateV2(postcode);
        }
        return deliveryChargeService.calculateV1(postcode);
    }
}</code></pre>
<div>
<h3 id="bd-3-registering-the-interceptor" data-id="3-registering-the-interceptor">3.3. Registering the Interceptor</h3>
<div class="bd-anchor" id="3-registering-the-interceptor"></div>
<div>
<div>
<p>Finally, let&#8217;s register the interceptor through <em>WebMvcConfigurer</em> and scope it to the delivery charges path:</p>
<pre><code class="language-java">@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    private final FeatureFlagService featureFlagService;
    public WebMvcConfig(FeatureFlagService featureFlagService) {
        this.featureFlagService = featureFlagService;
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DeliveryChargeInterceptor(featureFlagService))
          .addPathPatterns("/delivery-charges/**");
    }
}</code></pre>
<div>
<p><strong>The <em>addPathPatterns()</em> method ensures that only requests matching this path trigger the interceptor. All other endpoints remain unaffected.</strong></p>
</div>
</div>
<h2 id="bd-testing-the-spring-mvc-handlerinterceptor" data-id="testing-the-spring-mvc-handlerinterceptor">4. Testing the Spring MVC HandlerInterceptor</h2>
<div class="bd-anchor" id="testing-the-spring-mvc-handlerinterceptor"></div>
<div>
<p>Now, let&#8217;s verify the interceptor behavior using <em>@WebMvcTest</em>. This annotation loads only the web layer without starting the full application context. It includes controllers, interceptors, and their configuration. We&#8217;ll use <em>@MockBean</em> to provide mock implementations of our service interfaces:</p>
<div>
<pre><code class="language-java">@WebMvcTest(controllers = DeliveryChargesController.class)
class DeliveryChargeInterceptorIntegrationTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private DeliveryChargeService deliveryChargeService;
    @MockBean
    private FeatureFlagService featureFlagService;
}</code></pre>
</div>
</div>
<div>
<div>
<p>It&#8217;s worth noting that <em>@WebMvcTest</em> automatically picks up any <em>WebMvcConfigurer</em> beans in the application context. Since our <em>WebMvcConfig</em> registers the interceptor, it becomes active during the test.<strong> This means we can test the full request flow from the interceptor through the controller without any additional configuration</strong>.</p>
<p>Let&#8217;s first verify that all requests use <em>v1</em> when rollout is zero:</p>
<pre><code class="language-java">@Test
void givenZeroRollout_whenCalculateDeliveryCharge_thenV1IsUsed() throws Exception {
    when(featureFlagService.rolloutPercentage()).thenReturn(0);
    when(deliveryChargeService.calculateV1("SW1A")).thenReturn(5.0);
    mockMvc.perform(post("/delivery-charges/calculate").param("postcode", "SW1A"))
      .andExpect(status().isOk())
      .andExpect(content().string("5.0"));
}</code></pre>
<div>
<p>With a rollout of zero, the random check in the interceptor always evaluates to <em>false</em>. As a result, the controller consistently calls <em>calculateV1()</em>.</p>
<p>Next, let&#8217;s confirm that all requests use <em>v2</em> when rollout is 100%:</p>
<pre><code class="language-java">@Test
void givenFullRollout_whenCalculateDeliveryCharge_thenV2IsUsed() throws Exception {
    when(featureFlagService.rolloutPercentage()).thenReturn(100);
    when(deliveryChargeService.calculateV2("SW1A")).thenReturn(3.5);
    mockMvc.perform(post("/delivery-charges/calculate").param("postcode", "SW1A"))
      .andExpect(status().isOk())
      .andExpect(content().string("3.5"));
}</code></pre>
</div>
</div>
<div>
<p>At 100% rollout, the random value is always less than the threshold. Therefore, every request routes to <em>calculateV2()</em>.</p>
<p>Finally, let&#8217;s verify that a 50% rollout produces a mix of both versions. We send 20 requests and assert that both <em>v1</em> and <em>v2</em> receive traffic:</p>
<pre><code class="language-java">@Test
void givenPartialRollout_whenCalculateDeliveryCharge_thenBothVersionsAreUsed() throws Exception {
    when(featureFlagService.rolloutPercentage()).thenReturn(50);
    when(deliveryChargeService.calculateV1("SW1A")).thenReturn(5.0);
    when(deliveryChargeService.calculateV2("SW1A")).thenReturn(3.5);
    int v1Count = 0;
    int v2Count = 0;
    for (int i = 0; i &lt; 20; i++) {
        String response = mockMvc.perform(
            post("/delivery-charges/calculate")
              .param("postcode", "SW1A")
          )
          .andExpect(status().isOk())
          .andReturn()
          .getResponse()
          .getContentAsString();
        if ("5.0".equals(response)) {
            v1Count++;
        } else if ("3.5".equals(response)) {
            v2Count++;
        }
    }
    assertThat(v1Count).isGreaterThan(0);
    assertThat(v2Count).isGreaterThan(0);
}</code></pre>
<div>
<p>This test proves that the interceptor distributes traffic between both versions based on the configured percentage.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<div>
<div>
<p>In this article, we explored how to test a Spring MVC HandlerInterceptor using <em>@WebMvcTest</em> and <em>MockMvc</em>. This approach is helpful because <em>@WebMvcTest</em> loads only the web layer, making tests fast. It automatically picks up interceptors registered through <em>WebMvcConfigurer</em>, so we can verify the complete request flow without starting the full application context.</p>
<p>However,  <em>@WebMvcTest</em> only verifies the web layer in isolation, so integration issues with services or repositories won&#8217;t surface here. For scenarios that require the complete application context, we can use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-testing#integration-testing-with-springboottest"><em>@SpringBootTest</em> with <em>MockMvc</em></a> instead. Additionally, for simple interceptor logic that doesn&#8217;t depend on Spring&#8217;s request handling, a unit test <span style="margin: 0px;padding: 0px">using a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-httpservletrequest-mock#using-mockhttpservletrequest-from-spring" target="_blank" rel="noopener"><em>MockHttpServletRequest</em></a> provides</span> even faster feedback.</p>
</div>
<p>As always, the source code for this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-mvc-5">over on GitHub</a>.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mvc-handlerinterceptor-test">Testing Spring MVC HandlerInterceptor</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/960129299/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960129299/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960129299/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960129299/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960129299/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960129299/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-mvc-handlerinterceptor-test#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-mvc-handlerinterceptor-test/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/960129299/0/baeldung~Testing-Spring-MVC-HandlerInterceptor/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-654</feedburner:origLink>
		<title>Java Weekly, Issue 654</title>
		<link>https://feeds.feedblitz.com/~/960038423/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/960038423/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 10 Jul 2026 21:30:52 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204286</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>A Java movie? Cool. And Spring Cloud Contract has a new home.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/960038423/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 654</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960038423/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960038423/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960038423/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960038423/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960038423/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-654#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-654/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/nulling-out-references-wont-help-your-garbage-collector/" target="_blank" rel="noopener"><strong>Nulling Out References Won&#8217;t Help Your Garbage Collector</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/07/hardwood-java-parquet/" target="_blank" rel="noopener"><strong>Hardwood Promises High-Speed JVM Apache Parquet Processing with Zero Mandatory Dependencies</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/whats-new-in-actions-setup-java-5-4-and-5-5-signature-verification-kona-jdk-and-a-better-maven-experience/" target="_blank" rel="noopener"><strong>What&#8217;s New in actions/setup-java 5.4 and 5.5: Signature Verification, Kona JDK, and a Better Maven Experience</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/exposed-kotlin-orm-complete-guide/" target="_blank" rel="noopener"><strong>Demystifying Exposed: The Intelligent SQL Library for Kotlin</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/the-java-story-a-film-about-all-of-us/" target="_blank" rel="noopener"><strong>The Java Story: A Film About All of Us</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/07/introducing-the-kotlin-benchmark-evaluate-ai-coding-agents-on-real-world-kotlin-tasks/" target="_blank" rel="noopener"><strong>Introducing the Kotlin Benchmark for AI Coding Agents</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/06/spring-cloud-contract-transition-to-stubbornsh" target="_blank" rel="noopener"><strong>A New Home for Spring Cloud Contract: Transitioning to Stubborn.sh</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/09/podcast-061/" target="_blank" rel="noopener"><strong>Episode 61 &#8220;Scripting JS and Python with Project Detroit&#8221; [AtA]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/09/a-bootiful-podcast-moritz-halbritter" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Spring Boot legend Moritz Halbritter on the latest and greatest in Spring Boot 4 and 4.1</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/06/spring-office-hours-podcast-S5E17" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E17 &#8211; Spring Boot 4.1 with Phil Webb</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-37-2-released/" target="_blank" rel="noopener"><strong>Quarkus 3.37.2 &#8211; Maintenance release</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/4.5.29" target="_blank" rel="noopener"><strong>Vert.x 4.5.29</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.1.4" target="_blank" rel="noopener"><strong>5.1.4</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.0.0" target="_blank" rel="noopener"><strong>Zuul v4.0.0</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.21" target="_blank" rel="noopener"><strong>v3.6.21</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.20" target="_blank" rel="noopener"><strong>v3.6.20</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.19" target="_blank" rel="noopener"><strong>v3.6.19</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.5" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.5</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.4" target="_blank" rel="noopener"><strong>5.1.4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.0.5" target="_blank" rel="noopener"><strong>5.0.5</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.26" target="_blank" rel="noopener"><strong>4.10.26</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/home-assistant/9/" target="_blank" rel="noopener"><strong>Two nasty surprises in Home Assistant&#8217;s config</strong></a> [<span style="color: #800000;">frankel.ch</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://lucumr.pocoo.org/2026/7/4/better-models-worse-tools/" target="_blank" rel="noopener"><strong>Better Models: Worse Tools</strong></a> [<span style="color: #800000;">lucumr.pocoo.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/07/sitar-agent-sidecar-config/" target="_blank" rel="noopener"><strong>Airbnb Shares Architecture behind Sitar-Agent Dynamic Configuration Sidecar for Kubernetes Services</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/exploring-gen-ai/local-models-for-coding-experiences.html" target="_blank" rel="noopener"><strong>Experiences with local models for coding</strong></a> [<span style="color: #800000;">martinfowler.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/exploring-gen-ai/local-models-for-coding-factors.html" target="_blank" rel="noopener"><strong>Viability of local models for coding</strong></a> [<span style="color: #800000;">martinfowler.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/07/08/glm-5-2-considerations-for-enterprise-teams-starting-out-with-open-weight-models.html" target="_blank" rel="noopener"><strong>GLM-5.2: Considerations for enterprise teams starting out with open-weight models</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/07/08/auditable-agentic-orchestration.html" target="_blank" rel="noopener"><strong>Auditable Agentic Orchestration: From Autonomous Systems to Governed Execution</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p>One more week:</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/courses/">&gt;&gt; The Summer Sale is Live</a></strong></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-654">Java Weekly, Issue 654</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/960038423/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/960038423/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/960038423/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/960038423/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/960038423/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/960038423/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-654#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-654/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/960038423/0/baeldung~Java-Weekly-Issue/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-sql-string-building</feedburner:origLink>
		<title>Building up a SQL Query String in Java</title>
		<link>https://feeds.feedblitz.com/~/959091590/0/baeldung~Building-up-a-SQL-Query-String-in-Java</link>
					<comments>https://feeds.feedblitz.com/~/959091590/0/baeldung~Building-up-a-SQL-Query-String-in-Java#respond</comments>
		
		<dc:creator><![CDATA[Ali Imran Nagori]]></dc:creator>
		<pubDate>Sat, 04 Jul 2026 21:28:22 +0000</pubDate>
				<category><![CDATA[Persistence]]></category>
		<category><![CDATA[JDBC]]></category>
		<category><![CDATA[popular]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[StringBuilder]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-sql-string-building</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="almost done featured" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to build dynamic SQL queries in Java using <em>StringBuilder</em>, <em>StringJoiner</em>, and <em>PreparedStatement</em> to avoid common pitfalls.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/959091590/0/baeldung~Building-up-a-SQL-Query-String-in-Java">Building up a SQL Query String in Java</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/959091590/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Core-Java-1.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-sql-string-building#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-sql-string-building/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="almost done featured" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>When writing <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/sql/">SQL</a> queries, we often need to build the query dynamically. For instance, dynamic string building is usually required when writing logic for filters, runtime-specified sorts, and optional join operations.</p>
<p>In this tutorial, we&#8217;ll see how Java creates SQL strings. Notably, we&#8217;ll use the Baeldung <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/sql/simple-schema">Simple University Schema</a>.</p>
<h2 id="bd-the-challenge-of-dynamic-queries" data-id="the-challenge-of-dynamic-queries">2. The Challenge of Dynamic Queries</h2>
<div class="bd-anchor" id="the-challenge-of-dynamic-queries"></div>
<p>Let&#8217;s suppose we&#8217;re building a student search feature. Consequently, a user can search for students using one or more optional filters:</p>
<ul>
<li>name</li>
<li>student ID</li>
<li>enrollment date</li>
<li>others</li>
</ul>
<p>Since filters may vary depending on search criteria, we need different or dynamic SQL queries.</p>
<p>If no filters are selected, we simply retrieve all students:</p>
<pre><code class="language-sql">SELECT * FROM Student;</code></pre>
<p>However, if we search only by name, the query becomes:</p>
<pre><code class="language-sql">SELECT * FROM Student WHERE name LIKE ?;</code></pre>
<p>If we search by both name and student ID, we need an additional condition:</p>
<pre><code class="language-sql">SELECT * FROM Student WHERE name LIKE ? AND id = ?;</code></pre>
<p>Finally, if all filters are provided, the query becomes:</p>
<pre><code class="language-sql">SELECT * FROM Student WHERE name LIKE ? AND id = ?
AND enrollment_date = ?;</code></pre>
<p>As the query grows, it becomes hard to maintain:</p>
<pre><code class="language-java">String sql = "SELECT s.id, s.name, s.national_id, s.birth_date, " 
  + "s.enrollment_date, s.graduation_date, s.gpa " 
  + "FROM students s " 
  + "WHERE s.gpa &gt;= " + minGpa + " " 
  + "AND s.enrollment_date &gt;= '" + enrollmentDate + "' " 
  + (includeGraduated ? "" : "AND s.graduation_date IS NULL ") 
  + "ORDER BY s.gpa DESC, s.name";</code></pre>
<p>As a result, readability becomes a key issue.</p>
<h2 id="bd-dynamic-sql-generation" data-id="dynamic-sql-generation">3. Dynamic SQL Generation</h2>
<div class="bd-anchor" id="dynamic-sql-generation"></div>
<p>When querying a database, users often sort the results with different filters. For example, they may choose to sort by name or date. Thus, <strong>we should be able to create an SQL query with dynamic parameters</strong>.</p>
<h3 id="bd-1-using-the-stringbuilder-class" data-id="1-using-the-stringbuilder-class">3.1. Using the <em>StringBuilder</em> Class</h3>
<div class="bd-anchor" id="1-using-the-stringbuilder-class"></div>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-strings-concatenation#string-builder"><em>StringBuilder</em></a> class in Java enables changes to string objects that are normally immutable in the <em>String</em> class.</p>
<p>Thus, we can assemble SQL queries in steps:</p>
<pre><code class="language-java">StringBuilder sb = new StringBuilder("SELECT id, name FROM Student");
if (enrollmentDate != null) {
    sb.append(" WHERE enrollment_date = ?");
}
if (birthDate != null) {
    sb.append(" AND birth_date = ?");
}</code></pre>
<p>Still, the above solution isn&#8217;t complete. For example, <strong>if the user provides a <em>null</em> value to <em>enrollment_date</em>, the <em>WHERE</em> clause doesn&#8217;t complete</strong>:</p>
<pre><code class="language-java">SELECT id, name FROM Student AND birth_date = ?</code></pre>
<p>Thus, the resulting SQL becomes invalid.</p>
<h3 id="bd-2-building-conditions-separately" data-id="2-building-conditions-separately">3.2. Building Conditions Separately</h3>
<div class="bd-anchor" id="2-building-conditions-separately"></div>
<p>Let&#8217;s consider another variant of the above code:</p>
<pre><code class="language-java">StringBuilder sb = new StringBuilder("SELECT id, name " + "FROM Student WHERE 1=1");
...</code></pre>
<p>In this case, <strong>we start with a new clause <em>WHERE 1=1</em>, which is always true</strong>. So, we can now start every conditional statement with <em>AND</em>:</p>
<pre><code class="language-java">if (enrollmentDate != null) {
    sb.append(" AND enrollment_date = ?");
}
if (birthDate != null) {
    sb.append(" AND birth_date = ?");
}</code></pre>
<p><strong>The parameters are now dynamically added to the SQL query</strong>. Lastly, we can execute this query using <em>PreparedStatement</em>.</p>
<h3 id="bd-3-execution-using-preparedstatement" data-id="3-execution-using-preparedstatement">3.3. Execution Using <em>PreparedStatement</em></h3>
<div class="bd-anchor" id="3-execution-using-preparedstatement"></div>
<p>Since we&#8217;ve used parameterized queries in the above code, we can use <em>PreparedStatement</em> to execute the query:</p>
<pre><code class="language-java">ps.setString(parameterIndex++, enrollmentDate);
ps.setString(parameterIndex++, birthDate);
</code></pre>
<p>In the above code, <em>ps</em> is the <em>PreparedStatement</em> object.</p>
<h2 id="bd-using-stringjoiner-java-8" data-id="using-stringjoiner-java-8">4. Using <em>StringJoiner</em> (Java 8)</h2>
<div class="bd-anchor" id="using-stringjoiner-java-8"></div>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-string-joiner"><em>StringJoiner</em></a> class in Java can join multiple strings. Thus, <strong>we can use it to connect multiple SQL strings</strong>:</p>
<pre><code class="language-java">String baseQuery = "SELECT * FROM Student";
StringJoiner whereClause = new StringJoiner(" AND ");
whereClause.add("name LIKE ?");
whereClause.add("id = ?");
whereClause.add("enrollment_date = ?");
</code></pre>
<p>In the above example, the search criteria (<em>name</em>, <em>id</em>, and <em>enrollment_date</em>) are dynamic and optional. As a result, we can dynamically build a SQL <em>WHERE</em> clause.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we went through building a SQL query string in Java. First, we saw how long queries become unmanageable. Then, we looked at how the <em>StringBuilder</em> and <em>StringJoiner</em> classes help build dynamic SQL queries.</p>
<p>The code backing this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/persistence-modules/core-java-persistence-4">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-sql-string-building">Building up a SQL Query String in Java</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/959091590/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/959091590/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Core-Java-1.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-sql-string-building#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-sql-string-building/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/959091590/0/baeldung~Building-up-a-SQL-Query-String-in-Java/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors</feedburner:origLink>
		<title>Building LLM-as-a-Judge Using Recursive Advisors in Spring AI</title>
		<link>https://feeds.feedblitz.com/~/959091593/0/baeldung~Building-LLMasaJudge-Using-Recursive-Advisors-in-Spring-AI</link>
					<comments>https://feeds.feedblitz.com/~/959091593/0/baeldung~Building-LLMasaJudge-Using-Recursive-Advisors-in-Spring-AI#comments</comments>
		
		<dc:creator><![CDATA[Ralf Ueberfuhr]]></dc:creator>
		<pubDate>Sat, 04 Jul 2026 21:18:53 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[OpenAI]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204189</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to implement the LLM-as-a-Judge pattern in Spring AI as a quality gate for LLM responses.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/959091593/0/baeldung~Building-LLMasaJudge-Using-Recursive-Advisors-in-Spring-AI">Building LLM-as-a-Judge Using Recursive Advisors in Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/959091593/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#comments"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&nbsp;
<div style="clear:left;"><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#comments"><h3>Comments</h3></a><ul><li><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#comment-15699">In reply to fantaman.   Hey, Fantaman.   Thanks for the ...</a> <i>by Ulisses Lima</i><li><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#comment-15698">Wouldn't it make sense to also include the LLM answer in the ...</a> <i>by fantaman</i></ul></div>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>With <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai">Spring AI</a>, we can integrate LLMs into our Spring applications. This is helpful, for example, for processing unstructured data like wiki pages, emails, or chat messages by extracting structured information to store it in a database. However, LLM responses are not deterministic and so neither testable nor handable by our application code. The best way to implement a kind of quality gate for LLM responses is to use an LLM again for evaluation<strong>.</strong> This pattern is called <em>LLM-as-a-judge</em>.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">In this tutorial, we&#8217;ll learn how to implement the LLM-as-a-Judge pattern in Spring AI.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-maven-dependencies" data-id="maven-dependencies">2. Maven Dependencies</h2>
<div class="bd-anchor" id="maven-dependencies"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">We add the Spring AI OpenAI starter to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-starter-model-openai&lt;/artifactId&gt;
    &lt;version&gt;${spring-ai.version}&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>The latest version of <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-openai">spring-ai-starter-model-openai</a> is available on Maven Central.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-the-llm-as-a-judge-pattern" data-id="the-llm-as-a-judge-pattern">3. The LLM-as-a-Judge Pattern</h2>
<div class="bd-anchor" id="the-llm-as-a-judge-pattern"></div>
<p>The LLM-as-a-Judge pattern is like a code review. A developer (the <em>generator</em>) writes code, and a senior engineer (the <em>judge</em>) reviews it and gives structured feedback. If the judge decides that the code doesn&#8217;t meet the requirements, the developer needs to improve it, and the judge then needs to review it again.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Applied to LLMs, the process is:</p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2">The <strong>generator</strong> produces an initial answer to the user&#8217;s question</li>
<li class="whitespace-normal break-words pl-2">The <strong>judge</strong> evaluates that answer against a rubric and returns a numeric score and written feedback</li>
<li class="whitespace-normal break-words pl-2">If the score is too low, the generator receives the original question <em>plus</em> the judge&#8217;s critique and produces a refined answer.</li>
<li>This will repeat until the answer meets the requirements. To avoid endless repetition in case of never reaching the needed quality, it&#8217;s a best practice to limit the number of loops.</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>This pattern improves output quality without model fine-tuning, without a human in the loop, and without changing a single line of calling code.</strong></p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">It&#8217;s most valuable wherever a weak answer has real consequences, e.g., customer support bots, where a vague answer to a billing question damages trust directly. In such cases, the pattern acts as an invisible quality gate between the model and the user.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-1-why-recursive-advisors-are-the-right-fit" data-id="1-why-recursive-advisors-are-the-right-fit">3.1. Why Recursive Advisors Are the Right Fit</h3>
<div class="bd-anchor" id="1-why-recursive-advisors-are-the-right-fit"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-advisors">Advisors in Spring AI</a> are interceptors that wrap the request/response cycle of a <em>ChatClient</em>. A <em>CallAroundAdvisor</em> can inspect a request before it reaches the model and modify the response before it reaches the caller, but it only ever makes one model call per invocation.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-recursive-advisors">Recursive advisors</a> go further: they hold an internal <em>ChatClient</em> reference and can make additional model calls from within the advisor itself.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>We can apply that capability to a specific problem: using the second model call as a judge, not just an extension.</strong> The generate-judge-refine loop becomes a single, self-contained component that&#8217;s completely transparent to the caller.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The caller just invokes <em>chatClient.prompt().user(&#8220;&#8230;&#8221;).call().content()</em> as usual. The judge&#8217;s logic is invisible.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-implementing-the-llm-as-a-judge-advisor" data-id="implementing-the-llm-as-a-judge-advisor">4. Implementing the LLM-as-a-Judge Advisor</h2>
<div class="bd-anchor" id="implementing-the-llm-as-a-judge-advisor"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Let&#8217;s build the feature in three parts: a value object for the verdict, a prompt template for the judge, and the advisor itself.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-1-the-verdict-record" data-id="1-the-verdict-record">4.1. The Verdict Record</h3>
<div class="bd-anchor" id="1-the-verdict-record"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The judge returns a structured verdict. We model it as a simple Java record:</p>
<pre><code class="language-java">public record Verdict(double score, String feedback) {}</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">In our sample, the <em>score</em> is a value between <em>0.0</em> (poor) and <em>1.0</em> (excellent). The <em>feedback</em> explains what&#8217;s missing or weak, and it&#8217;s what we&#8217;ll feed back to the generator during refinement.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-2-the-judge-prompt-template" data-id="2-the-judge-prompt-template">4.2. The Judge Prompt Template</h3>
<div class="bd-anchor" id="2-the-judge-prompt-template"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The judge needs a clear, structured system prompt. For this, let&#8217;s define it as a constant inside the advisor:</p>
<pre><code class="language-java">private static final String JUDGE_SYSTEM_PROMPT = """
    You are a strict quality evaluator for AI-generated answers.
    
    Given a user question and an AI-generated answer, rate the answer quality.
    
    Use this rubric:
    - 1.0: Complete, accurate, and clearly explained
    - 0.7: Mostly correct but missing details or clarity
    - 0.4: Partially correct or overly vague
    - 0.0: Incorrect, irrelevant, or harmful
    
    Respond ONLY with a valid JSON object. Do not add any explanation outside the JSON.
    Format: {"score": &lt;0.0 to 1.0&gt;, "feedback": "&lt;one concise sentence&gt;"}
    """;</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">It&#8217;s important to declare an explicit rubric and a strict JSON output to get a parseable and consistent answer from the judge model.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-3-the-advisor-class-and-its-dependencies" data-id="3-the-advisor-class-and-its-dependencies">4.3. The Advisor Class and Its Dependencies</h3>
<div class="bd-anchor" id="3-the-advisor-class-and-its-dependencies"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>The key design decision of a recursive advisor is that it holds its own <em>ChatClient</em> instance to make additional model calls independently of the advisor chain.</strong> We also inject the configurable quality thresholds:</p>
<pre><code class="language-java">public class LlmJudgeAdvisor implements CallAdvisor {
    private final ChatClient judgeClient;
    private final double scoreThreshold;
    private final int maxRefinements;
    public LlmJudgeAdvisor(
      ChatClient judgeClient,
      double scoreThreshold,
      int maxRefinements
    ) {
        this.judgeClient = judgeClient;
        this.scoreThreshold = scoreThreshold;
        this.maxRefinements = maxRefinements;
    }
    @Override
    public int getOrder() {
        return 0;
    }
    @Override
    public String getName() {
        return "LlmJudgeAdvisor";
    }
    // ...
}</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The <em>ChatClient.Builder</em> receives the same auto-configured model settings as the rest of the application. We could wire a different, specialized judge model here. The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.spring.io/spring-ai/reference/guides/llm-as-judge.html">official Spring AI docs</a> recommend this specifically to avoid the model judging its own output too leniently.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-4-evaluating-and-refining-the-response" data-id="4-evaluating-and-refining-the-response">4.4. Evaluating and Refining the Response</h3>
<div class="bd-anchor" id="4-evaluating-and-refining-the-response"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The <em>adviseCall()</em> method is the only entry point that the advisor chain calls. Rather than forwarding the request once, we call <em>chain.copy(this).nextCall(request)</em>, which creates a sub-chain that includes our advisor again. That&#8217;s what enables the loop to re-evaluate and re-generate across multiple attempts:</p>
<pre><code class="language-java">@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
    for (int attempt = 1; attempt &lt;= maxRefinements + 1; attempt++) { 
        ChatClientResponse response = chain.copy(this).nextCall(request);
        if (attempt &gt; maxRefinements) {
          return response;
        }
        Verdict verdict = evaluate(request, response); 
        if (verdict.score() &gt;= scoreThreshold) {
            return response;
        }
        request = addFeedback(request, verdict.feedback());
    }
    return chain.copy(this).nextCall(request);
}</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The loop needs an explicit upper bound to avoid unbounded recursion. On the last allowed attempt, the method returns the response immediately, skipping the evaluation. There&#8217;s no point scoring an answer we can&#8217;t act on. Otherwise, if the score meets <em>scoreThreshold,</em> it returns early. If not, it augments the request with the judge&#8217;s feedback and continues to the next iteration.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-5-evaluating-the-answer" data-id="5-evaluating-the-answer">4.5. Evaluating the Answer</h3>
<div class="bd-anchor" id="5-evaluating-the-answer"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>The <em>evaluate()</em> method sends the original question and the generated answer to the judge model.</strong> We can use  <em>.entity(&#8230;)</em> to render the response JSON directly into our record, without any manual JSON parsing:</p>
<pre><code class="language-java">private Verdict evaluate(ChatClientRequest request, ChatClientResponse response) {
    String question = request.prompt().getUserMessage().getText();
    String answer = response.chatResponse().getResult().getOutput().getText();
    return judgeClient.prompt()
      .system(JUDGE_SYSTEM_PROMPT)
      .user("Question: " + question + "\n\nAnswer: " + answer)
      .call()
      .entity(Verdict.class);
}</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Spring AI injects a JSON schema derived from the <em>Verdict</em> record into the model call and handles deserialization. This removes the need for any try-catch around JSON parsing and keeps the evaluation code focused on the prompt, not on format handling.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-6-augmenting-the-request-with-feedback" data-id="6-augmenting-the-request-with-feedback">4.6. Augmenting the Request With Feedback</h3>
<div class="bd-anchor" id="6-augmenting-the-request-with-feedback"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">When the rating falls short, we don&#8217;t start over with the original prompt. Instead, let&#8217;s augment it, i.e., we append the judge&#8217;s critique to the user message so the model has full context for its next attempt. The <em>addFeedback()</em> helper does this using <em>ChatClientRequest.mutate()</em>:</p>
<pre><code class="language-java">private ChatClientRequest addFeedback(ChatClientRequest original, String feedback) {
    Prompt augmented = original.prompt()
      .augmentUserMessage(msg -&gt; msg.mutate()
          .text(msg.getText()
              + "\n\nYour previous answer was insufficient. Feedback: " + feedback
              + "\nPlease provide an improved answer.")
          .build());
    return original.mutate().prompt(augmented).build();
}</code></pre>
<p><strong><em>augmentUserMessage()</em> gives us a mutable copy of the user message without touching the rest of the request.</strong> System prompt, tool definitions, and conversation history all stay intact. The updated <em>ChatClientRequest</em> is passed into the next loop iteration.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-configuring-the-application" data-id="configuring-the-application">5. Configuring the Application</h2>
<div class="bd-anchor" id="configuring-the-application"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Let&#8217;s declare both beans in a single <em>@Configuration</em> class. The <em>LlmJudgeAdvisor</em> bean receives its own <em>ChatClient</em> instance, built from the same auto-configured <em>ChatClient.Builder,</em> along with the quality thresholds from <em>application.properties</em>:</p>
<pre><code class="language-java">@Configuration
public class ChatConfig {
    @Bean
    public ChatClient chatClient(
      ChatClient.Builder builder, 
      LlmJudgeAdvisor judgeAdvisor
    ) {
        return builder
          .defaultAdvisors(judgeAdvisor)
          .build();
    }
    @Bean
    public LlmJudgeAdvisor llmJudgeAdvisor(
      ChatClient.Builder builder,
      @Value("${judge.score-threshold:0.7}") double scoreThreshold,
      @Value("${judge.max-refinements:2}") int maxRefinements
    ) {
        return new LlmJudgeAdvisor(
          builder.build(),
          scoreThreshold,
          maxRefinements
        );
    }
}</code></pre>
<p>We can then set the thresholds in <em>application.properties</em>:</p>
<pre><code class="language-properties">spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o-mini
judge.score-threshold=0.75
judge.max-refinements=2</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">A score threshold of <em>0.75</em> means the judge must rate the answer at least 75% quality before we accept it. Setting the number of maximum refinements to <em>2</em> caps the recursive loop at two improvement attempts, preventing runaway API usage.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-testing-the-advisor" data-id="testing-the-advisor">6. Testing the Advisor</h2>
<div class="bd-anchor" id="testing-the-advisor"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>Let&#8217;s implement a <em>@SpringBootTest</em> with a single mocked <em>ChatModel</em>.</strong> Both the generator and the judge use the same underlying instance<em>,</em> so one mock covers the entire call sequence. No API key is needed.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The test stubs four sequential <em>chatModel</em> responses matching the expected flow:</p>
<pre><code class="language-java">@SpringBootTest
class LlmJudgeAdvisorTest {
    @MockitoBean
    ChatModel chatModel;
    @Autowired
    ChatClient chatClient;
    @Test
    void givenLowQualityAnswer_whenAdvisorRuns_thenAnswerIsRefined() {
        when(chatModel.call(any(Prompt.class)))
          .thenReturn(buildChatResponse("It runs Java."))
          .thenReturn(buildChatResponse("{\"score\": 0.3, \"feedback\": \"Too vague.\"}"))
          .thenReturn(buildChatResponse(
            "The JVM executes Java bytecode, manages memory, and enables platform independence."))
          .thenReturn(buildChatResponse("{\"score\": 0.9, \"feedback\": \"Complete and accurate.\"}"));
        String result = chatClient.prompt()
          .user("Explain what a JVM is.")
          .call()
          .content();
        assertThat(result).contains("bytecode");
    }
    private ChatResponse buildChatResponse(String content) {
        return new ChatResponse(List.of(new Generation(new AssistantMessage(content))));
    }
}</code></pre>
<p>The four stubs map directly to the generate-evaluate-refine-evaluate cycle: call one is the weak generator response, call two is the judge&#8217;s low verdict, call three is the refined generator response, and call four is the judge&#8217;s passing verdict. The <em>chatClient</em> with the advisor is real. Only the model underneath is mocked. <strong>This means the full advisor logic runs as it would in production.</strong></p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">In this article, we implemented the LLM-as-a-Judge pattern in Spring AI using a recursive <em>CallAdvisor</em>. We saw how the generate-evaluate-refine loop maps naturally onto the advisor model: the first model call produces an answer, Spring AI&#8217;s structured output maps the judge&#8217;s response into a typed <em>Verdict</em>, and the loop augments the original request with feedback until the score is sufficient or the attempt limit is reached.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The result is a reusable component that improves output quality automatically and remains completely transparent to any caller using the <em>ChatClient</em>.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The code for this tutorial is available <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-llm-as-a-judge">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors">Building LLM-as-a-Judge Using Recursive Advisors in Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/959091593/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/959091593/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#comments"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&nbsp;
<div style="clear:left;"><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#comments"><h3>Comments</h3></a><ul><li><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#comment-15699">In reply to fantaman.   Hey, Fantaman.   Thanks for the ...</a> <i>by Ulisses Lima</i><li><a rel="NOFOLLOW" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#comment-15698">Wouldn't it make sense to also include the LLM answer in the ...</a> <i>by fantaman</i></ul></div>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/959091593/0/baeldung~Building-LLMasaJudge-Using-Recursive-Advisors-in-Spring-AI/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-653</feedburner:origLink>
		<title>Java Weekly, Issue 653</title>
		<link>https://feeds.feedblitz.com/~/958918577/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/958918577/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 03 Jul 2026 13:12:53 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204222</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Our summer lunch is finally live, and so is my Foundation's to AI coding course.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958918577/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 653</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958918577/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-653#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-653/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.allthingsdistributed.com/2026/06/return-to-two-pizza-culture.html">&gt;&gt; A return to two-pizza culture</a></strong> [<span style="color: #993300;">allthingsdistributed.com</span>]</p>
<p>Revisiting Amazon&#8217;s two-pizza team idea as an operating model for fast, autonomous, accountable engineering. The practical point: team size, ownership, architecture, and feedback loops shape each other. A useful read.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/security-baked-into-jvm/1/" target="_blank" rel="noopener"><strong>Security Baked Into the JVM: why fork Apache River and OpenJDK?</strong></a> [<span style="color: #800000;">frankel.ch</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/this-dependency-update-looked-exactly-like-an-account-takeover/" target="_blank" rel="noopener"><strong>This Dependency Update Looked Exactly Like an Account Takeover</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/7-new-vulnerabilities-in-jackson-in-one-day-this-is-what-ai-assisted-security-research-looks-like/" target="_blank" rel="noopener"><strong>7 New Vulnerabilities in Jackson in One Day: This Is What AI-Assisted Security Research Looks Like</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/endive-1-0-wasm/" target="_blank" rel="noopener"><strong>Endive 1.0 Is Here: Wasm on the JVM Ships Under the Bytecode Alliance</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/ai-assisted-unused-dead-code-removal/" target="_blank" rel="noopener"><strong>AI-Assisted Unused &amp; Dead Code Removal</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/21/spring-boot-41-and-spring-batch" target="_blank" rel="noopener"><strong>MongoDB-backed Spring Batch jobs and more in Spring Boot 4.1</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javaspecialists.eu/archive/Issue336-CopyOnWriteArrayList.subList-ConcurrentModificationException.html" target="_blank" rel="noopener"><strong>CopyOnWriteArrayList.subList() ConcurrentModificationException</strong></a> [<span style="color: #800000;">javaspecialists.eu</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/articles/tradeoffs-event-driven-design/" target="_blank" rel="noopener"><strong>Scaling Java-Based Real-Time Systems: The Hidden Tradeoffs of Event-Driven Design</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/06/eliya-jvm-diagnostic-profile/" target="_blank" rel="noopener"><strong>Eliya 25 Brings a JVM-Level Diagnostic Profile to OpenJDK 25 LTS</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/platform/2026/06/open-sourcing-the-lsp-client-api-in-intellij-idea-2026-2/" target="_blank" rel="noopener"><strong>Open-Sourcing the LSP Client API in IntelliJ IDEA 2026.2</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/06/29/agentrification.html" target="_blank" rel="noopener"><strong>Agentrification and the Agentrification Index</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/genpage-towards-end-to-end-generative-homepage-construction-at-netflix-77146fba8a08" target="_blank" rel="noopener"><strong>GenPage: Towards End-to-End Generative Homepage Construction at Netflix</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/02/simd-vectors-hotspot-jvm/" target="_blank" rel="noopener"><strong>SIMD Vectors in the HotSpot JVM &#8211; Auto Vectorization and the Vector API</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/06/30/zgc-performance-decade/" target="_blank" rel="noopener"><strong>ZGC: A Decade of Redefining Java Performance</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/02/a-bootiful-podcast-sebastien-deleuze" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Sébastien Deleuze on the latest-and-greatest in Spring AI and Spring Framework</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/07/intellij-idea-2026-1-4/" target="_blank" rel="noopener"><strong>IntelliJ IDEA 2026.1.4 Is Out!</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-37-1-released/" target="_blank" rel="noopener"><strong>Quarkus 3.37.1 &#8211; Maintenance release</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/graalvm/graalvm-ce-builds/releases/tag/graal-25.1.3" target="_blank" rel="noopener"><strong>GraalVM Community 25.1.3</strong></a> [<span style="color: #800000;">github.com/graalvm</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.1.11" target="_blank" rel="noopener"><strong>Jetty 12.1.11</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.37" target="_blank" rel="noopener"><strong>12.0.37</strong></a> [<span style="color: #800000;">github.com/jetty</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v8.19.18" target="_blank" rel="noopener"><strong>Elasticsearch 8.19.18</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.3.7" target="_blank" rel="noopener"><strong>9.3.7</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.4.3" target="_blank" rel="noopener"><strong>9.4.3</strong></a> [<span style="color: #800000;">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.18" target="_blank" rel="noopener"><strong>Zuul v3.6.18</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.17" target="_blank" rel="noopener"><strong>v3.6.17</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.2.0" target="_blank" rel="noopener"><strong>Grails 7.2.0</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.1.3" target="_blank" rel="noopener"><strong>7.1.3</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.0.13" target="_blank" rel="noopener"><strong>7.0.13</strong></a> [<span style="color: #800000;">github.com/apache</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.3" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.3</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.0.4" target="_blank" rel="noopener"><strong>5.0.4</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v3.10.9" target="_blank" rel="noopener"><strong>3.10.9</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.21.0" target="_blank" rel="noopener"><strong>Apache Camel 4.21.0</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.18.3" target="_blank" rel="noopener"><strong>4.18.3</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.14.8" target="_blank" rel="noopener"><strong>4.14.8</strong></a> [<span style="color: #800000;">github.com/apache</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">2.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/courses/">&gt;&gt; The Summer Sale is Live</a></strong></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-653">Java Weekly, Issue 653</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958918577/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958918577/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-653#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-653/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958918577/0/baeldung~Java-Weekly-Issue/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-dynamic-tool-discovery</feedburner:origLink>
		<title>Spring AI&#8217;s Dynamic Tool Discovery</title>
		<link>https://feeds.feedblitz.com/~/958677014/0/baeldung~Spring-AIs-Dynamic-Tool-Discovery</link>
					<comments>https://feeds.feedblitz.com/~/958677014/0/baeldung~Spring-AIs-Dynamic-Tool-Discovery#respond</comments>
		
		<dc:creator><![CDATA[Kostiantyn Ivanov]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 02:50:03 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[popular]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204066</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use Tool Search Tool in Spring AI to find available tools without wasting extra tokens.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958677014/0/baeldung~Spring-AIs-Dynamic-Tool-Discovery">Spring AI’s Dynamic Tool Discovery</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958677014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>When we build AI-integrated systems, we often provide our AI clients with a large number of tools. On every request, we send the definitions of all available tools to the LLM so it can decide which ones to use. As a result, we waste a significant number of tokens before the model even processes the user query.  <strong>In this article, we explore how we solve this issue using the Tool Search Tool. </strong></p>
<h2 id="bd-how-the-tool-search-tool-works" data-id="how-the-tool-search-tool-works">2. How the Tool Search Tool Works</h2>
<div class="bd-anchor" id="how-the-tool-search-tool-works"></div>
<p>Using the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/spring-ai-community/spring-ai-tool-search-tool">Tool Search Tool</a><em>,</em> we don&#8217;t send all the tool definitions with the context. We only expose tools when the model actually needs them. First, we index all registered tools at startup. We store them inside the <em>ToolSearcher</em>, but we do NOT send them to the LLM. Next, we send only the Tool Search Tool in the initial request. This keeps the prompt small and focused. When the model needs a capability, it calls the Tool Search Tool using a natural-language query.</p>
<p>We treat this as a discovery signal and trigger a search over the indexed tools using the configured strategy. Next, we return only the most relevant matches from the ToolSearcher and inject their definitions into the next LLM request, so the model sees a focused set of tools instead of the full registry.</p>
<p data-start="305" data-end="488" data-is-last-node="" data-is-only-node=""><strong>Once the relevant tools are available, the model selects and calls the actual tool. We execute it and send the result back to the LLM, which then uses it to generate the final answer.</strong></p>
<h2 id="bd-building-a-travel-assistant-example" data-id="building-a-travel-assistant-example">3. Building a Travel Assistant Example</h2>
<div class="bd-anchor" id="building-a-travel-assistant-example"></div>
<p data-start="54" data-end="187">Let&#8217;s build a travel assistant that helps users plan trips. We connect multiple tools such as flights, hotels, weather, and attractions. <strong>We use the Tool Search Tool approach to avoid sending all tools to the LLM upfront. Instead, we discover tools dynamically at runtime.</strong></p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">3.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p>We start by adding <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/tool-search-tool">tool search</a> dependency support:</p>
<pre><code class="language-">&lt;dependency&gt;
    &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
    &lt;artifactId&gt;tool-search-tool&lt;/artifactId&gt;
    &lt;version&gt;${tool-search-tool.version}&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>Also, let&#8217;s add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/tool-searcher-regex/2.1.0">regex searcher</a> dependency:</p>
<pre><code class="language-">&lt;dependency&gt;
    &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
    &lt;artifactId&gt;tool-searcher-regex&lt;/artifactId&gt;
    &lt;version&gt;${tool-search-tool.version}&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Using it, we&#8217;ll have a <em>regex</em> tool search strategy. The other available strategies can be found in the project repository.</p>
<h3 id="bd-2-flight-tools" data-id="2-flight-tools">3.2. Flight Tools</h3>
<div class="bd-anchor" id="2-flight-tools"></div>
<p>Let’s create a simple <em>FlightTools</em>. We’ll use this tool to retrieve available flight options. In addition, we’ll create a bunch of artificial tools to simulate context overloading<em>:</em></p>
<pre><code class="language-java">public class FlightTools {
    @Tool(description = "Searches available flights between two cities")
    public List&lt;FlightOption&gt; searchFlights(String from, String to, String departureDate) {
        return List.of(
          new FlightOption(
            "Romania Airlines",
            from,
            to,
            departureDate,
            249.99
          )
        );
    }
}</code></pre>
<p>Here we return a single flight option.</p>
<h3 id="bd-3-tokencounteradvisor" data-id="3-tokencounteradvisor">3.3. <em>TokenCounterAdvisor</em></h3>
<div class="bd-anchor" id="3-tokencounteradvisor"></div>
<p>Now let’s create a simple <em>TokenCounterAdvisor</em> that counts the number of tokens used to produce the final result. We’ll use it to compare token usage between different setups, with and without tool search enabled:</p>
<pre><code class="language-java">public class TokenCounterAdvisor implements BaseAdvisor {
    private static final Logger log = LoggerFactory.getLogger(TokenCounterAdvisor.class);
    private final AtomicInteger totalTokenCounter = new AtomicInteger(0);
    @Override
    public String getName() {
        return "TokenCounterAdvisor";
    }
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 1;
    }
    @Override
    public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
        return chatClientRequest;
    }
    @Override
    public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
        var usage = chatClientResponse.chatResponse().getMetadata().getUsage();
        totalTokenCounter.addAndGet(usage.getTotalTokens());
        log.info("Total tokens spent: {}", totalTokenCounter.get());
        return chatClientResponse;
    }
}</code></pre>
<p>Here we store the number of tokens in an <em>AtomicInteger</em> field and log this information during execution. <strong>We attach this advisor to the maximum order, so it runs at the end of the processing pipeline. As a result, it captures the total token usage after all other advisors complete.</strong></p>
<h3 id="bd-4-configuration" data-id="4-configuration">3.4. Configuration</h3>
<div class="bd-anchor" id="4-configuration"></div>
<p>Next, we add the <em>TravelAssistantConfig</em> implementation:</p>
<pre><code class="language-java">@Configuration
public class TravelAssistantConfig {
    @Bean
    ToolSearcher toolSearcher() {
        return new RegexToolSearcher();
    }
    @Bean
    ToolSearchToolCallAdvisor toolSearchToolCallAdvisor(ToolSearcher toolSearcher) {
        return ToolSearchToolCallAdvisor.builder()
          .toolSearcher(toolSearcher)
          .maxResults(5)
          .build();
    }
    @Bean
    ChatClient chatClient(ToolSearchToolCallAdvisor toolSearchToolCallAdvisor, OpenAiChatModel model) {
        return ChatClient.builder(model)
          .defaultTools(
            new FlightTools(),
            new RandomTools()
          )
          .defaultAdvisors(toolSearchToolCallAdvisor, new TokenCounterAdvisor())
          .build();
    }
    @Bean
    ChatClient chatClientWithoutToolsSearch(OpenAiChatModel model) {
        return ChatClient.builder(model)
          .defaultTools(
            new FlightTools(),
            new RandomTools()
          )
          .defaultAdvisors(new TokenCounterAdvisor())
          .build();
    }
}</code></pre>
<p>We configure a travel assistant that uses dynamic tool discovery instead of loading all tools into the LLM. Next, we set up a <em>ToolSearcher</em> with a <em>RegexToolSearcher</em> implementation. This allows us to match tools based on naming patterns and fast keyword-like queries. Then, we create a <em>ToolSearchToolCallAdvisor</em> and connect it to the searcher. After that, we build the <em>ChatClient</em> with the flight tools registered.</p>
<p><span style="margin: 0px;padding: 0px">By design, we&#8217;ve added <em>RandomTools</em>, which includes many unrelated tool definitions</span>.  However, we do not send these tool definitions to the LLM initially. Instead, we only index them in the system. <strong>Finally, we expose only the Tool Search Tool to the model at the start. The model then uses it to discover which tools it actually needs for a given request.</strong> Additionally, we&#8217;ve configured a separate <em>ChatClient</em> bean that doesn&#8217;t use the <em>ToolSearchToolCallAdvisor</em>.</p>
<h3 id="bd-5-call-the-travelassistant" data-id="5-call-the-travelassistant">3.5. Call the TravelAssistant</h3>
<div class="bd-anchor" id="5-call-the-travelassistant"></div>
<p>Finally, let&#8217;s create a <em>ToolsSearchToolLiveTest</em> with similar test cases for both clients:</p>
<pre><code class="language-java">@SpringBootTest
@ActiveProfiles("toolsearchtool")
class ToolsSearchToolLiveTest {
    @Autowired
    private ChatClient chatClient;
    @Autowired
    private ChatClient chatClientWithoutToolsSearch;
    @Test
    void shouldFindFlightsBetweenRomaniaAndCroatiaUsingToolsSearch() {
        String response = getClientResponseString(chatClient);
        assetClientResponse(response);
    }
    @Test
    void shouldFindFlightsBetweenRomaniaAndCroatiaWithoutToolsSearch() {
        String response = getClientResponseString(chatClientWithoutToolsSearch);
        assetClientResponse(response);
    }
    
    private static void assetClientResponse(String response) {
        assertThat(response).isNotBlank();
        assertThat(response).containsIgnoringCase("Croatia");
        assertThat(response).containsIgnoringCase("flight");
    }
    private String getClientResponseString(ChatClient chatClientWithoutToolsSearch) {
        return chatClientWithoutToolsSearch.prompt()
          .user("""
                  Find available flights from Romania to Croatia next week.
                  """)
          .call()
          .content();
    }
}</code></pre>
<p>We&#8217;ve called our travel advisor clients with the same prompt and obtained the same verified results. Now, let&#8217;s compare the token usage in both of them:</p>
<pre><code class="language-">[2026-05-24 11:39:07] [INFO] [c.b.s.t.TokenCounterAdvisor] - Total tokens spent: 974 //With tools search tool
[2026-05-24 11:39:10] [INFO] [c.b.s.t.TokenCounterAdvisor] - Total tokens spent: 3685 //Without tools search tool</code></pre>
<p>As we can see, the difference in token usage is crucial. <strong>The more tools we have in our system, the greater the token savings the Tool Search Tool will provide.</strong></p>
<h2 id="bd-conclusion" data-id="conclusion">4. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we reviewed the Tool Search Tool and demonstrated how it helps reduce token usage in real scenarios. Using it, we can build large AI-integrated systems with hundreds of attached tools and use them efficiently, without wasting tokens. Additionally, we can explore other tool search strategies, such as vector search, or even build our own custom strategy to make tool discovery even more efficient.</p>
<p>As always, the code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-4">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-dynamic-tool-discovery">Spring AI’s Dynamic Tool Discovery</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958677014/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958677014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958677014/0/baeldung~Spring-AIs-Dynamic-Tool-Discovery/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
</channel></rss>

