<?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>Sun, 19 Jul 2026 12:19:27 +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-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;" fetchpriority="high" /><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" 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="(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;" /><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>
<item>
<feedburner:origLink>https://www.baeldung.com/java-26-new-features</feedburner:origLink>
		<title>New Features in Java 26</title>
		<link>https://feeds.feedblitz.com/~/958677017/0/baeldung~New-Features-in-Java</link>
					<comments>https://feeds.feedblitz.com/~/958677017/0/baeldung~New-Features-in-Java#respond</comments>
		
		<dc:creator><![CDATA[Karthikeya Tatavarthi]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 02:46:15 +0000</pubDate>
				<category><![CDATA[Core Java]]></category>
		<category><![CDATA[>= Java 26]]></category>
		<category><![CDATA[popular]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-26-new-features</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>Explore all key features introduced in Java 26.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958677017/0/baeldung~New-Features-in-Java">New Features in Java 26</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/958677017/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/958677017/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/958677017/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/958677017/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/958677017/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-26-new-features#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-26-new-features/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-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>Oracle released Java 26 in March, which offered meaningful features that boost performance, new APIs, and preview features that may become permanent features in future releases. <strong>This release primarily focused on improvements in areas like garbage collection, networking, startup performance, cryptography, and other language features.</strong></p>
<p>In this tutorial, we’ll briefly talk about all key features introduced in Java 26.</p>
<h2 id="bd-restricts-reflective-modification-of-final-fields" data-id="restricts-reflective-modification-of-final-fields"><strong>2. Restricts Reflective Modification of <em>final</em> Fields</strong></h2>
<div class="bd-anchor" id="restricts-reflective-modification-of-final-fields"></div>
<p>We create <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-final"><em>final</em> variables</a> to ensure developers that their values can&#8217;t be changed after initialization. This will help us create immutable objects and help the JVM perform certain optimizations. However, this can be breached using reflections.</p>
<p><strong>There are libraries and frameworks that use reflections to modify final fields after an object has been created.</strong> This will invalidate the assumptions made by developers, and JVM optimizations may no longer hold true.</p>
<p>Starting from Java 26, the platform takes another step towards stronger encapsulation and integrity. When code tries to modify a final field using deep reflection, the JVM shows warnings indicating to the developer that the operation relies on a behavior that is being phased out. Even though this is currently not enforced, the direction is clear; future Java releases are expected to impose stronger restrictions.</p>
<p>This will impact certain serialization libraries, dependency injection frameworks, mocking tools, and legacy code that bypasses constructors to populate object state.</p>
<p><strong>Developers should now start to audit and identify reflective writes to final fields and migrate toward supported alternatives such as constructors, builders, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/factory-method-vs-factory-vs-abstract-factory">factory methods</a>, or records or use framework-provided APIs designed for immutable objects.</strong></p>
<h2 id="bd-removal-of-applet-api" data-id="removal-of-applet-api"><strong>3. Removal of Applet API</strong></h2>
<div class="bd-anchor" id="removal-of-applet-api"></div>
<p>Applets were historically used to run Java applications in browsers. However, modern browsers don&#8217;t support applets due to performance, compatibility, and security concerns. This is considered an outdated technology, and this API has been marked deprecated since Java 9. Developers nowadays use much better alternatives like Swing/<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/javafx">JavaFX</a> for UI.</p>
<p>With Java 26, the applet API is no longer existent.<strong> This update helps Oracle keep Java streamlined and secured while also reducing the maintenance burden. Its removal will affect only very old legacy applications that still depend on it.</strong></p>
<h2 id="bd-ahead-of-time-object-caching-with-any-gc" data-id="ahead-of-time-object-caching-with-any-gc"><strong>4. Ahead of Time Object Caching with Any GC</strong></h2>
<div class="bd-anchor" id="ahead-of-time-object-caching-with-any-gc"></div>
<p>Java is known for building distributed cloud-native applications that are frequently deployed, scaled, started, and stopped. Oracle is working towards bringing enhancements that reduce the start-up overhead, which allows applications to reach their peak performance more quickly. In Java 26, Oracle introduced one such enhancement.</p>
<p>Java has Ahead of Time (AOT) object caching, which identifies the objects that are expensive to create during startup and stores them in a cache. <strong>When the application starts again, these objects are loaded directly from the cache instead of recreating them, thereby reducing the start-up overhead.</strong></p>
<p>The benefits of Ahead of Time (AOT) are limited to specific GC configurations. <strong>However, in Java 26, this feature is available to any garbage collector, including low-latency collectors such as Z Garbage Collector (ZGC).</strong></p>
<p>This enhancement allows developers to take advantage of AOT object caching without giving up their preferred garbage collector.</p>
<h2 id="bd-http3-for-the-http-client-api" data-id="http3-for-the-http-client-api"><strong>5. HTTP/3 for the HTTP Client API</strong></h2>
<div class="bd-anchor" id="http3-for-the-http-client-api"></div>
<p>Network resilience and latency are key factors for applications that communicate heavily over the network, like microservices, real-time applications, and cloud-native services. <strong>With Java 26, the HTTP Client API now supports HTTP/3, which is the latest version of the HTTP protocol.</strong></p>
<p>While HTTP/1.1 and HTTP/2 were built on top of TCP, HTTP/3 uses the QUIC transport protocol, which runs on UDP and addresses some of the limitations of the TCP protocol. With TCP, a packet loss can delay all streams that are sharing the same connection, while QUIC allows streams to progress independently. QUIC achieves this by combining both transport and security handshakes to reduce the number of network round trips required before data starts flowing.</p>
<p><strong>Another practical advantage is automatic protocol negotiation. If the target server supports HTTP/3, the client can use it. If not, it can seamlessly fall back to HTTP/2 or HTTP/1.1, ensuring compatibility with existing infrastructure.</strong></p>
<p>With the introduction of HTTP/3, applications can now experience reduced latency and improved responsiveness. This is especially useful for real-time and cloud-native applications.</p>
<p>Let&#8217;s now take a look at a quick example:</p>
<pre><code class="language-java">@Test
void givenHttp3Client_whenSendingARequest_thenShouldReceiveSuccessfulResponse()
    throws IOException, InterruptedException {
    HttpClient client = HttpClient.newBuilder()
      .version(HttpClient.Version.HTTP_3)
      .build();
    HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://example.com"))
      .GET()
      .build();
    HttpResponse&lt;String&gt; response =
      client.send(request, HttpResponse.BodyHandlers.ofString());
    assertEquals(200, response.statusCode());
}</code></pre>
<p>To use HTTP/3, we configure the <em>HttpClient</em> by specifying <em>HttpClient.Version.HTTP_3</em> while building the client. We then create a simple <em>GET</em> request to <em>https://example.com</em> and send it synchronously using the <em>send()</em> method. Finally, we verify that the request completes successfully by asserting that the response status code is <em>200</em>.</p>
<p>It&#8217;s worth noting that configuring the client for HTTP/3 doesn&#8217;t guarantee that the connection will use HTTP/3. If the target server doesn&#8217;t support HTTP/3, the client automatically falls back to an earlier HTTP version (HTTP/2 or HTTP/1.1), while still completing the request successfully.</p>
<h2 id="bd-g1-garbage-collector-throughput-improvements" data-id="g1-garbage-collector-throughput-improvements"><strong>6. G1 Garbage Collector Throughput Improvements</strong></h2>
<div class="bd-anchor" id="g1-garbage-collector-throughput-improvements"></div>
<p>For quite some time, the Garbage First (G1) garbage collector has been Java&#8217;s default <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jvm-garbage-collectors">garbage collector</a>. It offers an impressive blend of throughput, memory, and predictability of pause times.</p>
<p>A typical modern Java application will constantly create or update object references. G1 is responsible for tracking object reference changes to perform garbage collection. Ultimately, there is some overhead in this tracking, particularly in applications with high allocation rates or frequent object updates.</p>
<p>With Java 26, G1 now has a set of internal changes that improve the throughput of an application. <strong>One such change is reducing the need for mutator application threads and garbage collection (GC) threads to synchronize when tracking the changes of which object references have been changed. </strong> Because of these improvements, application threads will synchronously wait less time related to GC and more time performing application logic.</p>
<h2 id="bd-pem-encodings-of-cryptographic-objects-second-preview" data-id="pem-encodings-of-cryptographic-objects-second-preview"><strong>7. PEM Encodings of Cryptographic Objects (Second Preview)</strong></h2>
<div class="bd-anchor" id="pem-encodings-of-cryptographic-objects-second-preview"></div>
<p>Applications that deal with security often need to handle certificates, public and private keys, and certificate chains. <strong>Cryptographic objects are usually stored and transmitted using PEM (Privacy Enhanced Mail)</strong>. PEM is a text-based format that allows binary security data to be formatted in a compact manner.</p>
<p>Java developers have historically been required to deploy third-party libraries or write code of their own to read and write PEM files. This has made security-related applications both complex and cumbersome due to additional dependencies and code that serves little or no purpose.</p>
<p>Java 26 introduces the second preview of a newly proposed API to PEM format cryptographic objects that adds the capability to Java SE.<strong> Using the proposed API brings the capability to Java developers to read and write certificates and keys using the standard Java SDK instead of using libraries.</strong></p>
<p>The proposed API enhances Java&#8217;s capability to integrate with standard security protocols and infrastructure. It also simplifies the management of certificates and keys. In addition, the API reduces the time and effort to write custom code for applications. Even if it is still a preview, it provides a more consistent and pragmatic approach to Java security.</p>
<h2 id="bd-structured-concurrency-sixth-preview" data-id="structured-concurrency-sixth-preview"><strong>8. Structured Concurrency (Sixth Preview)</strong></h2>
<div class="bd-anchor" id="structured-concurrency-sixth-preview"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-structured-concurrency">Structured concurrency</a> adds to Project Loom&#8217;s collection of tools to simplify concurrent programming in Java by proposing that multiple related tasks be treated as a unit of work, rather than requiring developers to manage multiple threads separately.</p>
<p>In traditional concurrent code, coordinating threads, handling failures, and cleaning up resources can become complex. <strong>Structured concurrency allows related tasks to run within the defined scope, and if one task fails, other related tasks will be cancelled automatically</strong>. This helps prevent wasted work.</p>
<p>Java 26 includes the sixth preview of Structured Concurrency, bringing further refinements based on real-world usage and community feedback as the feature moves closer to becoming a permanent part of the platform in the future.</p>
<h2 id="bd-lazy-constants-second-preview" data-id="lazy-constants-second-preview"><strong>9. Lazy Constants (Second Preview)</strong></h2>
<div class="bd-anchor" id="lazy-constants-second-preview"></div>
<p>In a normal scenario, class constants are created when the class gets loaded, even though the application never uses them. This contributes to increased startup time and creation of unnecessary objects.</p>
<p>To address this, developers used to introduce their own code to create constants only when they were actually needed. Often this code is complex, less readable, and harder to maintain.</p>
<p><strong>With lazy constants, this mechanism is now built in. Developers can now reap the benefits of lazy loading without having to introduce complex code.</strong> By creating objects only when they are first used, applications can now start faster and use fewer resources. Java 26 includes the second preview of this feature.</p>
<h2 id="bd-10-vector-api-eleventh-incubator" data-id="10-vector-api-eleventh-incubator"><strong>10. Vector API (Eleventh Incubator)</strong></h2>
<div class="bd-anchor" id="10-vector-api-eleventh-incubator"></div>
<p>Vector API in Java 26 is an incubator feature, which allows developers to take advantage of SIMD (Single Instruction, Multiple Data).</p>
<p>Normally, a processor performs an operation on one value at a time. With SIMD, the same operation can be performed on multiple values at once. This helps applications achieve performance closer to native code while remaining portable across different CPU architectures.</p>
<p><strong>This will significantly improve the performance for computation-heavy workloads such as scientific computing, data analytics, image processing, machine learning, and financial calculations.</strong></p>
<h2 id="bd-11-primitive-types-in-patterns-instanceof-and-switch-fourth-preview" data-id="11-primitive-types-in-patterns-instanceof-and-switch-fourth-preview"><strong>11. Primitive Types in Patterns, instanceof, and switch (Fourth Preview)</strong></h2>
<div class="bd-anchor" id="11-primitive-types-in-patterns-instanceof-and-switch-fourth-preview"></div>
<p>Pattern matching makes code easier to read and reduces the need for manual type checks and casting. In previous releases, this is only supported for reference types.</p>
<p><strong>However, with Java 26, this is also included for primitive types like int, long, float, and double.</strong> This allows developers to work with primitive values more naturally in pattern matching constructs, including switch statements and other pattern-based control flow.</p>
<h2 id="bd-12-conclusion" data-id="12-conclusion"><strong>12. Conclusion</strong></h2>
<div class="bd-anchor" id="12-conclusion"></div>
<p>We’ve seen various improvements and new features introduced in Java 26. While they are a mix of permanent and preview features, it’s evident that the Java community is moving towards making Java ideal for modern-day enterprise application development.</p>
<p><span style="font-weight: 400">The source code for the examples can be found </span><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-26-new-features"><span style="font-weight: 400">over on GitHub</span></a><span style="font-weight: 400">.</span></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-26-new-features">New Features in Java 26</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/958677017/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958677017/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/958677017/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/958677017/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/958677017/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/958677017/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-26-new-features#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-26-new-features/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/~/958677017/0/baeldung~New-Features-in-Java/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>
</channel></rss>

