<?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>Fri, 30 Oct 2026 16:16:14 +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-669</feedburner:origLink>
		<title>Java Weekly, Issue 669</title>
		<link>https://feeds.feedblitz.com/~/972125879/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/972125879/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 30 Oct 2026 16:16:14 +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=205316</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>Java 21 to 25 upgrades and lots of solid security releases. As expected after the weeks we've had :)</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/972125879/0/baeldung">Java Weekly, Issue 669</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/972125879/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/972125879/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 href="https://feeds.feedblitz.com/_/24/972125879/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/972125879/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/972125879/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-669#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-669/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://foojay.io/today/upgrading-java-21-to-25-what-actually-breaks/">&gt;&gt; Upgrading from Java 21 to 25: What Actually Breaks</a></strong> [<span style="color: #993300;">foojay.io</span>]</p>
<p>A walk through the upgrade traps between Java 21 and 25. Reproducible examples and a practical migration checklist, so a solid piece.</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/6/" target="_blank" rel="noopener"><strong>Security Baked Into the JVM: DirtyChai scales vertically, JGDMS scales horizontally</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://in.relation.to/2026/10/22/quest-for-reflection-free-access/" target="_blank" rel="noopener"><strong>The quest for reflection-free access (Part 1)</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://in.relation.to/2026/10/28/peek-behind-the-curtain-of-accessor-codegen/" target="_blank" rel="noopener"><strong>A peek behind the curtain of modern runtime codegen (Part 2)</strong></a> [<span style="color: #800000;">relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/10/jep-543-jdk-28/" target="_blank" rel="noopener"><strong>JEP 543 Finalizes Structured Concurrency in JDK 28, Ending Seven Preview Rounds</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/10/helidon-27-released/" target="_blank" rel="noopener"><strong>Helidon 27 Introduces Restructured Programming Models and New Messaging and Data Modules</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> Writing Unit Tests for &#8220;Normal&#8221; Controllers With Spring MockMvc: <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.petrikainulainen.net/programming/testing/writing-unit-tests-for-normal-controllers-with-spring-mockmvc-test-slice-configuration/" target="_blank" rel="noopener"><strong>Test Slice Configuration</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.petrikainulainen.net/programming/testing/writing-unit-tests-for-normal-spring-mvc-controllers-configuration/" target="_blank" rel="noopener"><strong>Standalone Configuration</strong></a> [<span style="color: #800000;">petrikainulainen.net</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/10/28/spring-ai-message-parts-reasoning-replay/" target="_blank" rel="noopener"><strong>Spring AI 2.1 Message Parts: Reasoning That Survives the Round Trip</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/10/28/spring-ai-upsert-own-embeddings/" target="_blank" rel="noopener"><strong>Using Your Own Embeddings in Spring AI 2.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://foojay.io/today/java-new-features-in-production/" target="_blank" rel="noopener"><strong>New Java Features Are Not Just for Interviews. Two Real-World Use Cases from Axelix</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/arcana-pure-java-archive-library/" target="_blank" rel="noopener"><strong>Arcana: One Pure-Java Library for (Almost) Every Archive Format</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/java-stream-gatherers/" target="_blank" rel="noopener"><strong>Java Stream Gatherers: From Built-in Patterns to Custom Pipeline Mechanics</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.agilejava.eu/2026/10/15/the-token-apocalypse-is-approaching-and-then-what/" target="_blank" rel="noopener"><strong>The Token Apocalypse is Approaching – and then what?</strong></a> [<span style="color: #800000;">agilejava.eu</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/10/27/quality-heads-up/" target="_blank" rel="noopener"><strong>Quality Outreach Heads-up &#8211; JDK 28: Value Classes</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/10/25/jep544-target-jdk28/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 28: 544: Ahead-of-Time Code Compilation</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://foojay.io/today/java-server-work-before-startup/" target="_blank" rel="noopener"><strong>A Smaller, Faster Java Server with a Spring-Style API</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/lightweight-spring-boot-monitoring/" target="_blank" rel="noopener"><strong>Lightweight Spring Boot Monitoring Without Prometheus and Grafana</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/what-i-learned-building-an-ai-code-reviewer-with-java-spring-boot/" target="_blank" rel="noopener"><strong>What I Learned Building an AI Code Reviewer with Java and Spring Boot</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://nipafx.dev/talk-modern-java" target="_blank" rel="noopener"><strong>Modern Java &#8211; Past | Present | Future</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://nipafx.dev/inside-java-newscast-116" target="_blank" rel="noopener"><strong>OpenJDK Bug Fixing, JavaOne, and JDK 28 &#8211; Inside Java Newscast #116</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-265-observability-and-evaluation-with-langfuse/" target="_blank" rel="noopener"><strong>Quarkus Insights #265: Observability and Evaluation with Langfuse</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://quarkus.io/blog/quarkus-insights-264-jobrunr-failure-handling/" target="_blank" rel="noopener"><strong>Quarkus Insights #264: JobRunr 9 on Quarkus 3.40 LTS: What Happens When a Job Fails Halfway?</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://vladmihalcea.com/jenesis-build-tool/" target="_blank" rel="noopener"><strong>A beginner’s guide to the Jenesis build tool</strong></a> [<span style="color: #800000;">vladmihalcea.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/what-jstack-misses-when-your-java-service-uses-virtual-threads/" target="_blank" rel="noopener"><strong>What jstack Misses When Your Java Service Uses Virtual Threads</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/bxplaywright-10-real-browsers-fluent-boxlang-built-for-ai-agents/" target="_blank" rel="noopener"><strong>BxPlaywright 1.0: Real Browsers, Fluent BoxLang, Built for AI Agents</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/foojay-podcast-103/" target="_blank" rel="noopener"><strong>Foojay Podcast #103: Killing Reflection at Build Time with Dmitry Moskalyuk and Shai Almog</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://inside.java/2026/10/28/java-secure-coding-guidelines/" target="_blank" rel="noopener"><strong>Java Secure Coding Guidelines</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/10/22/jvm-diagnosis/" target="_blank" rel="noopener"><strong>What&#8217;s Going Wrong with My JVM?! A Guide to First-Time Failure Diagnosis</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/10/20/devoxxbelgium-bringing-gpu-computing-to-java/" target="_blank" rel="noopener"><strong>Reflect, Transform, Accelerate: Bringing GPU Computing to Java</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/10/15/a-bootiful-podcast-andrew-lombardi/" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Can Java be more performant than Rust? Andrew Lombardi has the numbers</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/10/19/spring-office-hours-podcast-S5E26/" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E26 &#8211; dev2Next, Native JSON and a Week of New Models</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/10/22/a-bootiful-podcast-anton-arhipov/" target="_blank" rel="noopener"><strong>A Bootiful Podcast: A Bootiful Podcast: Anton Arhipov on AI Development within JetBrains IDEs</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/10/26/spring-office-hours-podcast-S5E27/" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E27 &#8211; Hugging Face for Java Developers with Pratik Patel</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://in.relation.to/2026/10/28/orm-80-beta4/" target="_blank" rel="noopener"><strong>Hibernate 8.0.0.Beta4</strong></a> [<span style="color: #800000;">relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/10/intellij-idea-s-java-and-kotlin-for-cursor-and-vs-code-hits-1-0/" target="_blank" rel="noopener"><strong>IntelliJ IDEA’s Java and Kotlin for Cursor and VS Code Hits 1.0</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://www.wildfly.org/news/2026/10/22/WildFly-42-is-released/" target="_blank" rel="noopener"><strong>WildFly 42 is released!</strong></a> [<span style="color: #800000;">wildfly.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wildfly.org/news/2026/10/21/A2A-Jakarta-1-1-0-Final-is-released/" target="_blank" rel="noopener"><strong>A2A Jakarta 1.1.0.Final is released!</strong></a> [<span style="color: #800000;">wildfly.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/duckdb-2-0-is-here/" target="_blank" rel="noopener"><strong>DuckDB 2.0 Is Here</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/owasp-java-security-libraries-please-update/" target="_blank" rel="noopener"><strong>OWASP Java Security Libraries: Please Update!</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/boxlang-1-18-0-released-every-cloud-every-node-every-agent/" target="_blank" rel="noopener"><strong>BoxLang 1.18.0 Released: Every Cloud, Every Node, Every Agent</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> Quarkus <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-40-1-1-released/" target="_blank" rel="noopener"><strong>3.40.1.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-33-4-1-released/" target="_blank" rel="noopener"><strong>3.33.4.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-27-6-1-released/" target="_blank" rel="noopener"><strong>3.27.6.1</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> Vert.x <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.2.2" target="_blank" rel="noopener"><strong>5.2.2</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
<li><strong>&gt;&gt;</strong> Zuul <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.10" target="_blank" rel="noopener"><strong>4.1.10</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> Micronaut Core <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.20" target="_blank" rel="noopener"><strong>5.2.20</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.19" target="_blank" rel="noopener"><strong>5.2.19</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.18" target="_blank" rel="noopener"><strong>5.2.18</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.17" target="_blank" rel="noopener"><strong>5.2.17</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.16" target="_blank" rel="noopener"><strong>5.2.16</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.16" target="_blank" rel="noopener"><strong>5.1.16</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.35" target="_blank" rel="noopener"><strong>4.10.35</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.34" target="_blank" rel="noopener"><strong>4.10.34</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.33" target="_blank" rel="noopener"><strong>4.10.33</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.32" target="_blank" rel="noopener"><strong>4.10.32</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> Helidon <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/27.0.1" target="_blank" rel="noopener"><strong>27.0.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/4.5.6" target="_blank" rel="noopener"><strong>4.5.6</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/3.2.22" target="_blank" rel="noopener"><strong>3.2.22</strong></a> [<span style="color: #800000;">github.com/helidon-io</span>]</li>
<li><strong>&gt;&gt;</strong> Micrometer <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micrometer-metrics/micrometer/releases/tag/v1.17.2" target="_blank" rel="noopener"><strong>1.17.2</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micrometer-metrics/micrometer/releases/tag/v1.16.8" target="_blank" rel="noopener"><strong>1.16.8</strong></a> [<span style="color: #800000;">github.com/micrometer-metrics</span>]</li>
<li><strong>&gt;&gt;</strong> Apache Camel <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.22.2" target="_blank" rel="noopener"><strong>4.22.2</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://spring.io/blog/2026/10/23/spring-ai-2-1-0-RC1-available-now/" target="_blank" rel="noopener"><strong>Spring AI 2.1.0-RC1 and 2.0.2 Available Now</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/10/23/spring-boot-4-0-9-available-now/" target="_blank" rel="noopener"><strong>Spring Boot 4.0.9 available now</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/10/23/spring-boot-4-1-2-available-now/" target="_blank" rel="noopener"><strong>Spring Boot 4.1.2 available now</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/10/23/spring-boot-4-2-0-RC1-available-now/" target="_blank" rel="noopener"><strong>Spring Boot 4.2.0-RC1 available now</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/10/23/spring-cloud-2025-1-4-has-been-released/" target="_blank" rel="noopener"><strong>Spring Cloud 2025.1.4 (aka Northfields) Has Been Released</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/10/23/spring-cloud-2026-0-0-RC1-has-been-released/" target="_blank" rel="noopener"><strong>Spring Cloud 2026.0.0-RC1 (aka Paddington) Has Been Released</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/10/23/spring-ldap-maintenance-releases/" target="_blank" rel="noopener"><strong>Spring LDAP 4.0.6 and 4.1.2 Available Now</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/10/23/spring-ldap-4/" target="_blank" rel="noopener"><strong>Spring LDAP 4.2.0-RC1 Available Now</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/10/23/spring-security-7/" target="_blank" rel="noopener"><strong>Spring Security 7.2.0-RC1 Available Now</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.infoq.com/news/2026/10/tornadovm-7-gpu/" target="_blank" rel="noopener"><strong>TornadoVM 7 Enables GPU-Native Workloads from Java Applications</strong></a> [<span style="color: #800000;">infoq.com</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/ontology-driven-observability-building-an-end-to-end-knowledge-graph-at-netflix-scale-421392cf5adf">&gt;&gt; Ontology-Driven Observability: Building an End-to-End Knowledge Graph at Netflix Scale</a></strong> [<span style="color: #993300;">netflixtechblog.com</span>]</p>
<p>An interesting look at the data modelling behind observability, and why giving tools a common understanding of your systems matters for incident investigation. Agentic or not.</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.christianposta.com/not-all-agent-sandboxes-are-created-equally/" target="_blank" rel="noopener"><strong>Not All Agent Sandboxes Are Created Equally</strong></a> [<span style="color: #800000;">christianposta.com</span>]</li>
<li><strong>&gt;&gt;</strong> Credential Brokering Patterns for AI Agents: <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.christianposta.com/credential-brokering-mode-3-at-scale/" target="_blank" rel="noopener"><strong>Part 2.5: Where KMS Grants Stop Scaling</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.christianposta.com/credential-brokering-patterns-for-ai-agent-egress-part3/" target="_blank" rel="noopener"><strong>Part 3: Protecting the Token Vault with OpenBao</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/software-meets-hardware.html" target="_blank" rel="noopener"><strong>When Software Meets Hardware</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://reflectoring.io/two-years-of-ai-changed-20-years-of-habits/" target="_blank" rel="noopener"><strong>Two Years of AI Changed 20 Years of Habits</strong></a> [<span style="color: #800000;">reflectoring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/10/19/an-honest-review-of-matt-pococks-skills.html" target="_blank" rel="noopener"><strong>An honest review of Matt Pocock&#8217;s Skills framework</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/beyond-api-compatibility-building-a-tool-to-measure-the-blast-radius-of-library-changes-9474f2fd4f90" target="_blank" rel="noopener"><strong>Beyond API Compatibility: Building a Tool to Measure the Blast Radius of Library Changes</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/10/15/proactive-messaging-reactions-and-compute-pooling-with-microsoft-foundry-hosted-agents.html" target="_blank" rel="noopener"><strong>Proactive messaging, reactions, and compute pooling with Microsoft Foundry hosted 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.com/making-failure-diagnosis-reliable-with-pensive-ai-6b0811a1c015" target="_blank" rel="noopener"><strong>Making Failure Diagnosis Reliable with Pensive AI</strong></a> [<span style="color: #800000;">netflixtechblog.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><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://evancz.dev/why-is-it-so-expensive-to-send-an-email"><strong>&gt;&gt; Why is it so expensive to send an email?</strong></a> [<span style="color: #800000;">evancz.dev</span>]</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-669">Java Weekly, Issue 669</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/972125879/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/972125879/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/972125879/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 href="https://feeds.feedblitz.com/_/24/972125879/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/972125879/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/972125879/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-669#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-669/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/~/972125879/0/baeldung/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-rag-query-transformation</feedburner:origLink>
		<title>Query Transformation in Spring AI Modular RAG</title>
		<link>https://feeds.feedblitz.com/~/972117848/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/972117848/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Manfred Ng]]></dc:creator>
		<pubDate>Fri, 30 Oct 2026 11:51:20 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[LLM]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=205313</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2021/08/Baeldung-Featured-Image-1024x536.png" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" /><p>Learn how to set up query transformation in Spring AI for retrieval augmented generation.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/972117848/0/baeldung">Query Transformation in Spring AI Modular RAG</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/972117848/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/972117848/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2021%2f08%2fBaeldung-Featured-Image-1024x536.png"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/972117848/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/972117848/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/972117848/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-rag-query-transformation#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-rag-query-transformation/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/2021/08/Baeldung-Featured-Image-1024x536.png" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2021/08/Baeldung-Featured-Image-1024x536.png 1024w, https://www.baeldung.com/wp-content/uploads/2021/08/Baeldung-Featured-Image-300x157.png 300w, https://www.baeldung.com/wp-content/uploads/2021/08/Baeldung-Featured-Image-768x402.png 768w, https://www.baeldung.com/wp-content/uploads/2021/08/Baeldung-Featured-Image-100x52.png 100w, https://www.baeldung.com/wp-content/uploads/2021/08/Baeldung-Featured-Image.png 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><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/retrieval-augmented-generation">Retrieval-Augmented Generation (RAG)</a> queries relevant documents from a knowledge base, such as a vector database, and then feeds them to the LLM as part of the prompt so it can generate a more accurate response.</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai">Spring AI</a> provides a modular RAG architecture that divides RAG into four stages:</p>
<ul>
<li>Pre-retrieval,</li>
<li>Retrieval,</li>
<li>Post-retrieval, and</li>
<li>Generation</li>
</ul>
<p>In this tutorial, we&#8217;ll focus on query transformation in the pre-retrieval stage that processes the user query before retrieval.</p>
<h2 id="bd-why-transform-queries" data-id="why-transform-queries">2. Why Transform Queries?</h2>
<div class="bd-anchor" id="why-transform-queries"></div>
<p>Retrieving from a vector store is basically a similarity search over the datastore embeddings, with the query embedding as the search key.</p>
<p>Therefore, the <strong>relevance of the retrieved information depends on the query wording.</strong> For example, a query may be an inefficient search key due to:</p>
<ul>
<li>Lack of clarity &#8211; a query may be ambiguous or too verbose and mention many concepts, many irrelevant to the search. In both cases, the query’s embedding is less aligned with relevant documents.</li>
<li>Language mismatch &#8211; a query in a different language from the stored documents may not produce a match even if the content is there. This gets worse if the embedding model wasn&#8217;t trained on the documents in the query&#8217;s language.</li>
<li>Missing context &#8211; A follow-up question within a long conversation, such as “And how does it support RAG?”, relies on previous messages. The user knows what “it” refers to, but the RAG system searches with the latest message only. Without the chat history, the query is ambiguous.</li>
</ul>
<p>Query transformers use an LLM to rewrite queries before retrieval in a way we expect to improve the relevance of the retrieved data.</p>
<h2 id="bd-setup" data-id="setup">3. Setup</h2>
<div class="bd-anchor" id="setup"></div>
<p>Let&#8217;s set up our project. First, we need the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-rag">Spring AI RAG dependency</a> to work with modular RAG:</p>
<pre><code class="language-xml">&lt;dependency&gt;
   &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
   &lt;artifactId&gt;spring-ai-rag&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<p>As every query transformer needs a chat model that connects to an LLM, we&#8217;ll use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-openai">Spring AI OpenAI dependency</a>:</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;/dependency&gt;</code></pre>
<p>Now, let&#8217;s configure our OpenAI API key and the chat model in our <em>application.yml</em>:</p>
<pre><code class="language-yaml">spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini
          temperature: 0.0</code></pre>
<p>A high temperature makes the model more creative, so the same user query can be transformed differently in each call. For that reason, the Spring AI documentation <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html#_query_transformation">recommends using a low temperature</a> such as 0.0 to get more deterministic results.</p>
<h2 id="bd-modular-rag" data-id="modular-rag">4. Modular RAG</h2>
<div class="bd-anchor" id="modular-rag"></div>
<p>In Spring AI, <em>RetrievalAugmentationAdvisor</em> ties the RAG stages together. Once we register it on a <em>ChatClient</em>, every prompt runs through the full pipeline, e.g.:</p>
<pre><code class="language-java">RetrievalAugmentationAdvisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
  .queryTransformers(
    RewriteQueryTransformer.builder()
      .chatClientBuilder(chatClientBuilder)
      .build(),
    TranslationQueryTransformer.builder()
      .chatClientBuilder(chatClientBuilder)
      .targetLanguage("english")
      .build(),
    CompressionQueryTransformer.builder()
      .chatClientBuilder(chatClientBuilder)
      .build())
  .documentRetriever(VectorStoreDocumentRetriever.builder()
    .vectorStore(vectorStore)
    .topK(3)
    .build())
  .build();
ChatClient chatClient = chatClientBuilder.defaultAdvisors(ragAdvisor)
  .build();</code></pre>
<p>Here, <em>vectorStore</em> is a <em>VectorStore</em> instance holding our embedded documents, e.g. backed by an <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-oracle-vector-database">Oracle vector database</a>.</p>
<p>The builder of the <em>RetrievalAugmentationAdvisor</em> exposes at least one method for each RAG stage. In our example, we configure <em>queryTransformers()</em> for the pre-retrieval stage and the <em>documentRetriever()</em> method for the retrieval stage. Let&#8217;s focus on the former.</p>
<p><strong>The <em>queryTransformers()</em> method accepts multiple query transformers. </strong>They implement the <em>QueryTransformer</em> interface and <strong>run in sequence,</strong> each one receiving the previous one&#8217;s output.<strong> Each transformer makes its own LLM call that requires a <em>ChatClient.Builder</em>. </strong></p>
<p>Spring AI provides three query transformers. To demonstrate each transformer separately, we&#8217;ll implement a test method for each in our test class:</p>
<pre><code class="language-java">@SpringBootTest
class PreRetrievalQueryTransformerLiveTest {
    private static final Logger logger = LoggerFactory.getLogger(PreRetrievalQueryTransformerLiveTest.class);
    @Autowired
    private ChatClient.Builder chatClientBuilder;
}</code></pre>
<p>Let&#8217;s check them out.</p>
<h2 id="bd-rewritequerytransformer" data-id="rewritequerytransformer">5. <em>RewriteQueryTransformer</em></h2>
<div class="bd-anchor" id="rewritequerytransformer"></div>
<p><strong><em>RewriteQueryTransformer</em> filters out irrelevant information from a query.</strong> By keeping only the relevant parts,<strong> it produces a concise text that we expect to find better results.</strong> This transformer is useful when the user query is lengthy and unclear.</p>
<p>As an example, let&#8217;s show how it handles a verbose query:</p>
<pre><code class="language-java">@Test
void whenQueryIsVerbose_thenRewrite() {
    QueryTransformer queryTransformer = RewriteQueryTransformer.builder()
      .chatClientBuilder(chatClientBuilder)
      .build();
    String userQuery = "I'm studying machine learning and honestly the whole thing is confusing. What is an LLM anyway?";
    Query transformed = queryTransformer.transform(new Query(userQuery));
    logger.info("Rewrite: [{}] -&gt; [{}]", userQuery, transformed.text());
}</code></pre>
<p>We can see the following or similar log when executing the test:</p>
<pre><code class="language-plaintext">Rewrite: [I'm studying machine learning and honestly the whole thing is confusing. What is an LLM anyway?] -&gt; [What is an LLM in machine learning?]</code></pre>
<p>The transformer identifies machine learning and LLM as important context to keep and discards the user&#8217;s introduction that is irrelevant for the search.</p>
<h2 id="bd-translationquerytransformer" data-id="translationquerytransformer">6. <em>TranslationQueryTransformer</em></h2>
<div class="bd-anchor" id="translationquerytransformer"></div>
<p>Embedding models work best for the languages they were trained on. For example, if most of the training data is in English, the model represents English texts more precisely than other written languages. Consequently, a query written in another language such as Chinese or French may align poorly with documents stored in English, even when they have the same meaning.</p>
<p><strong><em>TranslationQueryTransformer</em> solves this by translating the user&#8217;s query into the target language: the language of the documents in our datastore.</strong></p>
<p>To see this, let&#8217;s add a new test method to send a Traditional Chinese query 什麼是向量資料庫？ asking &#8220;What is a vector database?&#8221; to the transformer:</p>
<pre><code class="language-java">@Test
void whenQueryIsInTraditionalChinese_thenTranslate() {
    QueryTransformer queryTransformer = TranslationQueryTransformer.builder()
      .chatClientBuilder(chatClientBuilder)
      .targetLanguage("english")
      .build();
    String userQuery = "什麼是向量資料庫？";
    Query transformed = queryTransformer.transform(new Query(userQuery));
    logger.info("Translation: [{}] -&gt; [{}]", userQuery, transformed.text());
}</code></pre>
<p>We specify the target language explicitly using the <em>targetLanguage()</em> method. The log shows the Chinese query has been translated correctly:</p>
<pre><code class="language-plaintext">Translation: [什麼是向量資料庫？] -&gt; [What is a vector database?]</code></pre>
<p>If the query is already in the target language or in a language the LLM can&#8217;t recognize, it&#8217;s returned unchanged. For example, if we use a query in English, the output is identical:</p>
<pre><code class="language-plaintext">Translation: [What is a vector database?] -&gt; [What is a vector database?]</code></pre>
<p>Similarly, a query with made-up words of an unknown language passes through unchanged as well:</p>
<pre><code class="language-plaintext">Translation: [Gblorvak tensig quimbo darnel?] -&gt; [Gblorvak tensig quimbo darnel?]</code></pre>
<h2 id="bd-compressionquerytransformer" data-id="compressionquerytransformer">7. <em>CompressionQueryTransformer</em></h2>
<div class="bd-anchor" id="compressionquerytransformer"></div>
<p>A user query usually refers to previous messages and doesn&#8217;t contain complete information on its own. For that reason, a single query in a chat may be ambiguous and not return anything useful from the vector store.</p>
<p><strong><em>CompressionQueryTransformer</em> solves this by compressing the conversation history and using it to rewrite the query to resolve possible ambiguities.</strong></p>
<p>Unlike the previous two transformers, it requires the history of the <em>Query</em>, so must supply both the query text and the history:</p>
<pre><code class="language-java">@Test
void whenQueryIsFollowUpWithHistory_thenCompress() {
    QueryTransformer queryTransformer = CompressionQueryTransformer.builder()
      .chatClientBuilder(chatClientBuilder)
      .build();
    List&lt;Message&gt; history = List.of(
      new UserMessage("What is Spring AI?"),
      new AssistantMessage("Spring AI is an application framework that brings Spring's design principles to AI engineering."));
    
    String followUpQuery = "And how does it support RAG?";
    
    Query query = Query.builder()
      .text(followUpQuery)
      .history(history)
      .build();
    Query transformed = queryTransformer.transform(query);
    logger.info("Compression: [{}] -&gt; [{}]", followUpQuery, transformed.text());
}</code></pre>
<p>Here, we construct the conversation history explicitly as a list of messages. Executing the test should produce a log like this one:</p>
<pre><code class="language-plaintext">Compression: [And how does it support RAG?] -&gt; [How does Spring AI support Retrieval-Augmented Generation (RAG)?]</code></pre>
<p>The original query was unclear on its own because of &#8220;it.&#8221; The transformer compresses it and the conversation history into a new query where the meaning of &#8220;it&#8221; is clear.</p>
<p><strong>In a real application, a <em>ChatClient</em> doesn’t keep previous messages by default, so we need to register a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-chat-memory#apply-chat-memory-to-chat-client">chat memory</a> advisor with the RAG advisor. </strong>Otherwise, the transformer receives no history to work with.</p>
<p>However, the chat history may not always be relevant as the user may switch the topic during conversation. Let&#8217;s run the same test again with an unrelated query:</p>
<pre><code class="language-">String followUpQuery = "What's a good recipe for carbonara?";</code></pre>
<p>The transformer should recognize the topic switch and not include anything from previous messages:</p>
<pre><code class="language-plaintext">Compression: [What's a good recipe for carbonara?] -&gt; [Can you provide a good recipe for carbonara?]</code></pre>
<h2 id="bd-query-transformer-considerations" data-id="query-transformer-considerations">8. Query Transformer Considerations</h2>
<div class="bd-anchor" id="query-transformer-considerations"></div>
<p>All query transformers have these common characteristics:</p>
<ul>
<li>They run sequentially, and each receives the previous one&#8217;s output.</li>
<li>Each transformer adds an LLM call.</li>
<li>Temperature needs to be low (e.g., 0.0) to ensure more deterministic results.</li>
</ul>
<p>Because of additional LLM calls, the overall latency adds up. So, we should enable only the transformers we actually need:</p>
<ul>
<li><em>RewriteQueryTransformer</em> simplifies verbose and unclear queries to provide clarity.</li>
<li><em>TranslationQueryTransformer</em> translates queries into the language of the database.</li>
<li><em>CompressionQueryTransformer</em> compresses and merges a query with its history into a new query containing relevant context from the chat history.</li>
</ul>
<h2 id="bd-conclusion" data-id="conclusion">9. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explained query transformation in the pre-retrieval stage of Spring AI&#8217;s modular RAG architecture.</p>
<p>Three <em>QueryTransformer</em> implementations that Spring AI provide transform queries to improve retrieval performance.</p>
<p>As always, the full source code is available <a class="text-accent hover:underline underline-offset-[1px] focus-visible:outline-hidden hide-focus-ring focus-visible:shadow-focus rounded-[3px]" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-rag" target="_blank" rel="noopener noreferrer">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-rag-query-transformation">Query Transformation in Spring AI Modular RAG</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/972117848/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/972117848/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/972117848/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2021%2f08%2fBaeldung-Featured-Image-1024x536.png"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/972117848/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/972117848/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/972117848/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-rag-query-transformation#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-rag-query-transformation/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/~/972117848/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2021/08/Baeldung-Featured-Image-150x150.png</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-boot-handle-forwarded-headers</feedburner:origLink>
		<title>Handle Forwarded Headers in Spring Boot</title>
		<link>https://feeds.feedblitz.com/~/972117851/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/972117851/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Saikat Chakraborty]]></dc:creator>
		<pubDate>Fri, 30 Oct 2026 11:42:54 +0000</pubDate>
				<category><![CDATA[Java Web]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<category><![CDATA[HTTP Post]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-boot-handle-forwarded-headers</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-03-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 various strategies for handling forwarded headers in a Spring Boot application.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/972117851/0/baeldung">Handle Forwarded Headers in Spring Boot</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/972117851/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/972117851/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Boot-Featured-Image-03-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/972117851/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/972117851/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/972117851/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-boot-handle-forwarded-headers#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-boot-handle-forwarded-headers/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-Boot-Featured-Image-03-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-Boot-Featured-Image-03-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-03-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-03-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-03-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-03-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-03.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>In modern web services, we integrate backend services with essential services such as API gateways, load balancers, and reverse proxies. As a resulr, our backend service is no longer aware of the client-specific network details, such as the client&#8217;s IP address, hostname, and protocol, due to the network hop.</p>
<p><strong>We can overcome this visibility limitation by including the request details as HTTP forwarded headers.</strong></p>
<p>In this tutorial, we&#8217;ll learn various strategies for handling forwarded headers in a Spring Boot application. We&#8217;ll also implement and test these approaches with a configured reverse proxy service.</p>
<h2 id="bd-what-are-forwarded-headers" data-id="what-are-forwarded-headers">2. What are Forwarded Headers?</h2>
<div class="bd-anchor" id="what-are-forwarded-headers"></div>
<p><strong>Forwarded headers are HTTP headers that specify network-related metadata</strong>. The upstream services, while processing the request to the backend APIs, would attach the headers.</p>
<p>Traditionally, many web servers used the de facto standard <em>X-Forwarded-*</em> headers.</p>
<p>Below are the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-get-host-name-port-from-http-request#handling-reverse-proxies-x-forwarded--headers"><em>X-Forwarded-*</em></a> header details:</p>
<ul>
<li><strong><em>X-Forwarded-For </em><em>&#8211;</em></strong> To specify the originating client IP address</li>
<li><strong><em>X-Forwarded-By &#8211;</em></strong> To include the proxy&#8217;s IP address for tracing</li>
<li><strong><em>X-Forwarded-Host &#8211;</em></strong> To include the destination hostname as mentioned by the client</li>
<li><strong><em>X-Forwarded-Port &#8211;</em></strong> It specifies the destination port that the client used to connect</li>
<li><strong><em>X-Forwarded-Proto &#8211;</em></strong> To specify the protocol used, i.e http, https, ws, or wss</li>
<li><strong><em>X-Forwarded-Prefix &#8211; </em></strong>To specify any path-prefix URL, e.g /myapp, which is not included in the backend URL</li>
</ul>
<p>We can also use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://datatracker.ietf.org/doc/html/rfc7239">RFC 7239</a> standard <em>Forwarded</em> header:</p>
<pre><code class="language-bash">Forwarded: by=&lt;identifier&gt;; for=&lt;identifier&gt;; host=&lt;host&gt;; proto=&lt;http/https&gt;</code></pre>
<p><strong>The purpose of using forwarded headers can vary, such as enabling client IP-based rate limiting, auditing, sending HTTP URLs</strong> (HATEOAS), or returning an HTTP redirect response.</p>
<h2 id="bd-set-up-the-example-application" data-id="set-up-the-example-application">3. Set up the Example Application</h2>
<div class="bd-anchor" id="set-up-the-example-application"></div>
<p>Let&#8217;s implement a backend service with an endpoint. We&#8217;ll also expose the API through a reverse proxy service.</p>
<h3 id="bd-1-maven-dependencies" data-id="1-maven-dependencies">3.1. Maven Dependencies</h3>
<div class="bd-anchor" id="1-maven-dependencies"></div>
<p>We include the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter"><em>spring-boot-starter</em></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web"><em>spring-boot-starter-web</em></a> dependencies:</p>
<pre><code class="language-xml">&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt;
            &lt;version&gt;4.1.1&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;
&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</code></pre>
<p>Next, we implement a REST API service.</p>
<h3 id="bd-2-implement-the-api" data-id="2-implement-the-api">3.2. Implement the API</h3>
<div class="bd-anchor" id="2-implement-the-api"></div>
<p>We&#8217;ll use the <em>RestController</em> class. First, let&#8217;s define a test endpoint:</p>
<pre><code class="language-java">@GetMapping("/test")
public ResponseEntity&lt;Void&gt; test(HttpServletRequest request) {
    LOGGER.info("""
      scheme : {}
      Remote Host IP : {}
      Remote Port : {}
      Server Name : {}
      Server Port : {}
      Request URL : {}""", request.getScheme(), request.getRemoteHost(), request.getRemotePort(), request.getServerName(), request.getServerPort(),
        request.getRequestURL());
    return new ResponseEntity&lt;&gt;(HttpStatus.OK);
}</code></pre>
<p>Here, we&#8217;re logging some of the network metadata present in the <em>HttpServletRequest </em>object.</p>
<h3 id="bd-3-implement-the-nginx-service" data-id="3-implement-the-nginx-service">3.3. Implement the Nginx Service</h3>
<div class="bd-anchor" id="3-implement-the-nginx-service"></div>
<p>Now, let&#8217;s configure an Nginx service to serve as a reverse proxy for the backend service. We do it in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/linux/nginx-configuration-include-directive"><em>nginx.conf </em></a>file:</p>
<pre><code class="language-yaml">worker_processes auto;
events {
    worker_connections 1024;
}
http {
    server {
        listen 443 ssl;
        server_name api.example.com;
        ssl_certificate     /etc/nginx/certs/api.example.com.crt;
        ssl_certificate_key /etc/nginx/certs/api.example.com.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        location /myapp/ {
            proxy_pass http://backend-app:8081/;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }
}</code></pre>
<p>We set the hostname <em>api.example.com,</em> the location <em>/myapp/,</em> and the TLS certificate. Note the trailing <em>/ </em>in the <em>proxy_pass</em> is mandatory so that Nginx can replace the matching <em>/myapp/</em> path when forwarding the request.</p>
<p>For testing purposes, we generate the self-signed certificate using the <em>openssl</em> command:</p>
<pre><code class="language-bash">$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout nginx/certs/api.example.com.key \
  -out nginx/certs/api.example.com.crt \
  -subj "/CN=api.example.com"</code></pre>
<p>Additionally, we need to map the hostname <em>api.example.com</em> to the localhost address.</p>
<h3 id="bd-4-implement-the-docker-services" data-id="4-implement-the-docker-services">3.4. Implement the Docker Services</h3>
<div class="bd-anchor" id="4-implement-the-docker-services"></div>
<p>Let&#8217;s implement the entire setup in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/containers-vs-virtual-machines">Docker</a> environment.</p>
<p>First, we specify the application image using a <em>Dockerfile</em>:</p>
<pre><code class="language-bash">FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/spring-boot-5-0.0.1-SNAPSHOT.jar backend-app.jar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "backend-app.jar"]</code></pre>
<p>Next, we stitch the above services together in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/dockerizing-spring-boot-application#2-the-docker-compose-file"><em>docker-compose.yml</em></a> file:</p>
<pre><code class="language-bash">services:
  backend-app:
    build:
      context: ../../../../../
      dockerfile: ./src/test/resources/docker/Dockerfile
    container_name: backend-app
    expose:
      - "8081"
  nginx:
    image: nginx:1.31.3-alpine
    container_name: nginx-proxy
    volumes:
      - ../../nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ../../nginx/certs:/etc/nginx/certs:ro
    ports:
      - "443:443"
    depends_on:
      - backend-app
</code></pre>
<p>In <em>docker-compose.yml</em>, we configure the backend endpoint to run on the Docker internal port 8081. The Nginx service is accessible over TLS on port 443.</p>
<h2 id="bd-different-strategies-in-spring-boot" data-id="different-strategies-in-spring-boot">4. Different Strategies in Spring Boot</h2>
<div class="bd-anchor" id="different-strategies-in-spring-boot"></div>
<p>While the upstream services should include the forwarded headers, we still need to process headers in the application.</p>
<p>Starting with version 2.2.0, Spring Boot supports three different strategies. The options include None, Native, and Framework approaches:</p>
<ul>
<li><strong>None means the application ignores the forwarded headers.</strong></li>
<li><strong>Native lets the web server handles the forwarded headers.</strong></li>
<li><strong>With Framework, the Spring framework processes these forwarded headers</strong> before serving the request.</li>
</ul>
<p>This is configured using the <em>forward-headers-strategy </em>enum property.</p>
<p><strong>The default strategy is None</strong>, except when Spring Boot detects that the application is running on a recognized cloud platform, which then enables the native strategy.</p>
<p>Before version 2.2.0, we could either ignore the forwarded headers or enable native support using the <em>server.use-forward-headers </em>boolean configuration.</p>
<h2 id="bd-implement-the-strategies" data-id="implement-the-strategies">5. Implement the Strategies</h2>
<div class="bd-anchor" id="implement-the-strategies"></div>
<p>Let&#8217;s see these forwarded-headers strategies in action with Spring Boot support.</p>
<h3 id="bd-1-forwarded-headers-in-nginx" data-id="1-forwarded-headers-in-nginx">5.1. Forwarded Headers in Nginx</h3>
<div class="bd-anchor" id="1-forwarded-headers-in-nginx"></div>
<p>We need to include the forwarded headers in the Nginx service before implementing any strategy.</p>
<p>First, we define the <em>forwarded_for</em> and <em>forwarded_by</em> variables within the nginx<i> http </i>block:</p>
<pre><code class="language-bash">map $remote_addr $forwarded_for {
    ~: "\"[$remote_addr]:$remote_port\"";
    default "\"$remote_addr:$remote_port\"";
}
map $server_addr $forwarded_by {
    ~:  "\"[$server_addr]:$server_port\"";
    default "\"$server_addr:$server_port\"";
}</code></pre>
<p>The IP address and port are appended <span style="margin: 0px;padding: 0px">with a colon</span>, thus supporting both IPv4 and IPv6 formats.</p>
<p>Now, we set the <em>X-Forwarded-*, Forwarded,</em> and <em>Host</em> headers using the <em>proxy_set_header</em> directive in the <em>nginx.conf</em> file:</p>
<pre><code class="language-yaml">location /myapp/ {
    proxy_pass http://backend-app:8081/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_set_header X-Forwarded-Prefix /myapp;
    
    proxy_set_header Forwarded "for=$forwarded_for;by=$forwarded_by;host=$host;proto=$scheme";
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}</code></pre>
<p>With the config above, the Nginx service will automatically include the forwarded headers.</p>
<p>Instead of overwriting the <em>X-Forwarded-For</em> value, we can append the proxy&#8217;s IP to preserve the trusted upstream service&#8217;s <em>X-Forwarded-For </em>header. We can append the header using the built-in <em>proxy_add_x_forwarded_for</em> variable:</p>
<pre><code class="language-bash">proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</code></pre>
<p>We should append it only if the XFF header comes from a trusted internal service. This will ensure the client cannot spoof the forwarded headers.</p>
<h3 id="bd-2-none" data-id="2-none">5.2. None</h3>
<div class="bd-anchor" id="2-none"></div>
<p>Let&#8217;s say we want to ignore and can&#8217;t trust forwarded headers. For example, the application may be directly exposed to the client, or the proxy does not handle the forwarded headers. In such use cases, we use the None strategy.</p>
<p>First, we configure the <em>forward-headers-strategy</em> as <em>NONE</em> in the <em>application.yml</em> file:</p>
<pre><code class="language-yaml">server:
  port: 8081
  forward-headers-strategy: NONE</code></pre>
<p>For testing purposes, we run the above with the Nginx reverse proxy enabled. Then, we run the above Docker services with the <em>docker compose</em> command:</p>
<pre><code class="language-bash">$ docker compose up --build</code></pre>
<p>Finally, we execute the <em>/myapp/test</em> endpoint in the terminal:</p>
<pre><code class="language-bash">$ curl -k https://api.example.com/myapp/test</code></pre>
<p>We can confirm the request details in the Nginx log:</p>
<pre><code class="language-bash">192.168.65.1 - - [26/Aug/2026:08:13:08 +0000] "GET /myapp/test HTTP/1.1" 200 0 "-" "curl/8.7.1"
</code></pre>
<p>Also, we need to verify the <em>HttpServletRequest</em> attributes in the application log:</p>
<pre><code class="language-bash">2026-08-26T08:13:08.124Z  INFO 1 --- [backend-app] [nio-8081-exec-1] c.b.forwardheaders.TestController:
scheme : http
Remote Host IP : 172.21.0.3
Remote Port : 50754
Server Name : api.example.com
Server Port : 80
Request URL : http://api.example.com/test
</code></pre>
<p>The logs confirm that the scheme, remote IP, server port, and request URL are different from what the Nginx server received.</p>
<p>Additionally, let&#8217;s confirm the Nginx service&#8217;s IP address:</p>
<pre><code class="language-bash">$ docker exec -it nginx-proxy /bin/sh 
/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 2A:4C:7C:42:E0:87  
          inet addr:172.21.0.3  Bcast:172.21.255.255  Mask:255.255.0.0</code></pre>
<p><span data-preserver-spaces="true">The client IP address </span>is 172.21.0.3<span data-preserver-spaces="true">, which is the Nginx service&#8217;s IP address. </span><span data-preserver-spaces="true">However, in the Nginx log, we see that the client IP address is </span>192.168.65.1, which is actually the Docker&#8217;s Desktop default gateway address. As expected, the forwarded headers were not processed by the backend service.</p>
<h3 id="bd-3-native" data-id="3-native">5.3. Native</h3>
<div class="bd-anchor" id="3-native"></div>
<p>With the Native strategy, Spring Boot delegates the processing of the forwarded headers to the underlying web server. The use case can be to audit, enable IP-based rate limiting, or provide an accessible redirect URL.</p>
<p>First, let&#8217;s configure the<em> forward-headers-strategy</em> as <em>NATIVE</em> and include the Tomcat-related <em>remoteip</em> configs:</p>
<pre><code class="language-yaml">server:
  port: 8081
  forward-headers-strategy: NATIVE
  tomcat:
    remoteip:
      remote-ip-header: X-Forwarded-For
      protocol-header: X-Forwarded-Proto
      host-header: X-Forwarded-Host
      port-header: X-Forwarded-Port
      trusted-proxies: 172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}
</code></pre>
<p>In the config above, we&#8217;re setting both the <em>X-Forwarded-*</em> names and the <em>trusted-proxies</em> IP range pattern. The <em>remoteip</em> configuration is optional, as the Tomcat server implements a sensible default.</p>
<p>Then, let&#8217;s enable the access logs on the server:</p>
<pre><code class="language-yaml">server:
  tomcat:
    accesslog:
      enabled: true
      request-attributes-enabled: true
      directory: /app/logs
      prefix: access_log
      suffix: .log
      pattern: '%h %l %u %t "%r" %s %b "%{X-Forwarded-Host}i" "%{X-Forwarded-For}i" "%{X-Forwarded-Proto}i"'
      rotate: false</code></pre>
<p>Internally,<strong> the Tomcat server implements a valve called <em>RemoteIpValve</em></strong> to handle the configured forwarded headers.</p>
<p>Finally, we execute the same test endpoint to verify the application logs:</p>
<pre><code class="language-bash">2026-08-26T08:15:31.672Z  INFO 1 --- [backend-app] [nio-8081-exec-1] c.b.forwardheaders.TestController:
scheme : https
Remote Host IP : 192.168.65.1
Remote Port : 56444
Server Name : api.example.com
Server Port : 443
Request URL : https://api.example.com/test</code></pre>
<p>The logs confirm the expected scheme, remote IP, server name, and server port.</p>
<p>However, the request URL did not include the <em>/myapp </em>prefix. The reason is that the <em>RemoteIpValve</em> implementation ignores the <em>X-Forwarded-Prefix</em> header.</p>
<p>Additionally, we verify the forwarded headers in the server access log inside the Docker environment:</p>
<pre><code class="language-bash">$ docker exec -it backend-app /bin/sh
/app # tail -f ./logs/access_log.log
192.168.65.1 - - [26/Aug/2026:08:15:31 +0000] "GET /test HTTP/1.1" 200 - "api.example.com" "192.168.65.1" "https"</code></pre>
<p>The Native approach works as expected, except the request URL is missing the prefix.</p>
<p>Here&#8217;s how the overall flow for the client&#8217;s request looks:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/10/Forwarded_Header_Network_Flow-scaled.png"><img decoding="async" class="aligncenter size-large wp-image-261554" src="https://www.baeldung.com/wp-content/uploads/2026/10/Forwarded_Header_Network_Flow-1024x416.png" alt="Forwarded Headers network flow diagram" /></a>
<p>Although we processed the forwarded headers correctly, we still need to include the prefix in the request URL.</p>
<h3 id="bd-4-framework" data-id="4-framework">5.4. Framework</h3>
<div class="bd-anchor" id="4-framework"></div>
<p>In this approach, Spring Boot internally handles all forwarded headers, including the <em>X-Forwarded-Prefix </em>header. This will allow us to include the prefix in the URL.</p>
<p>We enable the <em>forward-headers-strategy</em> as <em>FRAMEWORK</em> in the <em>application.yml</em> file:</p>
<pre><code class="language-yaml">server:
  port: 8081
  forward-headers-strategy: FRAMEWORK</code></pre>
<p>Internally, <strong>Spring Boot implements this strategy using the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.spring.io/spring-framework/reference/web/webmvc/filters.html#filters-forwarded-headers"><em>ForwardedHeaderFilter</em></a></strong> class.</p>
<p>Let&#8217;s execute the <em>/myapp/test</em> endpoint and verify the application logs:</p>
<pre><code class="language-bash">2026-08-26T08:18:07.197Z  INFO 1 --- [backend-app] [nio-8081-exec-1] c.b.forwardheaders.TestController:
scheme : https
Remote Host IP : 192.168.65.1
Remote Port : 52414
Server Name : api.example.com
Server Port : 443
Request URL : https://api.example.com/myapp/test</code></pre>
<p>We confirm the scheme, remote IP/port, server name/port, and request URL as expected. Also, the <em>ForwardedHeaderFilter</em> correctly extracts the remote IP and port based on the configured <em>Forwarded</em> header. By default, the remote IP and port are updated using the <em>Forwarded: for </em>header, if present. Otherwise, only the remote IP gets updated using the <em>X-Forwarded-For</em> header.</p>
<p>The above behavior was implemented in Spring Boot 2.4.0. Before Spring Boot 2.4.0/Spring Framework 5.3.0, the remote IP and port were left untouched.</p>
<p><strong>We shouldn&#8217;t enable both the Framework and Native strategies</strong>, as it will cause duplicate processing.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we learned about the forwarded headers and why we need them.</p>
<p>We also demonstrated and tested three strategies provided by Spring Boot: None, Native, and Framework strategies with the <em>forward-header-strategy</em> configuration.</p>
<p>As always, the article code can be found <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-5">over the GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-handle-forwarded-headers">Handle Forwarded Headers in Spring Boot</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/972117851/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/972117851/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/972117851/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Boot-Featured-Image-03-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/972117851/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/972117851/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/972117851/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-boot-handle-forwarded-headers#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-boot-handle-forwarded-headers/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/~/972117851/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-03-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-safeguarding-llm-output</feedburner:origLink>
		<title>Safeguarding LLM Output in Spring AI</title>
		<link>https://feeds.feedblitz.com/~/971759387/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/971759387/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Yadier Betancourt]]></dc:creator>
		<pubDate>Sun, 18 Oct 2026 17:55:03 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=205222</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/05/admin-ajax-2.png" class="webfeedsFeaturedVisual wp-post-image" alt="start here featured" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn to create a custom advisor that inspects the model's response before Spring AI ChatClient hands it back to the user.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/971759387/0/baeldung">Safeguarding LLM Output in Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971759387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971759387/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f05%2fadmin-ajax-2.png"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/971759387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971759387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971759387/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-safeguarding-llm-output#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-safeguarding-llm-output/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/05/admin-ajax-2.png" class="webfeedsFeaturedVisual wp-post-image" alt="start here featured" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/05/admin-ajax-2.png 600w, https://www.baeldung.com/wp-content/uploads/2024/05/admin-ajax-2-300x157.png 300w, https://www.baeldung.com/wp-content/uploads/2024/05/admin-ajax-2-100x52.png 100w" 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 expose a large language model through our own API, our first instinct is to filter the prompts that users send. Spring AI ships a <em>SafeGuardAdvisor</em> for exactly that job. It blocks any prompt containing a forbidden term before the prompt reaches the model.</p>
<p>That covers only half of the problem. <strong>A perfectly innocent-looking prompt can still produce a response that we&#8217;d rather not return.</strong> The model answers from its own knowledge. The user never has to name the thing we&#8217;re trying to hide.</p>
<p>In this tutorial, we&#8217;ll close that gap with a custom <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-advisors">advisor</a> that inspects the model&#8217;s response before <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-chatclient"><em>ChatClient</em></a> hands it back. We&#8217;ll set up the input guard first, find the hole in it, then stack an output guard on top.</p>
<h2 id="bd-setting-up-the-project" data-id="setting-up-the-project">2. Setting Up the Project</h2>
<div class="bd-anchor" id="setting-up-the-project"></div>
<p>A chat starter is the only dependency we need. Let&#8217;s add the Google GenAI one:</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-google-genai&lt;/artifactId&gt;
    &lt;version&gt;1.1.2&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>The latest version of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-google-genai">spring-ai-starter-model-google-genai</a> is available in the Maven Repository. Advisors are model-agnostic, so any other chat starter works here just as well.</p>
<p>Next, we point the starter at our Gemini API key and pick a model:</p>
<pre><code class="language-yaml">spring:
  ai:
    google:
      genai:
        api-key: ${GEMINI_API_KEY}
        chat:
          options:
            model: gemini-3.5-flash
</code></pre>
<p>Throughout the tutorial, we&#8217;ll build a Java coding assistant with one rule: it must never recommend the legacy date API over <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-8-date-time-intro"><em>java.time</em></a>.</p>
<h2 id="bd-blocking-sensitive-prompts" data-id="blocking-sensitive-prompts">3. Blocking Sensitive Prompts</h2>
<div class="bd-anchor" id="blocking-sensitive-prompts"></div>
<p><em>SafeGuardAdvisor</em> is Spring AI&#8217;s built-in answer to unwanted input. <strong>It scans the prompt for a list of sensitive words. When it finds one, it short-circuits the advisor chain.</strong> The model is never called, and the caller gets a canned response instead.</p>
<p>Let&#8217;s register one as a default advisor on our <em>ChatClient</em>, next to a system prompt that sets the assistant&#8217;s role:</p>
<pre><code class="language-java">@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
    SafeGuardAdvisor inputGuard = SafeGuardAdvisor.builder()
      .sensitiveWords(List.of("SimpleDateFormat", "java.util.Date"))
      .failureResponse("Use java.time instead.")
      .build();
    return builder.defaultSystem(SYSTEM_PROMPT)
      .defaultAdvisors(inputGuard)
      .build();
}
</code></pre>
<p>The advisor compares each word against the prompt with a plain substring check, so the match is case-sensitive and exact. It also scans the whole prompt, system message included. That has a catch. If our system prompt spelled out <em>SimpleDateFormat</em>, the guard would reject every request.</p>
<p>The guard now stops any question that names the legacy API before it reaches the model:</p>
<pre><code class="language-java">private static final String FAILURE_RESPONSE = "Use java.time instead.";
@Test
void givenLegacyApiPrompt_whenCallingTheModel_thenItIsBlocked() {
    String answer = chatClient.prompt()
      .user("How do I use SimpleDateFormat?")
      .call()
      .content();
    assertThat(answer).isEqualTo(FAILURE_RESPONSE);
}
</code></pre>
<p><strong>The model is never invoked at all</strong>, which makes this the cheapest guardrail we can put in place.</p>
<h2 id="bd-why-input-filtering-isnt-enough" data-id="why-input-filtering-isnt-enough">4. Why Input Filtering Isn&#8217;t Enough</h2>
<div class="bd-anchor" id="why-input-filtering-isnt-enough"></div>
<p><em>SafeGuardAdvisor</em> only ever sees the prompt. A developer can ask about a class without ever typing its name. Asking &#8220;<em>How did we parse date strings in Java 7?</em>&#8221; matches nothing on our list, so it reaches the model. Since <em>java.time</em> didn&#8217;t exist before Java 8, it has only one honest answer:</p>
<pre><code>In Java 7, date strings were parsed using the java.text.SimpleDateFormat
class by defining a format pattern to convert the string into a
java.util.Date object.
</code></pre>
<p><strong>The guard held, and the legacy API came back anyway.</strong> Input filtering only matches what the user typed, and the model is under no obligation to stay inside that vocabulary. To catch this, we have to look at what comes back.</p>
<h2 id="bd-creating-the-output-advisor" data-id="creating-the-output-advisor">5. Creating the Output Advisor</h2>
<div class="bd-anchor" id="creating-the-output-advisor"></div>
<p>Spring AI has no built-in advisor that filters response content, so we&#8217;ll write our own. It takes the same three settings as <em>SafeGuardAdvisor</em>, and we&#8217;ll build it in two steps: the class and its builder, then the filtering logic.</p>
<h3 id="bd-1-skeleton-and-builder" data-id="1-skeleton-and-builder">5.1. Skeleton and Builder</h3>
<div class="bd-anchor" id="1-skeleton-and-builder"></div>
<p><strong>An advisor that inspects responses only needs the <em>CallAdvisor</em> interface</strong>, which Spring AI uses for non-streaming calls. Our class holds the three values the builder configures, and exposes two of them through the <em>Advisor</em> contract:</p>
<pre><code class="language-java">public class OutputSafeGuardAdvisor implements CallAdvisor {
    private List&lt;String&gt; sensitiveWords;
    private String failureResponse;
    private int order;
    private OutputSafeGuardAdvisor(
      List&lt;String&gt; sensitiveWords, String failureResponse, int order) {
        this.sensitiveWords = sensitiveWords;
        this.failureResponse = failureResponse;
        this.order = order;
    }
    public static Builder builder() {
        return new Builder();
    }
    @Override
    public String getName() {
        return getClass().getSimpleName();
    }
    @Override
    public int getOrder() {
        return order;
    }
}
</code></pre>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/creational-design-patterns">builder</a> mirrors the one on <em>SafeGuardAdvisor</em>, so both advisors read the same way at the call site:</p>
<pre><code class="language-java">public static class Builder {
    private List&lt;String&gt; sensitiveWords = List.of();
    private String failureResponse
      = "Response contained forbidden content.";
    private int order = Ordered.LOWEST_PRECEDENCE - 1;
    public Builder sensitiveWords(String... sensitiveWords) {
        this.sensitiveWords = Arrays.asList(sensitiveWords);
        return this;
    }
    public Builder sensitiveWords(List&lt;String&gt; sensitiveWords) {
        this.sensitiveWords = sensitiveWords;
        return this;
    }
    public Builder failureResponse(String failureResponse) {
        this.failureResponse = failureResponse;
        return this;
    }
    public Builder order(int order) {
        this.order = order;
        return this;
    }
    public OutputSafeGuardAdvisor build() {
        return new OutputSafeGuardAdvisor(
          sensitiveWords, failureResponse, order);
    }
}
</code></pre>
<p>The varargs overload of <em>sensitiveWords()</em> is one <em>SafeGuardAdvisor</em> doesn&#8217;t offer. The default order deserves a closer look. Spring AI terminates the chain with an internal <em>ChatModelCallAdvisor</em> whose order is exactly <em>Ordered.LOWEST_PRECEDENCE</em>. <strong>An advisor that shares that value can be sorted behind the terminal one.</strong> The terminal advisor never delegates any further. <strong>Ours would silently never run.</strong></p>
<p>Defaulting to <em>Ordered.LOWEST_PRECEDENCE &#8211; 1</em> puts us last among the advisors that do execute. Spring AI&#8217;s own <em>StructuredOutputValidationAdvisor</em> leaves the same gap, defaulting to <em>Ordered.LOWEST_PRECEDENCE &#8211; 2000</em>.</p>
<h3 id="bd-2-implementing-advisecall" data-id="2-implementing-advisecall">5.2. Implementing <em>adviseCall()</em></h3>
<div class="bd-anchor" id="2-implementing-advisecall"></div>
<p><strong>Unlike an input guard, an output advisor starts by letting the call through.</strong> We invoke the rest of the chain first, and only then inspect what came back:</p>
<pre><code class="language-java">@Override
public ChatClientResponse adviseCall(
  ChatClientRequest request, CallAdvisorChain chain) {
    ChatClientResponse response = chain.nextCall(request);
    ChatResponse chatResponse = response.chatResponse();
    if (chatResponse == null || chatResponse.getResults().isEmpty()) {
        return response;
    }
    List&lt;Generation&gt; results = chatResponse.getResults();
    List&lt;Generation&gt; filteredResults = results.stream()
      .map(this::filterGeneration)
      .toList();
    if (filteredResults.equals(results)) {
        return response;
    }
    ChatResponse filtered = new ChatResponse(
      filteredResults, chatResponse.getMetadata());
    return response.mutate()
      .chatResponse(filtered)
      .build();
}
</code></pre>
<p>A response with no <em>ChatResponse</em> or generations gives us nothing to inspect, so we hand it straight back. Otherwise, we map over every <em>Generation</em> and compare the filtered list against the original. <em>Generation</em> implements <em>equals()</em>, so that&#8217;s a value comparison, not an identity check, and when nothing changed we return the original response.</p>
<p>The replacement swaps the message but keeps the generation&#8217;s metadata, so the finish reason survives:</p>
<pre><code class="language-java">private Generation filterGeneration(Generation generation) {
    if (!containsSensitiveWord(generation.getOutput().getText())) {
        return generation;
    }
    return new Generation(
      new AssistantMessage(failureResponse), generation.getMetadata());
}
private boolean containsSensitiveWord(String text) {
    if (!StringUtils.hasText(text)) {
        return false;
    }
    return sensitiveWords.stream()
      .anyMatch(text::contains);
}
</code></pre>
<h2 id="bd-registering-the-advisor" data-id="registering-the-advisor">6. Registering the Advisor</h2>
<div class="bd-anchor" id="registering-the-advisor"></div>
<p>Registering the output guard means adding it to the same <em>defaultAdvisors()</em> call:</p>
<pre><code class="language-java">@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
    SafeGuardAdvisor inputGuard = SafeGuardAdvisor.builder()
      .sensitiveWords(List.of("SimpleDateFormat", "java.util.Date"))
      .failureResponse("Use java.time instead.")
      .build();
    OutputSafeGuardAdvisor outputGuard = OutputSafeGuardAdvisor.builder()
      .sensitiveWords("DateFormat", "java.util.Date",
        "Calendar", "Timestamp")
      .failureResponse("Use java.time instead.")
      .build();
    return builder.defaultSystem(SYSTEM_PROMPT)
      .defaultAdvisors(inputGuard, outputGuard)
      .build();
}
</code></pre>
<p><strong>The two guards deliberately watch different word lists.</strong> The output guard watches <em>DateFormat</em> rather than <em>SimpleDateFormat</em>, which also catches the parent class, and adds <em>Calendar</em> and <em>Timestamp</em>, the neighboring legacy types a model reaches for. Adding those to the input guard would be counterproductive: a developer asking about <em>Calendar</em> isn&#8217;t the problem, an answer that recommends it is.</p>
<p>We don&#8217;t set an order: <em>SafeGuardAdvisor</em> defaults to 0 and runs first, while our default puts the output guard last.</p>
<h2 id="bd-testing-the-guardrail" data-id="testing-the-guardrail">7. Testing the Guardrail</h2>
<div class="bd-anchor" id="testing-the-guardrail"></div>
<p>The advisor is a plain class, so we can exercise it without a model. Let&#8217;s start with the fixture:</p>
<pre><code class="language-java">private static final String FAILURE_RESPONSE = "Use java.time instead.";
private static final String LEAKED_ANSWER = "Java 7 used SimpleDateFormat.";
private static final String CLEAN_ANSWER = "LocalDate.now() returns today.";
private OutputSafeGuardAdvisor advisor = OutputSafeGuardAdvisor.builder()
  .sensitiveWords("SimpleDateFormat", "java.util.Date")
  .failureResponse(FAILURE_RESPONSE)
  .build();
</code></pre>
<p>Now we stub the chain with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/mockito-series">Mockito</a> and feed it a generation that leaks:</p>
<pre><code class="language-java">@Test
void givenAnswerNamingLegacyApi_whenAdvisingCall_thenAnswerIsReplaced() {
    CallAdvisorChain chain = chainReturning(generation(LEAKED_ANSWER));
    ChatClientResponse response = advisor.adviseCall(request(), chain);
    assertThat(textOf(response)).isEqualTo(FAILURE_RESPONSE);
}
</code></pre>
<p>A clean answer takes the early return, so we get the very same instance back:</p>
<pre><code class="language-java">@Test
void givenCleanAnswer_whenAdvisingCall_thenItIsReturnedUntouched() {
    ChatClientResponse original = responseOf(generation(CLEAN_ANSWER));
    CallAdvisorChain chain = chainReturning(original);
    ChatClientResponse response = advisor.adviseCall(request(), chain);
    assertThat(response).isSameAs(original);
}
</code></pre>
<p>End to end, the question that defeated the input guard now comes back filtered:</p>
<pre><code class="language-java">@Test
void givenJava7Question_whenCallingTheModel_thenAnswerIsBlocked() {
    String answer = chatClient.prompt()
      .user("How did we parse date strings in Java 7?")
      .call()
      .content();
    assertThat(answer).isEqualTo(FAILURE_RESPONSE);
}
</code></pre>
<h2 id="bd-limitations" data-id="limitations">8. Limitations</h2>
<div class="bd-anchor" id="limitations"></div>
<p><strong>Substring matching is blunt in both directions.</strong> It&#8217;s case-sensitive, so a stray capitalization slips through. It also has no notion of word boundaries. That&#8217;s why our list spells out <em>java.util.Date</em> rather than a bare <em>Date</em>. The shorter term would also reject a good answer built on <em>LocalDate</em>. Paraphrases defeat it outright.</p>
<p>It&#8217;s also a call-only guard. <em>OutputSafeGuardAdvisor</em> implements <em>CallAdvisor</em> but not <em>StreamAdvisor</em>, so a streamed response bypasses it. Filtering a stream is genuinely harder, because a sensitive word can straddle two chunks and the earlier ones have already reached the client.</p>
<p>For stronger guarantees, a moderation model is a better tool than a word list.</p>
<h2 id="bd-conclusion" data-id="conclusion">9. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we built a custom Spring AI advisor that inspects the model&#8217;s response and replaces it when it contains something we&#8217;d rather not return. Pairing it with <em>SafeGuardAdvisor</em> covers both directions: one guard rejects the prompt, the other rejects the answer. Neither is complete on its own, but together they close the obvious gaps with very little code.</p>
<p>As always, the full source 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><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-safeguarding-llm-output">Safeguarding LLM Output in Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/971759387/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971759387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971759387/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f05%2fadmin-ajax-2.png"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/971759387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971759387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971759387/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-safeguarding-llm-output#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-safeguarding-llm-output/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/~/971759387/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/05/admin-ajax-2-150x150.png</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-668</feedburner:origLink>
		<title>Java Weekly, Issue 668</title>
		<link>https://feeds.feedblitz.com/~/971746664/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/971746664/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sun, 18 Oct 2026 07:13:23 +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=205228</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>JDK 28 is shaping up nicely. And an interesting harness experiment.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/971746664/0/baeldung">Java Weekly, Issue 668</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971746664/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971746664/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 href="https://feeds.feedblitz.com/_/24/971746664/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971746664/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971746664/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-668#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-668/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/are-harnesses-worth-the-cost.html">&gt;&gt; Are Harnesses Worth the Cost?</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>
<p>Coding agents <strong>with and without a behavioral harness</strong>. An interesting experiment, where the harness makes a smaller model more reliable, but adds cost without improving a stronger model&#8217;s results.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/10/11/devoxxbelgium-runtime-year-keynote/">&gt;&gt; Java &#8211; The Year of the Runtime!</a></strong> [<span style="color: #993300;">inside.java</span>]</p>
<p>Why Java&#8217;s <strong>runtime matters</strong> as AI agents take on more coding.</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://openjdk.org/projects/jdk/28/" target="_blank" rel="noopener"><strong>JDK 28</strong></a> [<span style="color: #800000;">openjdk.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/embedding-strategies-job-search/" target="_blank" rel="noopener"><strong>One Embedding per Job, or Two? Measuring Semantic Search in a Spring Boot Job Portal</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/your-agents-dont-need-an-llm-to-negotiate/" target="_blank" rel="noopener"><strong>Your agents don&#8217;t need an LLM to negotiate</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/testing-jakarta-ee-three-layers-three-tools/" target="_blank" rel="noopener"><strong>Testing Jakarta EE: Three Layers, Three Tools</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/why-another-java-server/" target="_blank" rel="noopener"><strong>We Didn&#8217;t Want to Build Another Java Server</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/introducing-jenesis/" target="_blank" rel="noopener"><strong>Introducing Jenesis: You Already Wrote the Build Script</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://blogs.oracle.com/java/new-lower-smb-pricing-and-easier-online-purchasing-of-java-se-universal-subscription" target="_blank" rel="noopener"><strong>New Lower SMB Pricing and Easier Online Purchasing of Java SE Universal Subscription</strong></a> [<span style="color: #800000;">oracle.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/bypass-bot-protection/" target="_blank" rel="noopener"><strong>Scraping websites and bot-protection</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://blog.code-cop.org/2026/10/improving-harness.html" target="_blank" rel="noopener"><strong>Improving the Harness</strong></a> [<span style="color: #800000;">code-cop.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/keep-wordpress-if-youre-looking-for-trouble/" target="_blank" rel="noopener"><strong>Keep WordPress If You&#8217;re Looking for Trouble&#8230; Or Read This!</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://martinfowler.com/articles/practitioner-voice.html#TheCraftOfPractitionerVoice" target="_blank" rel="noopener"><strong>The Craft of Practitioner Voice</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://netflixtechblog.com/reliable-gpu-compute-for-large-training-inference-jobs-d62b03a96d7a" target="_blank" rel="noopener"><strong>Reliable GPU compute for large training &amp; inference jobs</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/10/15/java-fit-ai-world/" target="_blank" rel="noopener"><strong>How Does Java Fit Into the World of AI—and How Can It Become an Even Better Fit?</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/10/14/devoxxbelgium-everyday-json/" target="_blank" rel="noopener"><strong>Everyday JSON in Java</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/10/12/devoxxbelgium-structured-concurrency/" target="_blank" rel="noopener"><strong>Structured Concurrency Looms</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://blog.jetbrains.com/kotlin/2026/10/kotlinconf-recap-lessons-from-verifones-architect-on-kotlin-in-mission-critical-payments/" target="_blank" rel="noopener"><strong>KotlinConf Recap: Lessons from Verifone’s Architect on Kotlin in Mission-Critical Payments</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-insights-263-quarkus-build-reproducibility/" target="_blank" rel="noopener"><strong>Quarkus Insights # 263: Everything You Didn&#8217;t Want to Know About Quarkus Build Reproducibility</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/10/08/a-bootiful-podcast-kohsuke-kawaguchi/" target="_blank" rel="noopener"><strong>A Bootiful Podcast: legendary Java developer Kohsuke Kawaguchi</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://in.relation.to/2026/10/13/hibernate-validator-9-2-0-Beta1/" target="_blank" rel="noopener"><strong>Hibernate Validator 9.2.0.Beta1 is out</strong></a> [<span style="color: #800000;">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/10/08/java-vscode-extension-update/" target="_blank" rel="noopener"><strong>Oracle Java Platform Extension for Visual Studio Code &#8211; Version 27.0.0 Is Now Available</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> Micronaut Core <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.31" target="_blank" rel="noopener"><strong>4.10.31</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v3.10.15" target="_blank" rel="noopener"><strong>3.10.15</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> Apache Camel <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.23.0" target="_blank" rel="noopener"><strong>4.23.0</strong></a> [<span style="color: #800000;">github.com/apache</span>]</li>
<li><strong>&gt;&gt;</strong> Payara <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/payara/Payara/releases/tag/payara-server-7.2026.10" target="_blank" rel="noopener"><strong>7.2026.10</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/whats-new-in-the-october-2026-azul-payara-release/" target="_blank" rel="noopener"><strong>What&#8217;s New In The October 2026 Azul Payara Release?</strong></a> [<span style="color: #800000;">github.com/payara</span>] [<span style="color: #800000;">foojay.io</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><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://refactoringenglish.com/excerpts/write-an-effective-design-doc/"><strong>&gt;&gt; How to Write an Effective Software Design Document </strong></a>[<span style="color: #993300;">refactoringenglish.com</span>]</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-668">Java Weekly, Issue 668</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/971746664/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971746664/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971746664/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 href="https://feeds.feedblitz.com/_/24/971746664/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971746664/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971746664/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-668#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-668/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/~/971746664/0/baeldung/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-alibaba-graph-based-workflows</feedburner:origLink>
		<title>Building Graph-Based Workflows With Spring AI Alibaba</title>
		<link>https://feeds.feedblitz.com/~/971204387/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/971204387/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Hardik Singh Behl]]></dc:creator>
		<pubDate>Sat, 10 Oct 2026 01:04:27 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=205165</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use Spring AI Alibaba to implement and maintain your prompt orchestration logic.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/971204387/0/baeldung">Building Graph-Based Workflows With Spring AI Alibaba</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971204387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971204387/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 href="https://feeds.feedblitz.com/_/24/971204387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971204387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971204387/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-alibaba-graph-based-workflows#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-alibaba-graph-based-workflows/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-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>When building AI agents, a single prompt to an LLM is usually not enough. Solving complex tasks requires multiple steps, where the output of one step decides what happens next. However, it&#8217;s difficult to manually implement and maintain this orchestration logic.</p>
<p>In this tutorial, we&#8217;ll solve this problem using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://java2ai.com/en/">Spring AI Alibaba</a>. We&#8217;ll use it to model an agentic workflow as a graph of steps, which decides its own path at runtime.</p>
<h2 id="bd-graph-based-workflows-101" data-id="graph-based-workflows-101">2. Graph-Based Workflows 101</h2>
<div class="bd-anchor" id="graph-based-workflows-101"></div>
<p>A graph-based workflow breaks a complex task into smaller units of work and connects them to form a graph. <strong>Instead of hardcoding the order of execution, we just describe the possible paths</strong>. The workflow follows the appropriate path based on the data it has.</p>
<p>Such workflows consist of three main concepts:</p>
<ul>
<li><strong>State</strong>: the shared data that flows through the workflow.</li>
<li><strong>Nodes</strong>: the individual units of work in our graph. They typically read the state, perform business logic, and update the state at the end.</li>
<li><strong>Edges</strong>: the connections between the nodes. They control the order of execution by deciding which node to move to.</li>
</ul>
<p>To see these concepts in action, <strong>we&#8217;ll build an excuse escalation workflow</strong>. First, an employee node invents an excuse for a given situation at work. Then, a manager node rates how believable the excuse is.</p>
<p>Next, an edge checks the manager&#8217;s rating. If the manager believes the excuse, the workflow ends. Otherwise, the flow loops back to the employee node, which refines the original excuse.</p>
<h2 id="bd-configuring-an-llm" data-id="configuring-an-llm">3. Configuring an LLM</h2>
<div class="bd-anchor" id="configuring-an-llm"></div>
<p>Now, let&#8217;s set up our project. We&#8217;ll start by adding the necessary dependencies to our <em>pom.xml</em> file:</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;2.0.1&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alibaba.cloud.ai&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-alibaba-graph-core&lt;/artifactId&gt;
    &lt;version&gt;1.1.2.3&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Here, we first add <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-openai">Spring AI&#8217;s OpenAI starter dependency</a>, which we&#8217;ll use to interact with an LLM. Next, we add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba-graph-core">Spring AI Alibaba graph core dependency</a>. This provides the classes we need to define our graph-based workflow.</p>
<p>To avoid version conflicts and compatibility issues between these dependencies, let&#8217;s also include the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-bom">Spring AI Bill of Materials (BOM)</a>:</p>
<pre><code class="language-xml">&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
            &lt;artifactId&gt;spring-ai-bom&lt;/artifactId&gt;
            &lt;version&gt;2.0.1&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;</code></pre>
<p>Next, let’s configure our <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://platform.openai.com/api-keys">OpenAI API key</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://developers.openai.com/api/docs/models">chat model</a> in the <em>application.yaml</em> file:</p>
<pre><code class="language-yaml">spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        model: gpt-5.6-luna</code></pre>
<p>Here, we specify OpenAI’s <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://developers.openai.com/api/docs/models/gpt-5.6-luna">GPT-5.6 Luna</a> model using the <em>gpt-5.6-luna</em> model ID. Alternatively, we can use a different chat model, as the specific AI model or provider is irrelevant for this demonstration.</p>
<p><strong>With these two properties set, Spring AI automatically creates a <em>ChatClient.Builder</em> bean</strong>. We&#8217;ll use it to interact with the specified model in our nodes.</p>
<h2 id="bd-defining-state-keys" data-id="defining-state-keys">4. Defining State Keys</h2>
<div class="bd-anchor" id="defining-state-keys"></div>
<p><strong>The state of our workflow is a collection of key-value pairs</strong>. Before our nodes can read or write them, we need to declare these keys along with a strategy. These strategies control how the graph updates a value.</p>
<p>Let&#8217;s define these keys in a <em>KeyStrategyFactory</em> bean:</p>
<pre><code class="language-java">@Bean
KeyStrategyFactory excuseKeyStrategyFactory() {
    return () -&gt; {
        Map&lt;String, KeyStrategy&gt; strategies = new HashMap&lt;&gt;();
        strategies.put("situation", new ReplaceStrategy());
        strategies.put("excuse", new ReplaceStrategy());
        strategies.put("managerReplies", new AppendStrategy());
        strategies.put("believability", new ReplaceStrategy());
        strategies.put("attempts", new ReplaceStrategy());
        return strategies;
    };
}</code></pre>
<p>Here, the <em>situation</em> key holds the initial input of our workflow. The remaining keys represent the values our nodes will produce.</p>
<p>For most of them, we use the <em>ReplaceStrategy</em>, which overwrites the existing value with the new one. However, we use the <em>AppendStrategy</em> for the <em>managerReplies</em> key, which adds every new value to a list.</p>
<h2 id="bd-implementing-nodes" data-id="implementing-nodes">5. Implementing Nodes</h2>
<div class="bd-anchor" id="implementing-nodes"></div>
<p>With our state keys defined, let&#8217;s build the nodes of our graph. <strong>To create one, we implement the <em>NodeAction</em> interface and its <em>apply()</em> method</strong>. This method receives the current state in the form of an <em>OverAllState</em> instance. Then, after executing the business logic, we return a <em>Map</em> containing the updates we want to make to the state.</p>
<h3 id="bd-1-inventing-an-excuse" data-id="1-inventing-an-excuse">5.1. Inventing an Excuse</h3>
<div class="bd-anchor" id="1-inventing-an-excuse"></div>
<p>First, let&#8217;s create the node that plays the role of our employee:</p>
<pre><code class="language-java">@Component
class InventExcuseNode implements NodeAction {
    private static final String PROMPT_TEMPLATE = """
      You're an employee explaining why you missed something at work.
      You're a bad liar, but a creative one.
      You never take accountability or admit to lying.
      Situation: {situation}
      The excuse you already gave: {previousExcuse}
      How your manager responded: {managerReplies}
      Give a new excuse in at most two sentences. If you already gave one, do not
      abandon it. Keep the original story and add a further complication.
      Respond with only the excuse.
      """;
    private final ChatClient chatClient;
    InventExcuseNode(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }
}</code></pre>
<p>In our new class, we define the LLM instructions in a prompt template. Additionally, we inject the <em>ChatClient.Builder</em> bean that Spring AI auto-configures and use it to build a <em>ChatClient</em> instance. Next, let&#8217;s implement the <em>apply()</em> method:</p>
<pre><code class="language-java">@Override
public Map&lt;String, Object&gt; apply(OverAllState state) {
    String situation = state.value("situation", String.class)
      .orElseThrow(IllegalStateException::new);
    String previousExcuse = state.value("excuse", "none yet");
    List&lt;String&gt; managerReplies = state.value("managerReplies", List.of());
    Integer attempts = state.value("attempts", 0);
    String excuse = chatClient
      .prompt()
      .user(user -&gt; user.text(PROMPT_TEMPLATE)
        .param("situation", situation)
        .param("previousExcuse", previousExcuse)
        .param("managerReplies", managerReplies.isEmpty()
          ? "none yet"
          : String.join("\n", managerReplies)))
      .call()
      .content();
    return Map.of(
      "excuse", excuse,
      "attempts", attempts + 1
    );
}</code></pre>
<p>Here, <strong>we read the current state using the <em>value()</em> method</strong>. We define default values for these keys, except for the <em>situation</em> key. Instead, we treat it as a mandatory input to start the workflow.</p>
<p>Then, we fill these values into the prompt template, call the LLM, and get the <em>excuse</em> using the <em>content()</em> method. Finally, we update the state with the new excuse along with the incremented attempt count.</p>
<h3 id="bd-2-reacting-as-the-manager" data-id="2-reacting-as-the-manager">5.2. Reacting as the Manager</h3>
<div class="bd-anchor" id="2-reacting-as-the-manager"></div>
<p>Next, we need a manager node that rates the employee&#8217;s excuse. Let&#8217;s create a new <em>ManagerReactsNode</em> class and implement the <em>apply()</em> method:</p>
<pre><code class="language-java">private static final String PROMPT_TEMPLATE = """
  You are a tired engineering manager listening to an employee explain themselves.
  Situation: {situation}
  Their excuse: {excuse}
  Reply in one sentence and rate how believable the excuse is, from 0.0 to 1.0.
  """;
@Override
public Map&lt;String, Object&gt; apply(OverAllState state) {
    String situation = state.value("situation", String.class)
      .orElseThrow(IllegalStateException::new);
    String excuse = state.value("excuse", String.class)
      .orElseThrow(IllegalStateException::new);
    ManagerReaction managerReaction = chatClient
      .prompt()
      .user(user -&gt; user.text(PROMPT_TEMPLATE)
        .param("situation", situation)
        .param("excuse", excuse))
      .call()
      .entity(ManagerReaction.class);
    return Map.of(
      "managerReplies", managerReaction.reply(),
      "believability", managerReaction.believability()
    );
}
record ManagerReaction(
  String reply,
  Double believability
) {}</code></pre>
<p>Similar to our previous node, we read the current state, populate the prompt template, and invoke the LLM. However, <strong>instead of calling the <em>content()</em> method, we call the <em>entity()</em> method to get a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-artificial-intelligence-structure-output">structured output</a></strong>. Finally, we return the <em>reply</em> and the <em>believability</em> score as state updates.</p>
<h2 id="bd-creating-a-conditional-edge" data-id="creating-a-conditional-edge">6. Creating a Conditional Edge</h2>
<div class="bd-anchor" id="creating-a-conditional-edge"></div>
<p>Now, we need a way to connect the nodes we&#8217;ve implemented. Additionally, we need to write logic to decide whether the workflow should end or the employee should try again. To do this, <strong>we&#8217;ll create a conditional edge by implementing the <em>EdgeAction</em> interface</strong>:</p>
<pre><code class="language-java">@Component
class ExcuseDispatcher implements EdgeAction {
    private static final double BELIEVABILITY_THRESHOLD = 0.8;
    private static final int MAX_ATTEMPTS = 3;
    @Override
    public String apply(OverAllState state) {
        Double believability = state.value("believability", 0.0);
        Integer attempts = state.value("attempts", 0);
        if (isBelieved(believability) || attempts &gt;= MAX_ATTEMPTS) {
            return "stop";
        }
        return "escalate";
    }
    static boolean isBelieved(Double believability) {
        return believability &gt;= BELIEVABILITY_THRESHOLD;
    }
}</code></pre>
<p>Here, we read the <em>believability</em> and <em>attempts</em> keys from the state. If the manager&#8217;s rating reaches our configured threshold, we return the <em>stop</em> label. Otherwise, we return the <em>escalate</em> label to give the employee another chance.</p>
<p>In the first condition, <strong>we also check if the employee has exhausted their maximum attempts</strong>. This is important to avoid an infinite loop in our workflow.</p>
<h2 id="bd-assembling-the-graph" data-id="assembling-the-graph">7. Assembling the Graph</h2>
<div class="bd-anchor" id="assembling-the-graph"></div>
<p><strong>With all our building blocks in place, let&#8217;s wire them together and expose a <em>CompiledGraph</em> bean</strong>:</p>
<pre><code class="language-java">@Bean
CompiledGraph excuseGraph(
  KeyStrategyFactory keyStrategyFactory,
  InventExcuseNode inventExcuseNode,
  ManagerReactsNode managerReactsNode,
  ExcuseDispatcher excuseDispatcher
) throws GraphStateException {
    StateGraph excuseGraph = new StateGraph(keyStrategyFactory)
      .addNode("invent_excuse", AsyncNodeAction.node_async(inventExcuseNode))
      .addNode("manager_reacts", AsyncNodeAction.node_async(managerReactsNode))
      .addEdge(StateGraph.START, "invent_excuse")
      .addEdge("invent_excuse", "manager_reacts")
      .addConditionalEdges(
        "manager_reacts",
        AsyncEdgeAction.edge_async(excuseDispatcher),
        Map.of(
          "escalate", "invent_excuse",
          "stop", StateGraph.END
        )
      );
    return excuseGraph.compile();
}</code></pre>
<p>First, we create a <em>StateGraph</em> instance and pass it our <em>KeyStrategyFactory</em> bean. This helps the graph handle updates for each state key. Next, we register our two nodes using the <em>addNode()</em> method, giving each of them a unique name.</p>
<p>Then, we define the flow using the <em>addEdge()</em> method. <strong>We connect the <em>START</em> constant to our <em>invent_excuse</em> node, marking it as the entry point</strong>. From there, we connect <em>invent_excuse</em> to the <em>manager_reacts</em> node.</p>
<p>After that, we attach our <em>excuseDispatcher</em> to the <em>manager_reacts</em> node using the <em>addConditionalEdges()</em> method. <strong>The map we pass here links the labels our dispatcher returns to their target nodes</strong>. The <em>escalate</em> label loops back to the <em>invent_excuse</em> node. Meanwhile, the <em>stop</em> label moves to the <em>END</em> constant, which finishes the workflow.</p>
<h2 id="bd-testing-the-workflow" data-id="testing-the-workflow">8. Testing the Workflow</h2>
<div class="bd-anchor" id="testing-the-workflow"></div>
<p>Now that we&#8217;ve assembled our graph, let&#8217;s see how the workflow behaves under different situations.</p>
<h3 id="bd-1-exposing-a-rest-api" data-id="1-exposing-a-rest-api">8.1. Exposing a REST API</h3>
<div class="bd-anchor" id="1-exposing-a-rest-api"></div>
<p>First, let&#8217;s expose an API endpoint that allows us to initiate the workflow:</p>
<pre><code class="language-java">@PostMapping("/excuse")
ResponseEntity&lt;ExcuseResponse&gt; generateExcuse(@RequestBody ExcuseRequest request) {
    OverAllState finalState = excuseGraph
      .invoke(
        Map.of("situation", request.situation()),
        RunnableConfig.builder()
          .threadId(UUID.randomUUID().toString())
          .build())
      .orElseThrow();
    ExcuseResponse response = new ExcuseResponse(
      finalState.value("excuse", ""),
      finalState.value("managerReplies", List.of()),
      finalState.value("attempts", 0),
      ExcuseDispatcher.isBelieved(finalState.value("believability", 0.0)));
    return ResponseEntity.ok(response);
}
record ExcuseRequest(String situation) {}
record ExcuseResponse(
  String finalExcuse,
  List&lt;String&gt; managerReplies,
  Integer attempts,
  boolean believed
) {}</code></pre>
<p>Here, we call the <em>invoke()</em> method on our injected <em>CompiledGraph</em> bean. <strong>We pass the <em>situation</em> from the request as the initial state</strong>. Additionally, we pass a <em>RunnableConfig</em> instance with a random <em>threadId</em>. <strong>This helps keep execution of concurrent requests separate from each other</strong>.</p>
<p>The <em>invoke()</em> method runs the graph from start to end and returns the final state. Then, we simply read values from this state and use them to prepare the API response.</p>
<h3 id="bd-2-invoking-the-api-endpoint" data-id="2-invoking-the-api-endpoint">8.2. Invoking the API Endpoint</h3>
<div class="bd-anchor" id="2-invoking-the-api-endpoint"></div>
<p>Now, let&#8217;s use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/httpie-http-client-command-line">HTTPie</a> CLI to invoke our API endpoint:</p>
<pre><code class="language-bash">http POST :8080/excuse situation="Employee was 30 seconds late to the daily standup"</code></pre>
<p>Here, we enter a mundane situation. Let&#8217;s see what we get as a response:</p>
<pre class="add-horizontal-scrollbar"><code class="language-json">{
    "attempts": 1,
    "believed": true,
    "finalExcuse": "Sorry, I was only 30 seconds late because my calendar reminder fired late while my laptop was still reconnecting to the meeting room.",
    "managerReplies": [
        "Understood—30 seconds is minor, but please make sure you join promptly next time."
    ]
}</code></pre>
<p>As we can see, <strong>the manager believes the very first excuse, so the workflow ends after a single attempt</strong>. Consequently, the <em>managerReplies</em> list contains just one reply, since the loop never ran again.</p>
<p>Next, let&#8217;s try a situation that&#8217;s much harder to explain and see the API response:</p>
<pre class="add-horizontal-scrollbar"><code class="language-bash">http POST :8080/excuse situation="The employee didn't show up to work for 3 days without any notice. Yet, the employee uploaded pictures of them partying on their public instagram account."</code></pre>
<pre class="add-horizontal-scrollbar"><code class="language-json">{
    "attempts": 3,
    "believed": false,
    "finalExcuse": "The retreat’s emergency coordinator told us all communication had to go through their satellite system, but a solar flare knocked it offline and corrupted the evacuation logs. That also delayed the proof they promised to send confirming the phone confiscation, shuttle failure, and automated Instagram reposts.",
    "managerReplies": [
        "Frankly, this is difficult to believe, and disappearing for three days without finding any way to notify us is unacceptable.",
        "That explanation is extraordinarily unlikely, and we need to discuss your three-day absence and the conflicting Instagram activity immediately.",
        "That explanation strains credibility beyond reason; send the promised proof immediately, and we’ll discuss your three-day unreported absence."
    ]
}</code></pre>
<p>Here, the manager doesn&#8217;t believe any of the excuses. Hence, the workflow loops until it reaches the maximum number of attempts.</p>
<h2 id="bd-conclusion" data-id="conclusion">9. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we implemented a graph-based agentic workflow using Spring AI Alibaba.</p>
<p>We started by defining the state keys along with their strategies. Then, we implemented two LLM-powered nodes and a conditional edge that decides whether to loop or stop the workflow. Finally, we assembled everything into a graph and saw how the workflow adapts its path at runtime.</p>
<p>As always, all the code examples used in this article are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-alibaba">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-alibaba-graph-based-workflows">Building Graph-Based Workflows With Spring AI Alibaba</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/971204387/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971204387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971204387/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 href="https://feeds.feedblitz.com/_/24/971204387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971204387/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971204387/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-alibaba-graph-based-workflows#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-alibaba-graph-based-workflows/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/~/971204387/0/baeldung/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-weekly-667</feedburner:origLink>
		<title>Java Weekly, Issue 667</title>
		<link>https://feeds.feedblitz.com/~/971192846/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/971192846/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 09 Oct 2026 15:26:39 +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=205178</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>Spring + Jev, and the real use of unit testing.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/971192846/0/baeldung">Java Weekly, Issue 667</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971192846/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971192846/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 href="https://feeds.feedblitz.com/_/24/971192846/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971192846/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971192846/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-667#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-667/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://spring.io/blog/2026/10/02/spring-ai-modular-rag-typesafe-jev">&gt;&gt; Spring AI Modular RAG and TypeSafe Jev: Retrieve More, Keep Only What Answers</a></strong> [<span style="color: #993300">spring.io</span>]</p>
<p>A Spring AI&#8217;s RAG pipeline rewrites and expands a question into focused searches, then retrieves and joins matching chunks. TypeSafe Jev filters and reranks those results for answer evidence, while also screening retrieved text for prompt injection before it reaches the model. Interesting.</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://inside.java/2026/10/05/confined-pools/" target="_blank" rel="noopener"><strong>Making Arena.ofConfined() Even Cheaper in JDK 28</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://foojay.io/today/adding-rag-to-jakarta-ee-11-app/" target="_blank" rel="noopener"><strong>Adding RAG to a Jakarta EE 11 App With Only the JDK and Jakarta Specs</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/green-build-wrong-task-a-working-agreement-for-java-teams-using-ai-coding-agents/" target="_blank" rel="noopener"><strong>Green Build, Wrong Task: A Working Agreement for Java Teams Using AI Coding Agents</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/java-in-the-browser-with-teavm/" target="_blank" rel="noopener"><strong>Run Java in the Browser with TeaVM</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/lathe-a-java-language-server-from-your-maven-build/" target="_blank" rel="noopener"><strong>Lathe: A Java Language Server That Works Straight From Your Maven Build</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://inside.java/2026/10/06/jep542-target-jdk28/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 28: 542: PEM Encodings of Cryptographic Objects</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/10/02/jep549-target-jdk28/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 28: 540: Simple JSON API (Incubator)</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://blog.frankel.ch/security-baked-into-jvm/5/" target="_blank" rel="noopener"><strong>Security Baked Into the JVM: no single party controls the outcome</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://www.agilejava.eu/2026/10/07/herding-ducks-with-ibm-bob/" target="_blank" rel="noopener"><strong>Herding Ducks with IBM Bob</strong></a> [<span style="color: #800000">agilejava.eu</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/why-every-jvm-in-your-fleet-warms-up-alone/" target="_blank" rel="noopener"><strong>Why Every JVM In Your Fleet Warms Up Alone</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/idea/2026/10/your-next-action-is-two-dots-away/" target="_blank" rel="noopener"><strong>Your Next Action Is Two Dots Away</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.jetbrains.com/idea/2026/10/java-and-kotlin-by-intellij-idea-the-lsp-based-extension-reaches-release-candidate/" target="_blank" rel="noopener"><strong>Java and Kotlin by IntelliJ IDEA: The LSP-Based Extension Reaches Release Candidate</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://www.satisfice.com/blog/archives/488216" target="_blank" rel="noopener"><strong>Software Engineers Are Becoming More Like Lawyers</strong></a> [<span style="color: #800000">satisfice.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://lucumr.pocoo.org/2026/10/6/codemode/" target="_blank" rel="noopener"><strong>What is Codemode</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.jetbrains.com/ai/2026/10/air-in-ides-eap/" target="_blank" rel="noopener"><strong>A New Agentic Experience: JetBrains Air in IDEs – EAP Now Open</strong></a> [<span style="color: #800000">jetbrains.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/10/01/agent-helidon-scale/" target="_blank" rel="noopener"><strong>Agent Helidon: License to Scale</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://foojay.io/today/foojay-podcast-102/" target="_blank" rel="noopener"><strong>Foojay Podcast #102: Why Foojay Walked Away From WordPress for Static Sites</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/10/05/spring-office-hours-podcast-S5E25" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E25 &#8211; Future of Software Development with Josh Long</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/10/01/a-bootiful-podcast-andres-almiray" target="_blank" rel="noopener"><strong>A Bootiful Podcast: JReleaser creator and fellow Java Champion Andres Almiray</strong></a> [<span style="color: #800000">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> Maven <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/maven/releases/tag/maven-3.10.0" target="_blank" rel="noopener"><strong>3.10.0</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://in.relation.to/2026/10/01/orm-80-beta3/" target="_blank" rel="noopener"><strong>Hibernate 8.0.0.Beta3</strong></a> [<span style="color: #800000">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/10/start-your-next-kmp-app-with-kotlin-toolchain-0-13/" target="_blank" rel="noopener"><strong>Start Your Next KMP App With Kotlin Toolchain 0.13</strong></a> [<span style="color: #800000">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> Jetty <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.1.14" target="_blank" rel="noopener"><strong>12.1.14</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.40" target="_blank" rel="noopener"><strong>12.0.40</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://foojay.io/today/changes-included-in-release-26-08-of-azul-zing-builds-of-openjdk/" target="_blank" rel="noopener"><strong>Changes Included in Release 26.08 of Azul Zing Builds of OpenJDK</strong></a> [<span style="color: #800000">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> Vert.x <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.2.1" target="_blank" rel="noopener"><strong>5.2.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.1.10" target="_blank" rel="noopener"><strong>5.1.10</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.1.9" target="_blank" rel="noopener"><strong>5.1.9</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/4.5.35" target="_blank" rel="noopener"><strong>4.5.35</strong></a> [<span style="color: #800000">github.com/eclipse-vertx</span>]</li>
<li><strong>&gt;&gt;</strong> Elasticsearch <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.5.5" target="_blank" rel="noopener"><strong>9.5.5</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.4.8" target="_blank" rel="noopener"><strong>9.4.8</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v8.19.23" target="_blank" rel="noopener"><strong>8.19.23</strong></a> [<span style="color: #800000">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> Grails <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v8.0.0" target="_blank" rel="noopener"><strong>8.0.0</strong></a> [<span style="color: #800000">github.com/apache</span>]</li>
<li><strong>&gt;&gt;</strong> Micronaut Core <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.15" target="_blank" rel="noopener"><strong>5.2.15</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.14" target="_blank" rel="noopener"><strong>5.2.14</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.13" target="_blank" rel="noopener"><strong>5.2.13</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.12" target="_blank" rel="noopener"><strong>5.2.12</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.11" target="_blank" rel="noopener"><strong>5.2.11</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v3.10.14" target="_blank" rel="noopener"><strong>3.10.14</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v3.10.13" target="_blank" rel="noopener"><strong>3.10.13</strong></a> [<span style="color: #800000">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> Apache Camel <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.18.5" target="_blank" rel="noopener"><strong>4.18.5</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://yosefk.com/blog/unit-tests-mark-territory-more-than-squash-bugs.html">&gt;&gt; Unit tests mark territory more than squash bugs</a></strong> [<span style="color: #993300">yosefk.com</span>]
<br>
</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-667">Java Weekly, Issue 667</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/971192846/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971192846/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971192846/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 href="https://feeds.feedblitz.com/_/24/971192846/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971192846/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971192846/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-667#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-667/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/~/971192846/0/baeldung/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-spring-rest-docs-form-parameters</feedburner:origLink>
		<title>Documenting Form Parameters With Spring REST Docs</title>
		<link>https://feeds.feedblitz.com/~/971064149/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/971064149/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Ulisses Lima]]></dc:creator>
		<pubDate>Tue, 06 Oct 2026 15:29:15 +0000</pubDate>
				<category><![CDATA[Spring MVC]]></category>
		<category><![CDATA[Spring REST Docs]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-spring-rest-docs-form-parameters</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-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 to document form parameters with Spring REST Docs and include their validation constraints in the documentation, too.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/971064149/0/baeldung">Documenting Form Parameters With Spring REST Docs</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971064149/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971064149/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/971064149/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971064149/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971064149/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-spring-rest-docs-form-parameters#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-spring-rest-docs-form-parameters/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-11-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-11-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11.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>Form-encoded endpoints are common in login and subscription flows, but easy to misunderstand without precise request documentation. API consumers need to know which parameters are required, which are optional, which values are valid, and which extra parameters are intentionally ignored.</p>
<p>In this tutorial, we&#8217;ll document form parameters for a Spring MVC endpoint by using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-rest-docs">Spring REST Docs</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mockmvc-vs-webmvctest">MockMvc</a> tests.</p>
<p>It&#8217;s important to note that although related, query parameters serve different use cases and are <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-rest-document-query-parameters">documented with different REST Docs snippets</a>, so we won&#8217;t cover them here.</p>
<h2 id="bd-why-document-form-parameters" data-id="why-document-form-parameters">2. Why Document Form Parameters?</h2>
<div class="bd-anchor" id="why-document-form-parameters"></div>
<p>In form submissions, everything starts as key-value pairs. Without documentation, there is room for ambiguity:</p>
<ul>
<li>Are all fields required?</li>
<li>Which values do constrained fields accept?</li>
<li>Are there ignorable parameters?</li>
</ul>
<p>Spring MVC handles all this for us, but API consumers still need exact details. Spring REST Docs generates documentation snippets after a the test being documenteded passes.</p>
<p>It&#8217;s important to note that REST Docs doesn&#8217;t convert arbitrary assertions into documentation automatically. We still describe each parameter programmatically. The benefit we get is that we get the documentation only from the tests that pass and validate the behavior of the code.</p>
<p><strong>Note that we&#8217;ll handle only form parameters in this article, not the query parameters:</strong></p>
<ul>
<li>Form parameters belong to a request body encoded as <em>application/x-www-form-urlencoded</em></li>
<li>Query parameters are part of the URL, for example <em>/newsletter/subscriptions?frequency=weekly. </em>They serve different use cases and are documented with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-rest-document-query-parameters">different REST Docs snippets</a>.</li>
</ul>
<h2 id="bd-building-a-form-based-endpoint" data-id="building-a-form-based-endpoint">3. Building a Form-Based Endpoint</h2>
<div class="bd-anchor" id="building-a-form-based-endpoint"></div>
<p>Let&#8217;s build a small newsletter subscription API that receives form fields, validates the payload, and returns a JSON response.</p>
<p>Basically, our form model represents request fields from <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-url-encoded-form-data"><em>application/x-www-form-urlencoded</em></a> data. Spring binds parameters to this object by matching parameter names to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-pojo-javabeans-dto-vo#2-pojo-as-a-javabean">JavaBean</a> property names.</p>
<p>So, let&#8217;s start with our subscription form. We use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-validation">Bean Validation</a> annotations to enforce required and constrained values:</p>
<pre><code class="language-java">public class SubscriptionForm {
    @Email
    @NotBlank
    private String email;
    @NotBlank
    private String name;
    @NotBlank
    @Pattern(regexp = "weekly|monthly")
    private String frequency;
    private List&lt;String&gt; topics;
    private boolean marketingAccepted;
    // standard getters and setters
}</code></pre>
<p>The pattern for <em>frequency</em> explicitly states valid values. If a client sends a value that isn&#8217;t <em>weekly</em> or <em>monthly</em>, validation will fail before business logic runs. <strong>Note that Spring REST Docs doesn&#8217;t infer that constraint, so it doesn&#8217;t automatically add valid values to the resulting documentation.</strong></p>
<h2 id="bd-test-setup-and-request-construction" data-id="test-setup-and-request-construction">4. Test Setup and Request Construction</h2>
<div class="bd-anchor" id="test-setup-and-request-construction"></div>
<p>The main idea of REST Docs is generating snippets from tested behavior. So, we use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mockmvc-vs-webmvctest"><em>MockMvc</em></a> with REST Docs configuration to create a request that includes all supported form fields.</p>
<h3 id="bd-1-test-setup-and-snippet-naming" data-id="1-test-setup-and-snippet-naming">4.1. Test Setup and Snippet Naming</h3>
<div class="bd-anchor" id="1-test-setup-and-snippet-naming"></div>
<p>Our test class uses <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mockmvc-vs-webmvctest"><em>@WebMvcTest</em></a>, and we also need to specify the <em>RestDocumentationExtension</em> so JUnit can manage the <em>RestDocumentationContext </em>from Spring REST Docs:</p>
<pre><code class="language-java">@WebMvcTest
@ExtendWith(RestDocumentationExtension.class)
class NewsletterControllerDocumentationTest {
    @Autowired
    private MockMvc mockMvc;
    @BeforeEach
    void setUp(
      WebApplicationContext context, RestDocumentationContextProvider restDocumentation) {
        this.mockMvc = webAppContextSetup(context)
          .apply(documentationConfiguration(restDocumentation))
          .alwaysDo(document("{method-name}"))
          .build();
    }
}</code></pre>
<p>Our <em>setUp()</em> method prepares the context for each test:</p>
<ul>
<li><em>apply(documentationConfiguration(restDocumentation))</em> attaches Spring REST Docs to the <em>MockMvc</em> instance built by <em>webAppContextSetup()</em>, so it captures requests and responses.</li>
<li><strong><em>alwaysDo(document(&#8220;{method-name}&#8221;))</em> adds a default documentation generation action that runs for every <em>mockMvc.perform()</em> in our test.</strong> The <em>{method-name}</em> placeholder captures the current test method name.</li>
</ul>
<p><strong>When we want a fixed snippet directory name, we set an explicit identifier in the test itself, </strong>like <em>document(&#8220;newsletter-subscribe&#8221;)</em>. That creates snippets under <em>target/generated-snippets/newsletter-subscribe</em>.</p>
<h3 id="bd-2-the-form-request" data-id="2-the-form-request">4.2. The Form Request</h3>
<div class="bd-anchor" id="2-the-form-request"></div>
<p>Let&#8217;s break down each part of the test, starting with a simple mock payload that includes the form fields. We use the <em>param()</em> method to send key-value pairs to an endpoint that accepts form payloads:</p>
<pre><code class="language-java">private MockHttpServletRequestBuilder postSubscription() {
    return post("/newsletter/subscriptions")
      .contentType(MediaType.APPLICATION_FORM_URLENCODED)
      .param("email", "api@baeldung.com")
      .param("name", "Baeldung API")
      .param("frequency", "weekly")
      .param("topics", "spring", "testing")
      .param("marketingAccepted", "true");
}</code></pre>
<p>Note that we send <em>topics</em> twice with <em>param(&#8220;topics&#8221;, &#8220;spring&#8221;, &#8220;testing&#8221;)</em>, which is how it handles multi-value form fields.</p>
<h2 id="bd-documenting-form-parameters" data-id="documenting-form-parameters">5. Documenting Form Parameters</h2>
<div class="bd-anchor" id="documenting-form-parameters"></div>
<p>Now, we&#8217;re ready for documentation.</p>
<h3 id="bd-1-describing-form-parameters" data-id="1-describing-form-parameters">5.1. Describing Form Parameters</h3>
<div class="bd-anchor" id="1-describing-form-parameters"></div>
<p>We use <em>formParameters()</em> to document the form payload. We define a name and a description, using <em>optional()</em> for non-required parameters:</p>
<pre><code class="language-java">private FormParametersSnippet docFormParams() {
    return formParameters(
      parameterWithName("email").description("The subscriber email address"),
      parameterWithName("name").description("The display name of the subscriber"),
      parameterWithName("frequency").description("Delivery frequency: weekly or monthly"),
      parameterWithName("topics").optional()
        .description("One or more selected topic values"),
      parameterWithName("marketingAccepted").optional()
        .description("Whether marketing messages are accepted"));
}</code></pre>
<p>If valid <em>frequency </em>values change, we need to remember to manually update the parameter description, as the <em>@Pattern</em> annotation doesn&#8217;t infer it.</p>
<p><strong>Most importantly, <em>optional()</em> controls how Spring REST Docs documents a parameter. It doesn&#8217;t affect Bean Validation. Constraints such as <em>@NotBlank</em> apply independently when Spring validates the form object.</strong></p>
<p>If we add or remove a required parameter, one of two things usually happens: either request validation behavior changes and the test fails, or descriptor matching fails because our documented parameters no longer match the request.</p>
<h3 id="bd-2-dealing-with-bean-validation-constraints" data-id="2-dealing-with-bean-validation-constraints">5.2. Dealing With Bean Validation Constraints</h3>
<div class="bd-anchor" id="2-dealing-with-bean-validation-constraints"></div>
<p>To avoid describing constraints manually, we can read validation metadata through <em>ConstraintDescriptions</em> and incorporate it into parameter descriptions by calling <em>descriptionsForProperty()</em>.</p>
<p>Let&#8217;s create a helper method to get the constraint description for the <em>SubscriptionForm</em> class and <em>frequency</em> property:</p>
<pre><code class="language-java">private String constraintsFor(String property) {
    ConstraintDescriptions constraints = new ConstraintDescriptions(SubscriptionForm.class);
    List&lt;String&gt; frequencyConstraints = constraints.descriptionsForProperty("frequency");
}</code></pre>
<p>Now, we can go back to <em>docFormParams()</em> and use our <em>constraintsFor()</em> method:</p>
<pre><code class="language-java">parameterWithName("frequency")
  .description("Delivery frequency. Constraints: " + constraintsFor("frequency")),</code></pre>
<p>This is the description we get:</p>
<pre><code class="language-java">Delivery frequency. Constraints: Must match the regular expression `weekly|monthly`, Must not be blank</code></pre>
<p>So, it includes descriptions for all validation annotations. If they change, the description changes, too.</p>
<h3 id="bd-3-defining-ignored-parameters" data-id="3-defining-ignored-parameters">5.3. Defining Ignored Parameters</h3>
<div class="bd-anchor" id="3-defining-ignored-parameters"></div>
<p>Some clients send extra form values that we don&#8217;t want to include in our API documentation. For example, frontend tracking parameters are irrelevant to business logic. <strong>By default, REST Docs </strong><span style="margin: 0px;padding: 0px"><strong>fails with a </strong><em><strong>SnippetException</strong></em><strong> if it finds any undocumented parameter, so we have to ignore them explicitly</strong></span><strong>.</strong></p>
<p>Let&#8217;s start by going back to <em>postSubscription()</em> and sending the <em>trackingId</em> parameter that we&#8217;ll ignore later:</p>
<pre><code class="language-java">private MockHttpServletRequestBuilder postSubscription() {
    return post("/newsletter/subscriptions")
      // previous parameters ...
      .param("trackingId", "campaign-42");
}</code></pre>
<p>Now, in <em>docFormParams(), </em>we add <em>trackingId</em> as an ignored parameter:</p>
<pre><code class="language-java">private FormParametersSnippet docFormParams() {
    return formParameters(
      // earlier parameters ...
      parameterWithName("trackingId").ignored());
}</code></pre>
<p><strong>This way, the parameter can appear, but it isn&#8217;t part of the API docs we get in the end.</strong></p>
<h2 id="bd-executing-the-test-and-generating-snippets" data-id="executing-the-test-and-generating-snippets">6. Executing the Test and Generating Snippets</h2>
<div class="bd-anchor" id="executing-the-test-and-generating-snippets"></div>
<p>Finally, let&#8217;s put all those parts together in a mock request:</p>
<pre><code class="language-java">@Test
void whenFormRequestIsValid_thenDocumentFormParameters() throws Exception {
    mockMvc.perform(postSubscription())
      .andExpect(status().isCreated())
      .andExpect(jsonPath("$.id").isNumber())
      .andDo(
        document("newsletter-subscribe", 
          docFormParams()));
}</code></pre>
<p><strong>This single test validates the endpoint and defines field-level documentation for all form parameters. Most importantly, it helps keep the contract in one place.</strong></p>
<p>When we run our documented test, Spring REST Docs writes snippet files under <em>target/generated-snippets/newsletter-subscribe</em>, which was the name we chose with the <em>document()</em> method.</p>
<h3 id="bd-1-generated-form-snippet" data-id="1-generated-form-snippet">6.1. Generated Form Snippet</h3>
<div class="bd-anchor" id="1-generated-form-snippet"></div>
<p>The generated snippet for our form POST test execution shows the parameters in a structured format, using the descriptions we provided with the <em>parameterWithName()</em> calls:</p>
<pre><code class="language-markdown">|===
|Parameter|Description
|`+email+`
|The subscriber email address
|`+name+`
|The display name of the subscriber
|`+frequency+`
|Delivery frequency: weekly or monthly
|`+topics+`
|One or more selected topic values
|`+marketingAccepted+`
|Whether marketing messages are accepted
|===</code></pre>
<p>We can include this snippet in AsciiDoc pages during the Maven build using the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/asciidoctor">Asciidoctor</a> plugin.</p>
<p><strong>The main advantage is that our snippets come from requests that actually ran in tests. If tested behavior or descriptors no longer match, tests or snippet generation fail, forcing us to update code and documentation together.</strong></p>
<h2 id="bd-strict-vs-relaxed-parameter-documentation" data-id="strict-vs-relaxed-parameter-documentation">7. Strict vs Relaxed Parameter Documentation</h2>
<div class="bd-anchor" id="strict-vs-relaxed-parameter-documentation"></div>
<p>By default, <em>formParameters(&#8230;)</em> is strict. This means undocumented parameters fail the snippet unless we mark them as ignored.</p>
<p>However, sometimes we want lighter constraints. For example, we may only care about <strong>documenting core fields while allowing additional keys in the request.</strong> In that case, we enter the<strong> relaxed mode via <em>relaxedFormParameters()</em> instead of <em>formParameters()</em>.</strong> Let&#8217;s write a new helper method with <em>relaxedFormParameters()</em>:</p>
<pre><code class="language-java">private FormParametersSnippet docRelaxedCoreFormParams() {
    return relaxedFormParameters(
      parameterWithName("email").description("The subscriber email address"),
      parameterWithName("name").description("The display name of the subscriber"),
      parameterWithName("frequency").description("Delivery frequency: weekly or monthly"));
}</code></pre>
<p>Here&#8217;s the accompanying test method:</p>
<pre><code class="language-java">@Test
void whenOnlyCoreFieldsMatter_thenDocumentWithRelaxedMode() throws Exception {
    mockMvc.perform(postSubscription())
      .andExpect(status().isCreated())
      .andDo(document("newsletter-subscribe-relaxed", docRelaxedCoreFormParams()));
}</code></pre>
<p>It submits the form, checks if the return status is <em>created</em>, then generates the documentation snippets in the <em>newsletter-subscribe-relaxed</em> folder.</p>
<h3 id="bd-1-quick-reference-for-parameter-modes" data-id="1-quick-reference-for-parameter-modes">7.1. Quick Reference for Parameter Modes</h3>
<div class="bd-anchor" id="1-quick-reference-for-parameter-modes"></div>
<p>Let&#8217;s review all parameter processing modes:</p>
<table class="table-styled">
<thead>
<tr>
<th>Mode</th>
<th>How to use</th>
<th>What happens</th>
</tr>
</thead>
<tbody>
<tr>
<td>Required parameter</td>
<td><em>parameterWithName(&#8220;email&#8221;)</em></td>
<td>Must be present in the documented request</td>
</tr>
<tr>
<td>Optional parameter</td>
<td><em>parameterWithName(&#8220;topics&#8221;).optional()</em></td>
<td>We can omit it without snippet failure</td>
</tr>
<tr>
<td>Ignored parameter</td>
<td><em>parameterWithName(&#8220;trackingId&#8221;).ignored()</em></td>
<td>May be present but is excluded from published contract</td>
</tr>
<tr>
<td>Relaxed mode</td>
<td><em>relaxedFormParameters(&#8230;)</em></td>
<td>Tolerates undocumented parameters</td>
</tr>
</tbody>
</table>
<p><strong>In practice, we&#8217;ll use strict mode for stable contracts, add <em>ignored()</em> for known non-contract inputs, and switch to the relaxed mode only when we need additional flexibility.</strong></p>
<h2 id="bd-conclusion" data-id="conclusion">8. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we documented a form-encoded Spring MVC endpoint using Spring REST Docs and a test-first workflow. With Spring REST Docs, we document tested behavior and regenerate snippets as part of the test cycle, which helps avoid documentation drift over time.</p>
<p>We created a dedicated form model, validated input fields, and exposed a POST endpoint that consumes <em>application/x-www-form-urlencoded</em> requests. In the end, we used <em>formParameters()</em> to describe each key, including optional and ignored fields.</p>
<p>We covered required parameters, optional parameters, ignored parameters, and differences between strict and relaxed mode. Also, we saw how to define where REST Docs saves the documentation snippets.</p>
<p>As always, the source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/blob/master/spring-5-rest-docs">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-spring-rest-docs-form-parameters">Documenting Form Parameters With Spring REST Docs</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/971064149/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/971064149/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/971064149/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/971064149/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/971064149/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/971064149/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-spring-rest-docs-form-parameters#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-spring-rest-docs-form-parameters/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/~/971064149/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-666</feedburner:origLink>
		<title>Java Weekly, Issue 666</title>
		<link>https://feeds.feedblitz.com/~/970894736/0/baeldung</link>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 02 Oct 2026 12:07:00 +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=205124</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>Monoliths, Java 28 and performance. A good week.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/970894736/0/baeldung">Java Weekly, Issue 666</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970894736/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970894736/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 href="https://feeds.feedblitz.com/_/24/970894736/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970894736/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970894736/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&nbsp;&#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://inside.java/2026/09/28/performance-update-jdk27/">&gt;&gt; Performance Improvements in JDK 27</a></strong> [<span style="color: #993300;">inside.java</span>]</p>
<p>JDK 27 brings performance goodness across the board. This tour highlights measured gains in areas such as HashMap operations, cryptography, and vectorization, with hardware nuance.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://norlinder.nu/posts/The-Limitations-of-Running-a-Workload-Generator-In-the-Same-JVM-as-the-System-Under-Test/">&gt;&gt; The Limitations of Running a Workload Generator In the Same JVM as the System-Under-Test</a></strong> [<span style="color: #993300;">norlinder.nu</span>]</p>
<p>A workload generator sharing the system-under-test&#8217;s JVM can stop scheduling requests during garbage-collection pauses and distort tail-latency measurements. An interesting 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/~blog.code-cop.org/2026/09/finally-fizz-buzz-turing-machine.html" target="_blank" rel="noopener"><strong>Finally Fizz Buzz Turing Machine</strong></a> [<span style="color: #800000;">code-cop.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/my-love-hate-relationship-with-java-formatters/" target="_blank" rel="noopener"><strong>My love-hate relationship with Java formatters</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/performance-work-between-benchmarks/" target="_blank" rel="noopener"><strong>Lies, Damn Lies and Benchmarks</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-21-doomsday-clock-countdown/" target="_blank" rel="noopener"><strong>The Java 21 Doomsday Clock Countdown</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/one-database-two-models-mysql-json-duality-views-java/" target="_blank" rel="noopener"><strong>One Database, Two Models: Building AI-Friendly Java Applications with MySQL JSON Duality Views</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://inside.java/2026/09/30/faster-post-quantum-cryptography-with-jdk-intrinsics/" target="_blank" rel="noopener"><strong>Faster Post-Quantum Cryptography with JDK Intrinsics</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/09/29/jep541-target-jdk28/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 28: 541: Deprecate the macOS/x64 Port for Removal</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/09/26/jep539-target-jdk28/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 28: 539: Strict Field Initialization in the JVM (Preview)</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://blog.jetbrains.com/kotlin/2026/09/the-companions-to-come/" target="_blank" rel="noopener"><strong>The Companions to Come</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://nurkiewicz.com/2026/09/interoperability-with-java-write-yourself-a-compiler.html" target="_blank" rel="noopener"><strong>Interoperability with Java: Write yourself a compiler, Part VI</strong></a> [<span style="color: #800000;">nurkiewicz.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/09/state-of-kotlin-2026-report/" target="_blank" rel="noopener"><strong>The State of Kotlin in 2026 Report</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-desktop/" target="_blank" rel="noopener"><strong>Build AWT and Swing desktop applications with Quarkus Desktop</strong></a> [<span style="color: #800000;">quarkus.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://www.wimdeblauwe.com/blog/2026/09/27/taming-thymeleaf-4th-edition-release/" target="_blank" rel="noopener"><strong>Taming Thymeleaf 4th edition release</strong></a> [<span style="color: #800000;">wimdeblauwe.com</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-ai-340-gateways-hitl-security/" target="_blank" rel="noopener"><strong>BoxLang AI 3.4.0: Gateways, Human-in-the-Loop, and a Full Security Stack</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.jobrunr.io/en/blog/v9-release/" target="_blank" rel="noopener"><strong>JobRunr &amp; JobRunr Pro v9: See Inside Every Background Job</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jobrunr/jobrunr/releases/tag/v9.0.0" target="_blank" rel="noopener"><strong>v9.0.0 release notes</strong></a> [<span style="color: #800000;">jobrunr.io / github.com/jobrunr</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.40.1" target="_blank" rel="noopener"><strong>Quarkus 3.40.1</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-40-released/" target="_blank" rel="noopener"><strong>3.40.0</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-27-6-released/" target="_blank" rel="noopener"><strong>3.27.6</strong></a> [<span style="color: #800000;">quarkus.io / github.com/quarkusio</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/09/25/spring-ai-2-1-0-M1-available-now/" target="_blank" rel="noopener"><strong>Spring AI 2.1.0-M1 Available Now</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://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.10" target="_blank" rel="noopener"><strong>Micronaut Core 5.2.10</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.30" target="_blank" rel="noopener"><strong>4.10.30</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.9" target="_blank" rel="noopener"><strong>5.2.9</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.8" target="_blank" rel="noopener"><strong>5.2.8</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.7" target="_blank" rel="noopener"><strong>5.2.7</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.6" target="_blank" rel="noopener"><strong>5.2.6</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>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/durable-execution-is-a-property-not-a-product/">&gt;&gt; Durable Execution Is a Property, Not a Product</a></strong> [<span style="color: #993300;">foojay.io</span>]</p>
<p>A benchmark compares replay-based workflow orchestration with checkpointed jobs. A solid read to understand when workflow engines&#8217; replay and cross-service coordination justify that overhead.</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://foojay.io/today/voip-vpn-builders/" target="_blank" rel="noopener"><strong>VoIP, VPN, and the Build System Behind Them</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://martinfowler.com/bliki/SensibleDefault.html" target="_blank" rel="noopener"><strong>Bliki: Sensible Default</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://netflixtechblog.com/trading-a-cloud-identity-for-your-own-workload-attestation-on-managed-compute-516d5a29b252" target="_blank" rel="noopener"><strong>Trading a Cloud Identity for Your Own: Workload Attestation on Managed Compute</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.medium.com/netflix-stratum-media-processing-automated-container-right-sizing-ddf9aa68a801" target="_blank" rel="noopener"><strong>Netflix Stratum Media Processing: Automated Container Right-Sizing</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://vladmihalcea.com/thirteen-years-of-blogging/" target="_blank" rel="noopener"><strong>Thirteen years of blogging</strong></a> [<span style="color: #800000;">vladmihalcea.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://vladmihalcea.com/devtalks-cluj-napoca-2026/" target="_blank" rel="noopener"><strong>What I learned at DevTalks Cluj-Napoca 2026</strong></a> [<span style="color: #800000;">vladmihalcea.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/from-madrs-to-questioning-them/" target="_blank" rel="noopener"><strong>From documenting decisions to questioning them</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://blog.scottlogic.com/2026/09/30/noJS-4-AI.html" target="_blank" rel="noopener"><strong>NoJS 4 &#8211; Pure CSS hackery with AI by Gurveer Arora</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><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/bliki/MonolithFirst.html">&gt;&gt; Monolith First</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-666">Java Weekly, Issue 666</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/970894736/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970894736/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970894736/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 href="https://feeds.feedblitz.com/_/24/970894736/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970894736/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970894736/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&nbsp;&#160;</div>]]>
</content:encoded>
					
		
		
		<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-27-new-features</feedburner:origLink>
		<title>New Features in Java 27</title>
		<link>https://feeds.feedblitz.com/~/970740575/0/baeldung</link>
		
		<dc:creator><![CDATA[Ralf Ueberfuhr]]></dc:creator>
		<pubDate>Tue, 29 Sep 2026 06:55:30 +0000</pubDate>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[>= Java 27]]></category>
		<category><![CDATA[popular]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-27-new-features</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-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 what's new in Java 27: which JEPS are final and which are still in preview.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/970740575/0/baeldung">New Features in Java 27</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970740575/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970740575/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970740575/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970740575/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970740575/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&nbsp;&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-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-11-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11.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>Oracle released version 27 of the Java platform.</p>
<p dir="ltr">Its predecessor, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-26-new-features">Java 26</a>, brought HTTP/3 to the HTTP Client API and removed the Applet API. <strong>Java 27 has nine JEPS: four are final, and five remain in preview or incubation.</strong> No feature graduated from preview, and the four final JEPs are the ones <strong>we notice at runtime, not in our code.</strong></p>
<p dir="ltr">In this tutorial, we&#8217;ll look at what changes without any code adjustments and what moved in the preview features.</p>
<h2 dir="ltr" id="bd-new-defaults-in-the-jvm-runtime" data-id="new-defaults-in-the-jvm-runtime">2. New Defaults in the JVM Runtime</h2>
<div class="bd-anchor" id="new-defaults-in-the-jvm-runtime"></div>
<p>The following changes don&#8217;t require code adjustments or startup flags.</p>
<h3 id="bd-1-compact-object-headers-by-default" data-id="1-compact-object-headers-by-default">2.1. Compact Object Headers by Default</h3>
<div class="bd-anchor" id="1-compact-object-headers-by-default"></div>
<p>Java 25 introduced <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-object-header-reduced-size-save-memory">Compact Object Headers (JEP 519)</a> as a full feature, disabled by default. Subsequent tests and reports showed it could <strong>reduce the memory footprint and allow for faster processing and garbage collection.</strong> Consequently, this feature is now enabled by default in Java 27.vWe can still disable it with <em>-XX:-UseCompactObjectHeaders</em>, but the old layout is planned for deprecation, so that&#8217;s a temporary workaround.</p>
<p>Enabling compact headers by default will have the biggest impact for applications that use many small objects, because the header is a fixed cost per object. The smaller the object, the larger the share it occupies. REST services built around DTOs and entities are typical examples.</p>
<p>Note that if we previously sized -Xmx or a container memory limit by measurement, those values are now too generous. Smaller headers mean the same workload holds a smaller live set. Nothing will break, but re-measuring lets us lower the limit and shrink the container. This <strong>turns into an actual cost saving.</strong></p>
<h3 id="bd-2-g1-as-the-default-garbage-collector-everywhere" data-id="2-g1-as-the-default-garbage-collector-everywhere">2.2. G1 as the Default Garbage Collector Everywhere</h3>
<div class="bd-anchor" id="2-g1-as-the-default-garbage-collector-everywhere"></div>
<p>Since Java 9, G1 has been the default garbage collector, except on machines with a single CPU or less than 1792 MB of memory, where the JVM uses the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jvm-garbage-collectors#1-serial-garbage-collector">Serial GC</a>. Java 27 removes that exception, following the G1 improvements in Java 26 (JEP 522).</p>
<p>In practice, this <strong>affects small containers: CI runners, sidecars, and serverless environments.</strong></p>
<h2 id="bd-security-enhancements" data-id="security-enhancements">3. Security Enhancements</h2>
<div class="bd-anchor" id="security-enhancements"></div>
<p>Two of the four final JEPs improve security, and both take effect without any change to our code.</p>
<h3 id="bd-1-post-quantum-hybrid-key-exchange-for-tls-13" data-id="1-post-quantum-hybrid-key-exchange-for-tls-13">3.1. Post-Quantum Hybrid Key Exchange for TLS 1.3</h3>
<div class="bd-anchor" id="1-post-quantum-hybrid-key-exchange-for-tls-13"></div>
<p>Java 27 combines the classical ECDHE key exchange with the quantum-resistant ML-KEM in TLS 1.3. Applications using <em>javax.net.ssl</em> negotiate this hybrid scheme automatically as soon as the peer supports it.</p>
<p><strong>The enhancement targets attackers who record encrypted traffic today and plan on waiting for quantum computers to become able to decrypt it.</strong></p>
<p>Combining the two schemes means that breaking one is not enough for an attack to succeed. However, ML-KEM is much newer than ECDHE and has seen far less cryptanalysis, so it&#8217;s added alongside the classical exchange rather than replacing it. Should it prove weak, the connection is no worse than today.</p>
<p>To see which scheme a connection actually negotiates, we can turn on the JSSE handshake log:</p>
<pre><code class="language-bash">java -Djavax.net.debug=ssl,handshake -jar app.jar</code></pre>
<p>In the logged messages, the named group is the key exchange both sides agree on. Java 27 supports three hybrid groups:</p>
<ul>
<li><em> X25519MLKEM768</em></li>
<li><em>SecP256r1MLKEM768</em></li>
<li><em>SecP384r1MLKEM1024</em></li>
</ul>
<p>A plain value such as <em>x25519</em> means the peer doesn&#8217;t support any of them.</p>
<h3 id="bd-2-jfr-in-process-data-redaction" data-id="2-jfr-in-process-data-redaction">3.2. JFR In-Process Data Redaction</h3>
<div class="bd-anchor" id="2-jfr-in-process-data-redaction"></div>
<p>Java 27 masks sensitive values in JFR recordings inside the JVM, before they reach the recording file. Values that would previously appear in the recording now show up as <em>[REDACTED]</em>.</p>
<p>This matters because <strong>recordings capture command-line arguments, environment variables, and system properties, where passwords and tokens are typically stored.</strong> Attaching one to a support ticket used to mean shipping those secrets along.</p>
<p>We should be aware that redaction is name-based. The JVM matches property, variable, and argument names against a list of glob patterns. The default settings cover names such as <em>*password*</em> or <em>*token*</em>, but we can add our own patterns with a JVM flag:</p>
<pre><code class="language-bash">java -XX:FlightRecorderOptions:redact-key=*credential*,*secret* -jar app.jar</code></pre>
<p>Here, <strong><em>redact-key</em> applies to the names of system properties and environment variables.</strong> With the patterns above, the values of <em>db.credential</em> and <em>client.secret</em> will also be replaced in the recording, whereas a name such as <em>auth.key</em> still passes through. <strong>Command-line arguments are handled by a separate <em>redact-argument</em> option.</strong></p>
<h2 id="bd-still-in-preview" data-id="still-in-preview">4. Still in Preview</h2>
<div class="bd-anchor" id="still-in-preview"></div>
<p>Five features remain in preview or incubation. Most of them are resubmitted with minor refinements, and one has changes that break existing code.</p>
<h3 id="bd-1-structured-concurrency" data-id="1-structured-concurrency">4.1. Structured Concurrency</h3>
<div class="bd-anchor" id="1-structured-concurrency"></div>
<p>This preview brings code breaking changes in comparison to the sixth preview:</p>
<ul>
<li>The joiners now throw <em>ExecutionException</em> instead of <em>FailedException</em></li>
<li><em>Joiner</em> gains a third type parameter for the exception type</li>
<li><em>awaitAll()</em> is gone</li>
<li><em>onTimeout()</em> becomes <em>timeout()</em></li>
</ul>
<p>Breaks that are due to the exception type will probably be the most common in practice, for example:</p>
<pre><code class="language-java">try (var scope = StructuredTaskScope.open()) {
    Subtask&lt;User&gt; user = scope.fork(() -&gt; loadUser(id));
    Subtask&lt;List&lt;Order&gt;&gt; orders = scope.fork(() -&gt; loadOrders(id));
    scope.join();
    return new Profile(user.get(), orders.get());
} catch (ExecutionException e) {
    throw new ProfileException(e.getCause());
}</code></pre>
<p>Every catch clause written against the sixth preview has to be updated, and <em>ExecutionException</em> is checked, so the compiler will point them out.</p>
<h3 id="bd-2-lazy-constants" data-id="2-lazy-constants">4.2. Lazy Constants</h3>
<div class="bd-anchor" id="2-lazy-constants"></div>
<p><strong>A lazy constant holds a value that is computed on the first access and then behaves like a constant.</strong> The JVM can optimize them much like a final field, which plain lazy initialization behind a synchronized block doesn&#8217;t allow. The typical use is an expensive object we would rather not build during startup. For instance:</p>
<pre><code class="language-java">private static final LazyConstant&lt;ExpensiveParser&gt; PARSER = LazyConstant.of(ExpensiveParser::new);</code></pre>
<p>The third preview removes <em>isInitialized()</em> and <em>orElse()</em>, two low-level methods that invite inspecting the initialization state, exactly what the abstraction is meant to hide.</p>
<p>In return, <em>Set.ofLazy()</em> arrives, so <em>List</em>, <em>Set</em>, and <em>Map</em> now all have lazy variants.</p>
<p><strong>This is the only new API element in this release.</strong></p>
<h3 id="bd-3-pem-encodings-of-cryptographic-objects" data-id="3-pem-encodings-of-cryptographic-objects">4.3. PEM Encodings of Cryptographic Objects</h3>
<div class="bd-anchor" id="3-pem-encodings-of-cryptographic-objects"></div>
<p><em>PEM</em> is the text format that wraps Base64-encoded DER data between markers, and we use it for certificates, CRLs, and keys. Before this API, the JDK offered no standard way to read or write those data. We stripped the markers and decoded the Base64 by hand, or reached for a third-party library.</p>
<p><em>PEMEncoder</em> and <em>PEMDecoder</em> handle both directions, and <em>withEncryption(char[])</em> and <em>withDecryption(char[])</em> cover password-protected private keys.</p>
<p>The third preview is mostly refinement:</p>
<ul>
<li><em>DEREncodable</em> becomes <em>BinaryEncodable</em>, a name that no longer ties the interface to DER.</li>
<li><em>X509Certificate</em> and <em>X509CRL</em> now implement it. <em>EncryptedPrivateKeyInfo</em> gains methods for encrypting and decrypting keys directly.</li>
<li>A new <em>CryptoException</em> reports failures, and constructors taking Base64-encoded byte arrays were added.</li>
</ul>
<h3 id="bd-4-primitive-types-in-patterns-instanceof-and-switch" data-id="4-primitive-types-in-patterns-instanceof-and-switch">4.4. Primitive Types in Patterns, <em>instanceof</em>, and <em>switch</em></h3>
<div class="bd-anchor" id="4-primitive-types-in-patterns-instanceof-and-switch"></div>
<p>Pattern matching and <em>switch</em> have long worked on reference types but stopped at primitives. JEP 532 closes that gap: <em>instanceof</em> and <em>switch</em> accept primitive type patterns, and a pattern matches only when the value fits into the target type without loss.</p>
<p>So. the following sample is valid:</p>
<pre><code class="language-java">static String describe(int value) {
    return switch (value) {
        case byte b -&gt; "fits in a byte: " + b;
        case short s -&gt; "fits in a short: " + s;
        default -&gt; "needs an int: " + value;
    };
}</code></pre>
<p><strong>Java 27 resubmits the feature unchanged from Java 26.</strong> It has been in preview since Java 23.</p>
<h3 id="bd-5-vector-api" data-id="5-vector-api">4.5. Vector API</h3>
<div class="bd-anchor" id="5-vector-api"></div>
<p>The Vector API lets us express computations that the JIT compiles into the CPU&#8217;s vector instructions.</p>
<p><strong>The API is still in incubation and unchanged from Java 26.</strong> <strong>The only difference is the bundled SLEEF library,</strong> which supplies the vector math intrinsics on ARM and RISC-V and <strong>moves from <em>3.6.1</em> to <em>3.9.0</em>.</strong></p>
<p>Twelve incubation rounds sound like a stalled feature, but the reason is a dependency.<strong> The API waits for the value types from <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-valhalla-project">Project Valhalla</a></strong> so that vector values can be flattened instead of allocated. JEP 401 has since been integrated into JDK 28 as a preview, so the next round may be the one that promotes the Vector API out of incubation.</p>
<h2 id="bd-smaller-changes-and-removals" data-id="smaller-changes-and-removals">5. Smaller Changes and Removals</h2>
<div class="bd-anchor" id="smaller-changes-and-removals"></div>
<p>Beyond the JEPs, Java 27 brings a number of smaller changes to the class library and tooling:</p>
<ul>
<li><em>Math</em> and <em>StrictMath</em> gain the inverse hyperbolic functions <em>acosh()</em>, <em>asinh()</em>, and <em>atanh()</em></li>
<li><em>String.encodedLength(Charset)</em> returns the byte length in a given charset without building the array</li>
<li><em>BigDecimal.rootn(int, MathContext)</em> computes <em>n</em>th roots</li>
<li><em>DateTimeFormatter</em> accepts short zone offsets such as <em>+02</em></li>
<li><em>HashMap.putAll()</em> is 66–86% faster when copying from another <em>HashMap</em></li>
<li><em>jcmd VM.security_properties</em> prints the effective security properties of a running JVM</li>
</ul>
<p>The <em>JVM Compiler Interface (JVMCI)</em> has been removed. It was a socket for plugging an externally built JIT compiler into HotSpot, and its main user, the Graal compiler, already left the JDK in Java 17. The GraalVM distribution and Native Image are unaffected.</p>
<h2 id="bd-feature-summary" data-id="feature-summary">6. Feature Summary</h2>
<div class="bd-anchor" id="feature-summary"></div>
<p>Java 27 spreads its nine JEPs across three maturity levels, and only the final ones are active out of the box:</p>
<table class="table-styled" style="border-collapse: collapse;width: 100%;height: 312px">
<tbody>
<tr style="height: 24px">
<th style="width: 62.6527%;height: 24px"><strong>Feature</strong></th>
<th style="width: 10.4712%;height: 24px"><strong>JEP</strong></th>
<th style="width: 26.8761%;height: 24px"><strong>Status</strong></th>
</tr>
<tr style="height: 24px">
<td style="width: 62.6527%;height: 24px">Compact Object Headers by Default</td>
<td style="width: 10.4712%;height: 24px">534</td>
<td style="width: 26.8761%;height: 24px">Final</td>
</tr>
<tr style="height: 48px">
<td style="width: 62.6527%;height: 48px">G1 as the Default Garbage Collector Everywhere</td>
<td style="width: 10.4712%;height: 48px">523</td>
<td style="width: 26.8761%;height: 48px">Final</td>
</tr>
<tr style="height: 48px">
<td style="width: 62.6527%;height: 48px">Post-Quantum Hybrid Key Exchange for TLS 1.3</td>
<td style="width: 10.4712%;height: 48px">527</td>
<td style="width: 26.8761%;height: 48px">Final</td>
</tr>
<tr style="height: 24px">
<td style="width: 62.6527%;height: 24px">JFR In-Process Data Redaction</td>
<td style="width: 10.4712%;height: 24px">536</td>
<td style="width: 26.8761%;height: 24px">Final</td>
</tr>
<tr style="height: 24px">
<td style="width: 62.6527%;height: 24px">Lazy Constants</td>
<td style="width: 10.4712%;height: 24px">531</td>
<td style="width: 26.8761%;height: 24px">Preview</td>
</tr>
<tr style="height: 24px">
<td style="width: 62.6527%;height: 24px">PEM Encodings of Cryptographic Objects</td>
<td style="width: 10.4712%;height: 24px">538</td>
<td style="width: 26.8761%;height: 24px">Preview</td>
</tr>
<tr style="height: 48px">
<td style="width: 62.6527%;height: 48px">Primitive Types in Patterns, instanceof, and switch</td>
<td style="width: 10.4712%;height: 48px">532</td>
<td style="width: 26.8761%;height: 48px">Preview</td>
</tr>
<tr style="height: 24px">
<td style="width: 62.6527%;height: 24px">Structured Concurrency</td>
<td style="width: 10.4712%;height: 24px">533</td>
<td style="width: 26.8761%;height: 24px">Preview</td>
</tr>
<tr style="height: 24px">
<td style="width: 62.6527%;height: 24px">Vector API</td>
<td style="width: 10.4712%;height: 24px">537</td>
<td style="width: 26.8761%;height: 24px">Incubation</td>
</tr>
</tbody>
</table>
<p>The preview features need <em>&#8211;enable-preview</em> at both compile and run time, and the compiler requires us to specify the release:</p>
<pre><code class="language-bash">javac --enable-preview --release 27 Main.java
java --enable-preview Main</code></pre>
<p>Note that the Vector API isn&#8217;t a preview feature but an incubating module, so it takes a different flag:</p>
<pre><code class="language-">javac --add-modules jdk.incubator.vector Main.java
java --add-modules jdk.incubator.vector Main</code></pre>
<p>Both kinds of API may change or disappear in the next release, meaning they belong in experiments rather than in production code.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>Java 27 is a release we notice at runtime rather than in our code: smaller objects, one garbage collector everywhere, a quantum-resistant TLS handshake, and recordings that no longer leak credentials.</p>
<p>Nothing graduated from preview, so there&#8217;s no new production API to build on. But Java 29 will be the next LTS, and testing against 27 now lets us find the obsolete startup flags and the TLS handshakes that older network equipment rejects one release early, instead of all at once during the LTS upgrade.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-27-new-features">New Features in Java 27</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/970740575/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970740575/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970740575/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970740575/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970740575/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970740575/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&nbsp;&#160;</div>]]>
</content:encoded>
					
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-665</feedburner:origLink>
		<title>Java Weekly, Issue 665</title>
		<link>https://feeds.feedblitz.com/~/970561523/0/baeldung</link>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sun, 27 Sep 2026 13:04:27 +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=205082</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>Performance in Java is always getting better.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/970561523/0/baeldung">Java Weekly, Issue 665</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970561523/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 href="https://feeds.feedblitz.com/_/24/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></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://inside.java/2026/09/21/jit-for-java-performance/">&gt;&gt; Just-In-Time Compilation for Java Performance: Recent and Ongoing Improvements</a></strong> [<span style="color: #993300;">inside.java</span>]</p>
<p>The JVM&#8217;s JIT compilers keep adapting &#8211; a quick survey of recent performance improvements and the work underway around Valhalla, Leyden, and Panama.</p>
<p>A very useful look at where Java&#8217;s runtime speed comes from and where its compilers are headed.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/09/21/spring-ai-typesafe-structured-judgment/">&gt;&gt; Spring AI and TypeSafe Jev: Fast, Cheap, Structured Decisions</a></strong> [<span style="color: #993300;">spring.io</span>]</p>
<p>Sometimes an AI application needs a decision, not more text. Jev is for that <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (when it opens up agian).</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://inside.java/2026/09/23/quality-heads-up/" target="_blank" rel="noopener"><strong>Quality Outreach Heads-up &#8211; JDK 28: Rich JavaDoc Notes</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/09/20/jep401-target-jdk28/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 28: 401: Value Objects (Preview)</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://quarkus.io/blog/jekyll-to-roq/" target="_blank" rel="noopener"><strong>The quarkus.io site is now built with Quarkus Roq</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://quarkus.io/blog/mcp-stateless/" target="_blank" rel="noopener"><strong>MCP goes stateless, and Quarkus already implements it</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/09/21/releasing-spring-for-modern-challenges/" target="_blank" rel="noopener"><strong>Releasing Spring for Modern Challenges</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://netflixtechblog.com/leave-the-class-path-in-the-rearview-mirror-67a85b15b6be" target="_blank" rel="noopener"><strong>Leave the Class Path in the Rearview Mirror</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://marcphilipp.de/blog/2026/09/19/mirroring-the-junit-repository-to-codeberg/" target="_blank" rel="noopener"><strong>Mirroring the JUnit repository to Codeberg</strong></a> [<span style="color: #800000;">marcphilipp.de</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/09/17/podcast-070/" target="_blank" rel="noopener"><strong>Episode 70 “AOT Caching &#8211; Netflix&#8217; Practice vs OpenJDK&#8217;s Theory” [I/O]</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://quarkus.io/blog/quarkus-insights-260-cli-tui-java/" target="_blank" rel="noopener"><strong>Quarkus Insights #260: CLI and TUI Applications — A Terminal Renaissance in Java</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/09/21/spring-office-hours-podcast-S5E24" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E24 &#8211; Jev, Java and Spring</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/09/17/a-bootiful-podcast-martin-lippert/" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Spring Tools lead Martin Lippert</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-17-0-released-module-inception-cli-checker-jar-loading-and-much-more/" target="_blank" rel="noopener"><strong>BoxLang 1.17.0 Released: Module Inception, CLI Checker, Jar Loading 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://in.relation.to/2026/09/23/orm-80-beta2/" target="_blank" rel="noopener"><strong>Hibernate 8.0.0.Beta2</strong></a> [<span style="color: #800000;">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/22/helidon27-release/" target="_blank" rel="noopener"><strong>Helidon 27 Released</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://quarkus.io/blog/quarkus-3-39-5-released/" target="_blank" rel="noopener"><strong>Quarkus 3.39.5</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.33.4" target="_blank" rel="noopener"><strong>3.33.4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-33-3-3-released/" target="_blank" rel="noopener"><strong>3.33.3.3</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-27-5-3-released/" target="_blank" rel="noopener"><strong>3.27.5.3</strong></a> [<span style="color: #800000;">quarkus.io / github.com/quarkusio</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.azul.com/blog/whats-new-in-the-september-2026-azul-payara-release/" target="_blank" rel="noopener"><strong>What&#8217;s New in the September 2026 Azul Payara Release?</strong></a> [<span style="color: #800000;">azul.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/ktor/2026/09/18/ktor-3-6-0-is-now-available/" target="_blank" rel="noopener"><strong>Ktor 3.6.0 Is Now Available!</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://github.com/graalvm/graalvm-ce-builds/releases/tag/graal-25.4.4.1.1" target="_blank" rel="noopener"><strong>GraalVM Community 25 Innovation 4 (graal 25.4.4.1.1, jdk 25.0.4.1.1)</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/jhipster/generator-jhipster/releases/tag/v9.4.0" target="_blank" rel="noopener"><strong>JHipster 9.4.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.2.0" target="_blank" rel="noopener"><strong>Vert.x 5.2.0</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/elastic/elasticsearch/releases/tag/v8.19.22" target="_blank" rel="noopener"><strong>Elasticsearch 8.19.22</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/v4.1.9" target="_blank" rel="noopener"><strong>Zuul 4.1.9</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.8" target="_blank" rel="noopener"><strong>4.1.8</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.5" target="_blank" rel="noopener"><strong>4.1.5</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.4" target="_blank" rel="noopener"><strong>Grails 7.2.4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.1.7" target="_blank" rel="noopener"><strong>7.1.7</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.0.17" target="_blank" rel="noopener"><strong>7.0.17</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.2.5" target="_blank" rel="noopener"><strong>Micronaut Core 5.2.5</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.4" target="_blank" rel="noopener"><strong>5.2.4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.29" target="_blank" rel="noopener"><strong>4.10.29</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.3" target="_blank" rel="noopener"><strong>5.2.3</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>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/diagrams-as-code-mermaid-support-on-foojay/">&gt;&gt; Diagrams as Code: Mermaid Support on the new Foojay</a></strong> [<span style="color: #993300;">foojay.io</span>]</p>
<p>Foojay now renders Mermaid diagrams directly from fenced code blocks. Frank Delporte walks through sequence, class, state, ER, and Gantt examples, then covers theme-aware rendering, syntax checks, and accessibility. A practical way to keep diagrams for Java APIs and workflows in the same reviewable source as the article.</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/ai-assisted-genealogy-follow-up/" target="_blank" rel="noopener"><strong>AI-assisted genealogy, a follow-up</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/asciidoc-support-on-foojay/" target="_blank" rel="noopener"><strong>AsciiDoc Support on the new Foojay</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://melix.github.io/blog//2026/09/22-amateur-helioseismology.html" target="_blank" rel="noopener"><strong>Measuring the Sun’s oscillations with an amateur spectroheliograph</strong></a> [<span style="color: #800000;">melix.github.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/2026-dont-like-llms.html" target="_blank" rel="noopener"><strong>I don&#8217;t like 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/09/21/building-the-business-case-for-agentic-software-development.html" target="_blank" rel="noopener"><strong>Building the business case for agentic software development</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://www.satisfice.com/blog/archives/488069" target="_blank" rel="noopener"><strong>Responsible Quality Engineering</strong></a> [<span style="color: #800000;">satisfice.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wildfly.org/news/2026/09/23/Introducing-WildFly-Themes-Community-Driven-Planning-for-WildFly/" target="_blank" rel="noopener"><strong>Introducing WildFly Themes — Community-Driven Planning for WildFly</strong></a> [<span style="color: #800000;">wildfly.org</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><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://ai.baeldung.com/build-your-agentic-ai-harness-course/">&gt;&gt; Build your Agentic Harness is Out</a></strong></p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-665">Java Weekly, Issue 665</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/970561523/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970561523/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 href="https://feeds.feedblitz.com/_/24/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;</div>]]>
</content:encoded>
					
		
		
		<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-hibernate-find-annotation</feedburner:origLink>
		<title>The @Find Annotation in Hibernate</title>
		<link>https://feeds.feedblitz.com/~/970277321/0/baeldung</link>
		
		<dc:creator><![CDATA[Graham Cox]]></dc:creator>
		<pubDate>Sat, 26 Sep 2026 04:20:54 +0000</pubDate>
				<category><![CDATA[JPA]]></category>
		<category><![CDATA[Hibernate]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-hibernate-find-annotation</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-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 about how to use the @Find annotation in Hibernate to generate a repository implementation using method query naming conventions.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/970277321/0/baeldung">The @Find Annotation in Hibernate</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970277321/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-06-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-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/Data-Featured-Image-06-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction"><strong>1. Introduction</strong></h2>
<div class="bd-anchor" id="introduction"></div>
<p>In this article, we’re going to look at how to use the <em>@Find </em>annotation in Hibernate. We’ll see what it is, what it&#8217;s used for, and how to use it.</p>
<h2 id="bd-setting-up-hibernate" data-id="setting-up-hibernate"><strong>2. Setting up Hibernate</strong></h2>
<div class="bd-anchor" id="setting-up-hibernate"></div>
<p>Before we can use the <em>@Find </em>annotation, we need to set up Hibernate and our database.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies"><strong>2.1. Dependencies</strong></h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p><strong>To use <em>@Find</em> in <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core">Hibernate</a>, we need to use version 6.3 or newer. The newest release at the time of writing is <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core/7.4.6.Final">7.4.6.Final</a>:</strong></p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.hibernate.orm&lt;/groupId&gt;
    &lt;artifactId&gt;hibernate-core&lt;/artifactId&gt;
    &lt;version&gt;7.4.6.Final&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p><strong>We also need the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/jakarta.data/jakarta.data-api">Jakarta Data Core API</a>. The newest release at the time of writing is <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/jakarta.data/jakarta.data-api/1.0.2">1.0.2</a>:</strong></p>
<pre><code class="language-">&lt;dependency&gt;
    &lt;groupId&gt;jakarta.data&lt;/groupId&gt;
    &lt;artifactId&gt;jakarta.data-api&lt;/artifactId&gt;
    &lt;version&gt;1.0.2&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>This gives us everything we need to write our repository code.</p>
<h3 id="bd-2-hibernate-processor" data-id="2-hibernate-processor"><strong>2.2. Hibernate Processor</strong></h3>
<div class="bd-anchor" id="2-hibernate-processor"></div>
<p><strong>In addition to the dependencies for writing our repositories, we need to configure the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://hibernate.org/orm/processor/">Hibernate Processor</a>.</strong> This will generate concrete classes for our repository interfaces at compile time.</p>
<p>When using Hibernate 7, this is configured by adding an annotation processor to the <em>maven-compiler-plugin</em>:</p>
<pre><code class="language-xml">&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;annotationProcessorPaths&gt;
                    &lt;path&gt;
                        &lt;groupId&gt;org.hibernate.orm&lt;/groupId&gt;
                        &lt;artifactId&gt;hibernate-processor&lt;/artifactId&gt;
                        &lt;version&gt;7.4.6.Final&lt;/version&gt;
                    &lt;/path&gt;
                &lt;/annotationProcessorPaths&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;
</code></pre>
<p>This needs to match the version of the <em>hibernate-core </em>dependency we specified earlier.</p>
<p>Once the plugin is added, the Maven build will automatically generate additional classes for us, including <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-criteria-queries-metamodel">Hibernate Metamodel</a> classes for our entities, and concrete classes that implement our repository interfaces, as we&#8217;ll see later.</p>
<h3 id="bd-3-database" data-id="3-database"><strong>2.3. Database</strong></h3>
<div class="bd-anchor" id="3-database"></div>
<p><strong>For this article, we need to set up a database.</strong> Our tables will look as follows:</p>
<pre><code class="language-sql">CREATE TABLE authors (
  author_id   BIGINT PRIMARY KEY,
  name        TEXT NOT NULL
);
CREATE TABLE books (
  book_id     BIGINT PRIMARY KEY,
  title       TEXT NOT NULL,
  author_id   BIGINT NOT NULL,
  FOREIGN KEY (author_id) REFERENCES authors(author_id)
);</code></pre>
<p>This gives us two tables &#8211; <em>books </em>and <em>authors </em>&#8211; such that there&#8217;s a foreign key between them.</p>
<p>We then need some data in our database:</p>
<pre><code class="language-sql">INSERT INTO authors (author_id, name) VALUES
  (1, 'George Orwell'),
  (2, 'Haruki Murakami'),
  (3, 'Agatha Christie'),
  (4, 'Ursula K. Le Guin');
INSERT INTO books (book_id, title, author_id) VALUES
  (101, '1984', 1),
  (102, 'Animal Farm', 1),
  (103, 'Norwegian Wood', 2),
  (104, 'Kafka on the Shore', 2),
  (105, 'Murder on the Orient Express', 3),
  (106, 'And Then There Were None', 3),
  (107, 'The Left Hand of Darkness', 4);</code></pre>
<h3 id="bd-4-jpa-entity" data-id="4-jpa-entity"><strong>2.4. JPA Entity</strong></h3>
<div class="bd-anchor" id="4-jpa-entity"></div>
<p><strong>Finally, since we’ll be working with Hibernate, we also need Hibernate entities to represent our data.</strong></p>
<p>First, our <em>Author </em>entity:</p>
<pre><code class="language-java">@Entity
@Table(name = "authors")
public class Author {
    @Id
    @Column(name = "author_id")
    private Long authorId;
    private String name;
    public Long getAuthorId() {
        return authorId;
    }
    public String getName() {
        return name;
    }
}</code></pre>
<p>And then the<em> Book </em>entity:</p>
<pre><code class="language-java">@Entity
@Table(name = "books")
public class Book {
    @Id
    @Column(name = "book_id")
    private Long bookId;
    private String title;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id", nullable = false)
    private Author author;
    public Long getBookId() {
        return bookId;
    }
    public String getTitle() {
        return title;
    }
    public Author getAuthor() {
        return author;
    }
}</code></pre>
<p>This references our <em>Author </em>entity so we can follow those links in our queries.</p>
<h2 id="bd-repository-interfaces" data-id="repository-interfaces"><strong>3. Repository Interfaces</strong></h2>
<div class="bd-anchor" id="repository-interfaces"></div>
<p>Once everything is set up, we&#8217;re ready to start writing our repositories. <strong>These are written as Java interfaces and annotated with the <em>@jakarta.data.repository.Repository </em>annotation:</strong></p>
<pre><code class="language-java">@Repository
public interface BookRepository {
}
</code></pre>
<p>On its own, this is enough for the Hibernate Processor to discover and generate a concrete implementation &#8211; in this case, called <em>BookRepository_.</em></p>
<pre><code class="language-java">// Generated code
public class BookRepository_ implements BookRepository {
    protected StatelessSession session;
    public BookRepository_(StatelessSession session) {
        this.session = session;
    }
    public StatelessSession session() {
        return session;
    }
}</code></pre>
<p>This generates an implementation constructed with a <em>StatelessSession</em> instance. We can then create instances of this as needed when we want to query our data.</p>
<h3 id="bd-1-injecting-an-entitymanager" data-id="1-injecting-an-entitymanager"><strong>3.1. Injecting an EntityManager</strong></h3>
<div class="bd-anchor" id="1-injecting-an-entitymanager"></div>
<p>We often don&#8217;t want to create repository instances on demand. Instead, we&#8217;d like to create them once at the start of our application and then pass them around. For example, we might want to create them in our Spring context.</p>
<p><strong>Fortunately, Hibernate supports this too. All we need to do is add a special method to our interface that returns an <em>EntityManager</em>:</strong></p>
<pre><code class="language-java">@Repository
public interface AuthorRepository {
    EntityManager entityManager();
}
</code></pre>
<p>If we do this, Hibernate will construct an alternative form of our repository that is constructed using our <em>EntityManager </em>instead:</p>
<pre><code class="language-java">// Generated code
public class AuthorRepository_ implements AuthorRepository {
    protected EntityManager entityManager;
    public AuthorRepository_(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
    @Override
    public EntityManager entityManager() {
        return entityManager;
    }
}</code></pre>
<p>We can then safely construct this once and reuse it as much as needed.</p>
<h2 id="bd-finding-by-fields" data-id="finding-by-fields"><strong>4. Finding By Fields</strong></h2>
<div class="bd-anchor" id="finding-by-fields"></div>
<p>Once we&#8217;ve got our repository, we need to be able to do something with it. <strong>We can add methods to find entities using the <em>@Find </em> annotation, with special conventions for parameter names and return types.</strong></p>
<pre><code class="language-java">@Repository
public interface BookRepository {
    @Find
    List&lt;Book&gt; getAllBooks();
}
</code></pre>
<p>Here we have a method called <em>getAllBooks</em>. The method name is unimportant. However, the fact that it returns a <em>List&lt;Book&gt; </em>means that the generated code will understand that we&#8217;re working with <em>Book </em>entities, and that we&#8217;re returning all of the ones that match our query.</p>
<p>This will equate to running the following query:</p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0</code></pre>
<p>We can go a step further with this by actually filtering our results:</p>
<pre><code class="language-java">@Find
Book getBookWithTitle(String title);
</code></pre>
<p><strong>Again, the method title can be anything. However, the parameter name must exactly match a field in our <em>Book </em>entity.</strong> As such, Hibernate generates code that will filter by that field:</p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0 
WHERE b1_0.title = ?</code></pre>
<p>This time, we return only a single <em>Book </em>entity. As such, Hibernate knows to return a single matching entity. If there isn&#8217;t one, we&#8217;ll get an <em>EmptyResultException</em> thrown instead. Alternatively, if there are multiple records that match, we&#8217;ll get a <em>NonUniqueResultException </em>thrown.</p>
<h2 id="bd-optional-results" data-id="optional-results"><strong>5. Optional Results</strong></h2>
<div class="bd-anchor" id="optional-results"></div>
<p>Sometimes we want to search for a single record that may not exist. We can handle the <em>EmptyResultException</em> that gets thrown, but this isn&#8217;t ideal.</p>
<p>Hibernate supports a few ways to handle this automatically. <strong>The most obvious is to update our method to return <em>Optional&lt;Book&gt;</em> instead:</strong></p>
<pre><code class="language-java">@Find
Optional&lt;Book&gt; getOptionalBookWithTitle(String title);
</code></pre>
<p>In this case, an unknown record returns <em>Optional.empty()</em> instead.</p>
<p><strong>Alternatively, if we don&#8217;t want to deal with this, we can annotate our method with <em>jakarta.annotation.Nullable.</em></strong></p>
<pre><code class="language-java">@Find
@Nullable
Book getNullableBookWithTitle(String title);
</code></pre>
<p>This tells the generated code to return <em>null </em>instead of throwing an exception.</p>
<p>In both cases, the database queries are identical. The only difference is what the generated code does with the results after executing the query. Both cases also still throw a <em>NonUniqueResultException </em>if the query returns more than one result.</p>
<h2 id="bd-finding-by-nested-fields" data-id="finding-by-nested-fields"><strong>6. Finding By Nested Fields</strong></h2>
<div class="bd-anchor" id="finding-by-nested-fields"></div>
<p><strong>So far we&#8217;ve seen how to find records based on data directly in that record. However, often we want to search based on related data too.</strong> Hibernate can do this based on how we name our method parameters. If we use a &#8220;$&#8221; symbol, this is interpreted as linking fields through related entities:</p>
<pre><code class="language-java">@Find
List&lt;Book&gt; getAllBooksByAuthorName(String author$name);</code></pre>
<p>Here, our query will join from <em>Book</em> through the <em>Book.author </em>field, and then query based on <em>Author.name:</em></p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0 
  JOIN authors a1_0 ON a1_0.author_id = b1_0.author_id 
WHERE a1_0.name = ?</code></pre>
<p>We can use this technique for as many steps as we want. However, we can only follow relationships that use<em> @ManyToOne </em>or <em>@OneToOne</em>. Relationships using <em>@OneToMany </em>or <em>@ManyToMany </em>won&#8217;t work since the generated query would need to link to multiple records.</p>
<h2 id="bd-multiple-results" data-id="multiple-results"><strong>7. Multiple Results</strong></h2>
<div class="bd-anchor" id="multiple-results"></div>
<p>So far we&#8217;ve seen how to return either a single result or all matching results. However, sometimes we need more control. Hibernate can manage all of this for us.</p>
<h3 id="bd-1-sorting" data-id="1-sorting"><strong>7.1. Sorting</strong></h3>
<div class="bd-anchor" id="1-sorting"></div>
<p>By default, queries that return lists of records return them in the order the database returns them. This can vary based on many factors, so we can&#8217;t rely on it for consistent ordering.</p>
<p><strong>If we add the <em>@OrderBy</em> annotation to our method, Hibernate will sort our results by the specified fields:</strong></p>
<pre><code class="language-java">@Find
@OrderBy("title")
List&lt;Book&gt; getAllBooks();
</code></pre>
<p>This equates to executing the following query:</p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0 
ORDER BY b1_0.title</code></pre>
<p>With the addition of the <em>ORDER BY</em> clause for consistent sorting.</p>
<p>If necessary, we can repeat the annotation to allow sorting by multiple fields:</p>
<pre><code class="language-java">@Find
@OrderBy("title")
@OrderBy("author$name")
List&lt;Book&gt; getAllBooks();
</code></pre>
<p>Note that we can also sort by fields on joined entities using exactly the same syntax as we saw earlier. Doing this generates a query like this:</p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0 
  JOIN authors a1_0 ON a1_0.author_id = b1_0.author_id 
ORDER BY b1_0.title desc, a1_0.name</code></pre>
<p>We can also specify the sort direction using the <em>descending </em>parameter. This defaults to <em>false</em>, but we can change this to indicate descending sorts:</p>
<pre><code class="language-java">@Find
@OrderBy(value = "title", descending = true)
List&lt;Book&gt; getAllBooks();
</code></pre>
<p>Unsurprisingly, we can mix all of this as needed, allowing for sorts on multiple fields in different directions.</p>
<p><strong>We can also allow dynamic sorting by accepting a parameter of type <em>Order&lt;Book&gt;</em>.</strong></p>
<pre><code class="language-java">@Find
List&lt;Book&gt; getAllBooks(Order&lt;Book&gt; sort);
</code></pre>
<p>This is generic over the entity that we&#8217;re working on. If we use the wrong generic type, Hibernate will throw an error at compile time. We can now call this and define the sorts at runtime:</p>
<pre><code class="language-java">List&lt;Book&gt; books = repository.getAllBooks(Order.by(
  Sort.asc("title"),
  Sort.desc("author.name")
));
</code></pre>
<p>Note that here we need to specify nested fields using dotted syntax instead of the &#8220;$&#8221; symbol, but otherwise it all works the same.</p>
<h3 id="bd-2-pagination" data-id="2-pagination"><strong>7.2. Pagination</strong></h3>
<div class="bd-anchor" id="2-pagination"></div>
<p><strong>In addition to sorting our results, we can also request specific pages of results. We can do this by adding a parameter of type <em>PageRequest</em> to our method:</strong></p>
<pre><code class="language-java">@Find
@OrderBy("title")
List&lt;Book&gt; getBooksPage(PageRequest pageRequest);
</code></pre>
<p>We don&#8217;t technically need to enforce ordering on these queries, but it’s usually a good idea so results stay consistent across pages.</p>
<p>We can then call this specifying the desired page and page size:</p>
<pre><code class="language-java">List&lt;Book&gt; books = repository.getBooksPage(PageRequest.ofPage(1, 3, true));
</code></pre>
<p>Note that the page is 1-indexed, so this will return the first page of 3 records.</p>
<p><strong>If we want to know more about the details of the page, we need to change our return type to <em>Page&lt;Book&gt;</em> instead of <em>List&lt;Book&gt;</em>:</strong></p>
<pre><code class="language-java">@Find
@OrderBy("title")
Page&lt;Book&gt; getBooksPage(PageRequest pageRequest);</code></pre>
<p>We now have access to not only the page contents, but also page details like the total number of elements and whether there are next and previous pages:</p>
<pre><code class="language-java">long total = books.totalElements();
if (books.hasNext()) {
    // Do something
}
if (books.hasPrevious()) {
    // Do something
}</code></pre>
<p>Note that the third parameter to our <em>PageRequest.ofPage()</em> call tells Hibernate whether we want a total count of the elements across all pages. If we pass <em>true</em>, Hibernate executes an additional query to count matching elements. If we pass in <em>false, </em>Hibernate doesn&#8217;t execute this additional query, and the total number of elements won&#8217;t be available.</p>
<h2 id="bd-summary" data-id="summary"><strong>8. Summary</strong></h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we’ve had a very brief look at using the <em>@Find </em>annotation in Hibernate. We’ve seen what it is, how it works, and how we can use it. Next time you need to generate repositories like this, why not give it a go?</p>
<p>As always, all the code from this article is available over on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate-queries-3">GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-hibernate-find-annotation">The @Find Annotation in Hibernate</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><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/970277321/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970277321/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-06-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;</div>]]>
</content:encoded>
					
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-150x150.jpg</webfeeds:featuredImage></item>
</channel></rss>

