<?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>Sat, 04 Jul 2026 21:28:22 +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-sql-string-building</feedburner:origLink>
		<title>Building up a SQL Query String in Java</title>
		<link>https://feeds.feedblitz.com/~/959091590/0/baeldung~Building-up-a-SQL-Query-String-in-Java</link>
					<comments>https://feeds.feedblitz.com/~/959091590/0/baeldung~Building-up-a-SQL-Query-String-in-Java#respond</comments>
		
		<dc:creator><![CDATA[Ali Imran Nagori]]></dc:creator>
		<pubDate>Sat, 04 Jul 2026 21:28:22 +0000</pubDate>
				<category><![CDATA[Persistence]]></category>
		<category><![CDATA[JDBC]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[StringBuilder]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-sql-string-building</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="almost done featured" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" fetchpriority="high" /><p>Learn how to build dynamic SQL queries in Java using <em>StringBuilder</em>, <em>StringJoiner</em>, and <em>PreparedStatement</em> to avoid common pitfalls.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/959091590/0/baeldung~Building-up-a-SQL-Query-String-in-Java">Building up a SQL Query String in Java</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/959091590/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Core-Java-1.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-sql-string-building#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-sql-string-building/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="almost done featured" style="float: left; margin-right: 5px;" decoding="async" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1-768x402.jpg 768w" sizes="(max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>When writing <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/sql/">SQL</a> queries, we often need to build the query dynamically. For instance, dynamic string building is usually required when writing logic for filters, runtime-specified sorts, and optional join operations.</p>
<p>In this tutorial, we&#8217;ll see how Java creates SQL strings. Notably, we&#8217;ll use the Baeldung <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/sql/simple-schema">Simple University Schema</a>.</p>
<h2 id="bd-the-challenge-of-dynamic-queries" data-id="the-challenge-of-dynamic-queries">2. The Challenge of Dynamic Queries</h2>
<div class="bd-anchor" id="the-challenge-of-dynamic-queries"></div>
<p>Let&#8217;s suppose we&#8217;re building a student search feature. Consequently, a user can search for students using one or more optional filters:</p>
<ul>
<li>name</li>
<li>student ID</li>
<li>enrollment date</li>
<li>others</li>
</ul>
<p>Since filters may vary depending on search criteria, we need different or dynamic SQL queries.</p>
<p>If no filters are selected, we simply retrieve all students:</p>
<pre><code class="language-sql">SELECT * FROM Student;</code></pre>
<p>However, if we search only by name, the query becomes:</p>
<pre><code class="language-sql">SELECT * FROM Student WHERE name LIKE ?;</code></pre>
<p>If we search by both name and student ID, we need an additional condition:</p>
<pre><code class="language-sql">SELECT * FROM Student WHERE name LIKE ? AND id = ?;</code></pre>
<p>Finally, if all filters are provided, the query becomes:</p>
<pre><code class="language-sql">SELECT * FROM Student WHERE name LIKE ? AND id = ?
AND enrollment_date = ?;</code></pre>
<p>As the query grows, it becomes hard to maintain:</p>
<pre><code class="language-java">String sql = "SELECT s.id, s.name, s.national_id, s.birth_date, " 
  + "s.enrollment_date, s.graduation_date, s.gpa " 
  + "FROM students s " 
  + "WHERE s.gpa &gt;= " + minGpa + " " 
  + "AND s.enrollment_date &gt;= '" + enrollmentDate + "' " 
  + (includeGraduated ? "" : "AND s.graduation_date IS NULL ") 
  + "ORDER BY s.gpa DESC, s.name";</code></pre>
<p>As a result, readability becomes a key issue.</p>
<h2 id="bd-dynamic-sql-generation" data-id="dynamic-sql-generation">3. Dynamic SQL Generation</h2>
<div class="bd-anchor" id="dynamic-sql-generation"></div>
<p>When querying a database, users often sort the results with different filters. For example, they may choose to sort by name or date. Thus, <strong>we should be able to create an SQL query with dynamic parameters</strong>.</p>
<h3 id="bd-1-using-the-stringbuilder-class" data-id="1-using-the-stringbuilder-class">3.1. Using the <em>StringBuilder</em> Class</h3>
<div class="bd-anchor" id="1-using-the-stringbuilder-class"></div>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-strings-concatenation#string-builder"><em>StringBuilder</em></a> class in Java enables changes to string objects that are normally immutable in the <em>String</em> class.</p>
<p>Thus, we can assemble SQL queries in steps:</p>
<pre><code class="language-java">StringBuilder sb = new StringBuilder("SELECT id, name FROM Student");
if (enrollmentDate != null) {
    sb.append(" WHERE enrollment_date = ?");
}
if (birthDate != null) {
    sb.append(" AND birth_date = ?");
}</code></pre>
<p>Still, the above solution isn&#8217;t complete. For example, <strong>if the user provides a <em>null</em> value to <em>enrollment_date</em>, the <em>WHERE</em> clause doesn&#8217;t complete</strong>:</p>
<pre><code class="language-java">SELECT id, name FROM Student AND birth_date = ?</code></pre>
<p>Thus, the resulting SQL becomes invalid.</p>
<h3 id="bd-2-building-conditions-separately" data-id="2-building-conditions-separately">3.2. Building Conditions Separately</h3>
<div class="bd-anchor" id="2-building-conditions-separately"></div>
<p>Let&#8217;s consider another variant of the above code:</p>
<pre><code class="language-java">StringBuilder sb = new StringBuilder("SELECT id, name " + "FROM Student WHERE 1=1");
...</code></pre>
<p>In this case, <strong>we start with a new clause <em>WHERE 1=1</em>, which is always true</strong>. So, we can now start every conditional statement with <em>AND</em>:</p>
<pre><code class="language-java">if (enrollmentDate != null) {
    sb.append(" AND enrollment_date = ?");
}
if (birthDate != null) {
    sb.append(" AND birth_date = ?");
}</code></pre>
<p><strong>The parameters are now dynamically added to the SQL query</strong>. Lastly, we can execute this query using <em>PreparedStatement</em>.</p>
<h3 id="bd-3-execution-using-preparedstatement" data-id="3-execution-using-preparedstatement">3.3. Execution Using <em>PreparedStatement</em></h3>
<div class="bd-anchor" id="3-execution-using-preparedstatement"></div>
<p>Since we&#8217;ve used parameterized queries in the above code, we can use <em>PreparedStatement</em> to execute the query:</p>
<pre><code class="language-java">ps.setString(parameterIndex++, enrollmentDate);
ps.setString(parameterIndex++, birthDate);
</code></pre>
<p>In the above code, <em>ps</em> is the <em>PreparedStatement</em> object.</p>
<h2 id="bd-using-stringjoiner-java-8" data-id="using-stringjoiner-java-8">4. Using <em>StringJoiner</em> (Java 8)</h2>
<div class="bd-anchor" id="using-stringjoiner-java-8"></div>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-string-joiner"><em>StringJoiner</em></a> class in Java can join multiple strings. Thus, <strong>we can use it to connect multiple SQL strings</strong>:</p>
<pre><code class="language-java">String baseQuery = "SELECT * FROM Student";
StringJoiner whereClause = new StringJoiner(" AND ");
whereClause.add("name LIKE ?");
whereClause.add("id = ?");
whereClause.add("enrollment_date = ?");
</code></pre>
<p>In the above example, the search criteria (<em>name</em>, <em>id</em>, and <em>enrollment_date</em>) are dynamic and optional. As a result, we can dynamically build a SQL <em>WHERE</em> clause.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we went through building a SQL query string in Java. First, we saw how long queries become unmanageable. Then, we looked at how the <em>StringBuilder</em> and <em>StringJoiner</em> classes help build dynamic SQL queries.</p>
<p>The code backing this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/persistence-modules/core-java-persistence-4">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-sql-string-building">Building up a SQL Query String in Java</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/959091590/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/959091590/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Core-Java-1.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/959091590/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-sql-string-building#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-sql-string-building/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/959091590/0/baeldung~Building-up-a-SQL-Query-String-in-Java/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Core-Java-1-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors</feedburner:origLink>
		<title>Building LLM-as-a-Judge Using Recursive Advisors in Spring AI</title>
		<link>https://feeds.feedblitz.com/~/959091593/0/baeldung~Building-LLMasaJudge-Using-Recursive-Advisors-in-Spring-AI</link>
					<comments>https://feeds.feedblitz.com/~/959091593/0/baeldung~Building-LLMasaJudge-Using-Recursive-Advisors-in-Spring-AI#respond</comments>
		
		<dc:creator><![CDATA[Ralf Ueberfuhr]]></dc:creator>
		<pubDate>Sat, 04 Jul 2026 21:18:53 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[OpenAI]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204189</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" /><p>Learn how to implement the LLM-as-a-Judge pattern in Spring AI as a quality gate for LLM responses.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/959091593/0/baeldung~Building-LLMasaJudge-Using-Recursive-Advisors-in-Spring-AI">Building LLM-as-a-Judge Using Recursive Advisors in Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/959091593/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#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-llm-judge-recursive-advisors/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>With <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai">Spring AI</a>, we can integrate LLMs into our Spring applications. This is helpful, for example, for processing unstructured data like wiki pages, emails, or chat messages by extracting structured information to store it in a database. However, LLM responses are not deterministic and so neither testable nor handable by our application code. The best way to implement a kind of quality gate for LLM responses is to use an LLM again for evaluation<strong>.</strong> This pattern is called <em>LLM-as-a-judge</em>.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">In this tutorial, we&#8217;ll learn how to implement the LLM-as-a-Judge pattern in Spring AI.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-maven-dependencies" data-id="maven-dependencies">2. Maven Dependencies</h2>
<div class="bd-anchor" id="maven-dependencies"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">We add the Spring AI OpenAI starter to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-starter-model-openai&lt;/artifactId&gt;
    &lt;version&gt;${spring-ai.version}&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>The latest version of <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-openai">spring-ai-starter-model-openai</a> is available on Maven Central.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-the-llm-as-a-judge-pattern" data-id="the-llm-as-a-judge-pattern">3. The LLM-as-a-Judge Pattern</h2>
<div class="bd-anchor" id="the-llm-as-a-judge-pattern"></div>
<p>The LLM-as-a-Judge pattern is like a code review. A developer (the <em>generator</em>) writes code, and a senior engineer (the <em>judge</em>) reviews it and gives structured feedback. If the judge decides that the code doesn&#8217;t meet the requirements, the developer needs to improve it, and the judge then needs to review it again.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Applied to LLMs, the process is:</p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2">The <strong>generator</strong> produces an initial answer to the user&#8217;s question</li>
<li class="whitespace-normal break-words pl-2">The <strong>judge</strong> evaluates that answer against a rubric and returns a numeric score and written feedback</li>
<li class="whitespace-normal break-words pl-2">If the score is too low, the generator receives the original question <em>plus</em> the judge&#8217;s critique and produces a refined answer.</li>
<li>This will repeat until the answer meets the requirements. To avoid endless repetition in case of never reaching the needed quality, it&#8217;s a best practice to limit the number of loops.</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>This pattern improves output quality without model fine-tuning, without a human in the loop, and without changing a single line of calling code.</strong></p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">It&#8217;s most valuable wherever a weak answer has real consequences, e.g., customer support bots, where a vague answer to a billing question damages trust directly. In such cases, the pattern acts as an invisible quality gate between the model and the user.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-1-why-recursive-advisors-are-the-right-fit" data-id="1-why-recursive-advisors-are-the-right-fit">3.1. Why Recursive Advisors Are the Right Fit</h3>
<div class="bd-anchor" id="1-why-recursive-advisors-are-the-right-fit"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-advisors">Advisors in Spring AI</a> are interceptors that wrap the request/response cycle of a <em>ChatClient</em>. A <em>CallAroundAdvisor</em> can inspect a request before it reaches the model and modify the response before it reaches the caller, but it only ever makes one model call per invocation.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-recursive-advisors">Recursive advisors</a> go further: they hold an internal <em>ChatClient</em> reference and can make additional model calls from within the advisor itself.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>We can apply that capability to a specific problem: using the second model call as a judge, not just an extension.</strong> The generate-judge-refine loop becomes a single, self-contained component that&#8217;s completely transparent to the caller.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The caller just invokes <em>chatClient.prompt().user(&#8220;&#8230;&#8221;).call().content()</em> as usual. The judge&#8217;s logic is invisible.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-implementing-the-llm-as-a-judge-advisor" data-id="implementing-the-llm-as-a-judge-advisor">4. Implementing the LLM-as-a-Judge Advisor</h2>
<div class="bd-anchor" id="implementing-the-llm-as-a-judge-advisor"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Let&#8217;s build the feature in three parts: a value object for the verdict, a prompt template for the judge, and the advisor itself.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-1-the-verdict-record" data-id="1-the-verdict-record">4.1. The Verdict Record</h3>
<div class="bd-anchor" id="1-the-verdict-record"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The judge returns a structured verdict. We model it as a simple Java record:</p>
<pre><code class="language-java">public record Verdict(double score, String feedback) {}</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">In our sample, the <em>score</em> is a value between <em>0.0</em> (poor) and <em>1.0</em> (excellent). The <em>feedback</em> explains what&#8217;s missing or weak, and it&#8217;s what we&#8217;ll feed back to the generator during refinement.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-2-the-judge-prompt-template" data-id="2-the-judge-prompt-template">4.2. The Judge Prompt Template</h3>
<div class="bd-anchor" id="2-the-judge-prompt-template"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The judge needs a clear, structured system prompt. For this, let&#8217;s define it as a constant inside the advisor:</p>
<pre><code class="language-java">private static final String JUDGE_SYSTEM_PROMPT = """
    You are a strict quality evaluator for AI-generated answers.
    
    Given a user question and an AI-generated answer, rate the answer quality.
    
    Use this rubric:
    - 1.0: Complete, accurate, and clearly explained
    - 0.7: Mostly correct but missing details or clarity
    - 0.4: Partially correct or overly vague
    - 0.0: Incorrect, irrelevant, or harmful
    
    Respond ONLY with a valid JSON object. Do not add any explanation outside the JSON.
    Format: {"score": &lt;0.0 to 1.0&gt;, "feedback": "&lt;one concise sentence&gt;"}
    """;</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">It&#8217;s important to declare an explicit rubric and a strict JSON output to get a parseable and consistent answer from the judge model.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-3-the-advisor-class-and-its-dependencies" data-id="3-the-advisor-class-and-its-dependencies">4.3. The Advisor Class and Its Dependencies</h3>
<div class="bd-anchor" id="3-the-advisor-class-and-its-dependencies"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>The key design decision of a recursive advisor is that it holds its own <em>ChatClient</em> instance to make additional model calls independently of the advisor chain.</strong> We also inject the configurable quality thresholds:</p>
<pre><code class="language-java">public class LlmJudgeAdvisor implements CallAdvisor {
    private final ChatClient judgeClient;
    private final double scoreThreshold;
    private final int maxRefinements;
    public LlmJudgeAdvisor(
      ChatClient judgeClient,
      double scoreThreshold,
      int maxRefinements
    ) {
        this.judgeClient = judgeClient;
        this.scoreThreshold = scoreThreshold;
        this.maxRefinements = maxRefinements;
    }
    @Override
    public int getOrder() {
        return 0;
    }
    @Override
    public String getName() {
        return "LlmJudgeAdvisor";
    }
    // ...
}</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The <em>ChatClient.Builder</em> receives the same auto-configured model settings as the rest of the application. We could wire a different, specialized judge model here. The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.spring.io/spring-ai/reference/guides/llm-as-judge.html">official Spring AI docs</a> recommend this specifically to avoid the model judging its own output too leniently.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-4-evaluating-and-refining-the-response" data-id="4-evaluating-and-refining-the-response">4.4. Evaluating and Refining the Response</h3>
<div class="bd-anchor" id="4-evaluating-and-refining-the-response"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The <em>adviseCall()</em> method is the only entry point that the advisor chain calls. Rather than forwarding the request once, we call <em>chain.copy(this).nextCall(request)</em>, which creates a sub-chain that includes our advisor again. That&#8217;s what enables the loop to re-evaluate and re-generate across multiple attempts:</p>
<pre><code class="language-java">@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
    for (int attempt = 1; attempt &lt;= maxRefinements + 1; attempt++) { 
        ChatClientResponse response = chain.copy(this).nextCall(request);
        if (attempt &gt; maxRefinements) {
          return response;
        }
        Verdict verdict = evaluate(request, response); 
        if (verdict.score() &gt;= scoreThreshold) {
            return response;
        }
        request = addFeedback(request, verdict.feedback());
    }
    return chain.copy(this).nextCall(request);
}</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The loop needs an explicit upper bound to avoid unbounded recursion. On the last allowed attempt, the method returns the response immediately, skipping the evaluation. There&#8217;s no point scoring an answer we can&#8217;t act on. Otherwise, if the score meets <em>scoreThreshold,</em> it returns early. If not, it augments the request with the judge&#8217;s feedback and continues to the next iteration.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-5-evaluating-the-answer" data-id="5-evaluating-the-answer">4.5. Evaluating the Answer</h3>
<div class="bd-anchor" id="5-evaluating-the-answer"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>The <em>evaluate()</em> method sends the original question and the generated answer to the judge model.</strong> We can use  <em>.entity(&#8230;)</em> to render the response JSON directly into our record, without any manual JSON parsing:</p>
<pre><code class="language-java">private Verdict evaluate(ChatClientRequest request, ChatClientResponse response) {
    String question = request.prompt().getUserMessage().getText();
    String answer = response.chatResponse().getResult().getOutput().getText();
    return judgeClient.prompt()
      .system(JUDGE_SYSTEM_PROMPT)
      .user("Question: " + question + "\n\nAnswer: " + answer)
      .call()
      .entity(Verdict.class);
}</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Spring AI injects a JSON schema derived from the <em>Verdict</em> record into the model call and handles deserialization. This removes the need for any try-catch around JSON parsing and keeps the evaluation code focused on the prompt, not on format handling.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold" id="bd-6-augmenting-the-request-with-feedback" data-id="6-augmenting-the-request-with-feedback">4.6. Augmenting the Request With Feedback</h3>
<div class="bd-anchor" id="6-augmenting-the-request-with-feedback"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">When the rating falls short, we don&#8217;t start over with the original prompt. Instead, let&#8217;s augment it, i.e., we append the judge&#8217;s critique to the user message so the model has full context for its next attempt. The <em>addFeedback()</em> helper does this using <em>ChatClientRequest.mutate()</em>:</p>
<pre><code class="language-java">private ChatClientRequest addFeedback(ChatClientRequest original, String feedback) {
    Prompt augmented = original.prompt()
      .augmentUserMessage(msg -&gt; msg.mutate()
          .text(msg.getText()
              + "\n\nYour previous answer was insufficient. Feedback: " + feedback
              + "\nPlease provide an improved answer.")
          .build());
    return original.mutate().prompt(augmented).build();
}</code></pre>
<p><strong><em>augmentUserMessage()</em> gives us a mutable copy of the user message without touching the rest of the request.</strong> System prompt, tool definitions, and conversation history all stay intact. The updated <em>ChatClientRequest</em> is passed into the next loop iteration.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-configuring-the-application" data-id="configuring-the-application">5. Configuring the Application</h2>
<div class="bd-anchor" id="configuring-the-application"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Let&#8217;s declare both beans in a single <em>@Configuration</em> class. The <em>LlmJudgeAdvisor</em> bean receives its own <em>ChatClient</em> instance, built from the same auto-configured <em>ChatClient.Builder,</em> along with the quality thresholds from <em>application.properties</em>:</p>
<pre><code class="language-java">@Configuration
public class ChatConfig {
    @Bean
    public ChatClient chatClient(
      ChatClient.Builder builder, 
      LlmJudgeAdvisor judgeAdvisor
    ) {
        return builder
          .defaultAdvisors(judgeAdvisor)
          .build();
    }
    @Bean
    public LlmJudgeAdvisor llmJudgeAdvisor(
      ChatClient.Builder builder,
      @Value("${judge.score-threshold:0.7}") double scoreThreshold,
      @Value("${judge.max-refinements:2}") int maxRefinements
    ) {
        return new LlmJudgeAdvisor(
          builder.build(),
          scoreThreshold,
          maxRefinements
        );
    }
}</code></pre>
<p>We can then set the thresholds in <em>application.properties</em>:</p>
<pre><code class="language-properties">spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o-mini
judge.score-threshold=0.75
judge.max-refinements=2</code></pre>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">A score threshold of <em>0.75</em> means the judge must rate the answer at least 75% quality before we accept it. Setting the number of maximum refinements to <em>2</em> caps the recursive loop at two improvement attempts, preventing runaway API usage.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-testing-the-advisor" data-id="testing-the-advisor">6. Testing the Advisor</h2>
<div class="bd-anchor" id="testing-the-advisor"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>Let&#8217;s implement a <em>@SpringBootTest</em> with a single mocked <em>ChatModel</em>.</strong> Both the generator and the judge use the same underlying instance<em>,</em> so one mock covers the entire call sequence. No API key is needed.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The test stubs four sequential <em>chatModel</em> responses matching the expected flow:</p>
<pre><code class="language-java">@SpringBootTest
class LlmJudgeAdvisorTest {
    @MockitoBean
    ChatModel chatModel;
    @Autowired
    ChatClient chatClient;
    @Test
    void givenLowQualityAnswer_whenAdvisorRuns_thenAnswerIsRefined() {
        when(chatModel.call(any(Prompt.class)))
          .thenReturn(buildChatResponse("It runs Java."))
          .thenReturn(buildChatResponse("{\"score\": 0.3, \"feedback\": \"Too vague.\"}"))
          .thenReturn(buildChatResponse(
            "The JVM executes Java bytecode, manages memory, and enables platform independence."))
          .thenReturn(buildChatResponse("{\"score\": 0.9, \"feedback\": \"Complete and accurate.\"}"));
        String result = chatClient.prompt()
          .user("Explain what a JVM is.")
          .call()
          .content();
        assertThat(result).contains("bytecode");
    }
    private ChatResponse buildChatResponse(String content) {
        return new ChatResponse(List.of(new Generation(new AssistantMessage(content))));
    }
}</code></pre>
<p>The four stubs map directly to the generate-evaluate-refine-evaluate cycle: call one is the weak generator response, call two is the judge&#8217;s low verdict, call three is the refined generator response, and call four is the judge&#8217;s passing verdict. The <em>chatClient</em> with the advisor is real. Only the model underneath is mocked. <strong>This means the full advisor logic runs as it would in production.</strong></p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">In this article, we implemented the LLM-as-a-Judge pattern in Spring AI using a recursive <em>CallAdvisor</em>. We saw how the generate-evaluate-refine loop maps naturally onto the advisor model: the first model call produces an answer, Spring AI&#8217;s structured output maps the judge&#8217;s response into a typed <em>Verdict</em>, and the loop augments the original request with feedback until the score is sufficient or the attempt limit is reached.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The result is a reusable component that improves output quality automatically and remains completely transparent to any caller using the <em>ChatClient</em>.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The code for this tutorial is available <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-llm-as-a-judge">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors">Building LLM-as-a-Judge Using Recursive Advisors in Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/959091593/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/959091593/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/959091593/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-llm-judge-recursive-advisors#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-llm-judge-recursive-advisors/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/~/959091593/0/baeldung~Building-LLMasaJudge-Using-Recursive-Advisors-in-Spring-AI/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-653</feedburner:origLink>
		<title>Java Weekly, Issue 653</title>
		<link>https://feeds.feedblitz.com/~/958918577/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/958918577/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 03 Jul 2026 13:12:53 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204222</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Our summer lunch is finally live, and so is my Foundation's to AI coding course.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958918577/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 653</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958918577/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-653#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-653/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.allthingsdistributed.com/2026/06/return-to-two-pizza-culture.html">&gt;&gt; A return to two-pizza culture</a></strong> [<span style="color: #993300;">allthingsdistributed.com</span>]</p>
<p>Revisiting Amazon&#8217;s two-pizza team idea as an operating model for fast, autonomous, accountable engineering. The practical point: team size, ownership, architecture, and feedback loops shape each other. A useful read.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/security-baked-into-jvm/1/" target="_blank" rel="noopener"><strong>Security Baked Into the JVM: why fork Apache River and OpenJDK?</strong></a> [<span style="color: #800000;">frankel.ch</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/this-dependency-update-looked-exactly-like-an-account-takeover/" target="_blank" rel="noopener"><strong>This Dependency Update Looked Exactly Like an Account Takeover</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/7-new-vulnerabilities-in-jackson-in-one-day-this-is-what-ai-assisted-security-research-looks-like/" target="_blank" rel="noopener"><strong>7 New Vulnerabilities in Jackson in One Day: This Is What AI-Assisted Security Research Looks Like</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/endive-1-0-wasm/" target="_blank" rel="noopener"><strong>Endive 1.0 Is Here: Wasm on the JVM Ships Under the Bytecode Alliance</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/ai-assisted-unused-dead-code-removal/" target="_blank" rel="noopener"><strong>AI-Assisted Unused &amp; Dead Code Removal</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/21/spring-boot-41-and-spring-batch" target="_blank" rel="noopener"><strong>MongoDB-backed Spring Batch jobs and more in Spring Boot 4.1</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javaspecialists.eu/archive/Issue336-CopyOnWriteArrayList.subList-ConcurrentModificationException.html" target="_blank" rel="noopener"><strong>CopyOnWriteArrayList.subList() ConcurrentModificationException</strong></a> [<span style="color: #800000;">javaspecialists.eu</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/articles/tradeoffs-event-driven-design/" target="_blank" rel="noopener"><strong>Scaling Java-Based Real-Time Systems: The Hidden Tradeoffs of Event-Driven Design</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/06/eliya-jvm-diagnostic-profile/" target="_blank" rel="noopener"><strong>Eliya 25 Brings a JVM-Level Diagnostic Profile to OpenJDK 25 LTS</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/platform/2026/06/open-sourcing-the-lsp-client-api-in-intellij-idea-2026-2/" target="_blank" rel="noopener"><strong>Open-Sourcing the LSP Client API in IntelliJ IDEA 2026.2</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/06/29/agentrification.html" target="_blank" rel="noopener"><strong>Agentrification and the Agentrification Index</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/genpage-towards-end-to-end-generative-homepage-construction-at-netflix-77146fba8a08" target="_blank" rel="noopener"><strong>GenPage: Towards End-to-End Generative Homepage Construction at Netflix</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/02/simd-vectors-hotspot-jvm/" target="_blank" rel="noopener"><strong>SIMD Vectors in the HotSpot JVM &#8211; Auto Vectorization and the Vector API</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/06/30/zgc-performance-decade/" target="_blank" rel="noopener"><strong>ZGC: A Decade of Redefining Java Performance</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/02/a-bootiful-podcast-sebastien-deleuze" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Sébastien Deleuze on the latest-and-greatest in Spring AI and Spring Framework</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/07/intellij-idea-2026-1-4/" target="_blank" rel="noopener"><strong>IntelliJ IDEA 2026.1.4 Is Out!</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-37-1-released/" target="_blank" rel="noopener"><strong>Quarkus 3.37.1 &#8211; Maintenance release</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/graalvm/graalvm-ce-builds/releases/tag/graal-25.1.3" target="_blank" rel="noopener"><strong>GraalVM Community 25.1.3</strong></a> [<span style="color: #800000;">github.com/graalvm</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.1.11" target="_blank" rel="noopener"><strong>Jetty 12.1.11</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.37" target="_blank" rel="noopener"><strong>12.0.37</strong></a> [<span style="color: #800000;">github.com/jetty</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v8.19.18" target="_blank" rel="noopener"><strong>Elasticsearch 8.19.18</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.3.7" target="_blank" rel="noopener"><strong>9.3.7</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.4.3" target="_blank" rel="noopener"><strong>9.4.3</strong></a> [<span style="color: #800000;">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.18" target="_blank" rel="noopener"><strong>Zuul v3.6.18</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.17" target="_blank" rel="noopener"><strong>v3.6.17</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.2.0" target="_blank" rel="noopener"><strong>Grails 7.2.0</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.1.3" target="_blank" rel="noopener"><strong>7.1.3</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.0.13" target="_blank" rel="noopener"><strong>7.0.13</strong></a> [<span style="color: #800000;">github.com/apache</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.3" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.3</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.0.4" target="_blank" rel="noopener"><strong>5.0.4</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v3.10.9" target="_blank" rel="noopener"><strong>3.10.9</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.21.0" target="_blank" rel="noopener"><strong>Apache Camel 4.21.0</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.18.3" target="_blank" rel="noopener"><strong>4.18.3</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.14.8" target="_blank" rel="noopener"><strong>4.14.8</strong></a> [<span style="color: #800000;">github.com/apache</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">2.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/courses/">&gt;&gt; The Summer Sale is Live</a></strong></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-653">Java Weekly, Issue 653</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958918577/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958918577/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958918577/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-653#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-653/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958918577/0/baeldung~Java-Weekly-Issue/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-dynamic-tool-discovery</feedburner:origLink>
		<title>Spring AI&#8217;s Dynamic Tool Discovery</title>
		<link>https://feeds.feedblitz.com/~/958677014/0/baeldung~Spring-AIs-Dynamic-Tool-Discovery</link>
					<comments>https://feeds.feedblitz.com/~/958677014/0/baeldung~Spring-AIs-Dynamic-Tool-Discovery#respond</comments>
		
		<dc:creator><![CDATA[Kostiantyn Ivanov]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 02:50:03 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[LLM]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204066</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use Tool Search Tool in Spring AI to find available tools without wasting extra tokens.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958677014/0/baeldung~Spring-AIs-Dynamic-Tool-Discovery">Spring AI’s Dynamic Tool Discovery</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958677014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>When we build AI-integrated systems, we often provide our AI clients with a large number of tools. On every request, we send the definitions of all available tools to the LLM so it can decide which ones to use. As a result, we waste a significant number of tokens before the model even processes the user query.  <strong>In this article, we explore how we solve this issue using the Tool Search Tool. </strong></p>
<h2 id="bd-how-the-tool-search-tool-works" data-id="how-the-tool-search-tool-works">2. How the Tool Search Tool Works</h2>
<div class="bd-anchor" id="how-the-tool-search-tool-works"></div>
<p>Using the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/spring-ai-community/spring-ai-tool-search-tool">Tool Search Tool</a><em>,</em> we don&#8217;t send all the tool definitions with the context. We only expose tools when the model actually needs them. First, we index all registered tools at startup. We store them inside the <em>ToolSearcher</em>, but we do NOT send them to the LLM. Next, we send only the Tool Search Tool in the initial request. This keeps the prompt small and focused. When the model needs a capability, it calls the Tool Search Tool using a natural-language query.</p>
<p>We treat this as a discovery signal and trigger a search over the indexed tools using the configured strategy. Next, we return only the most relevant matches from the ToolSearcher and inject their definitions into the next LLM request, so the model sees a focused set of tools instead of the full registry.</p>
<p data-start="305" data-end="488" data-is-last-node="" data-is-only-node=""><strong>Once the relevant tools are available, the model selects and calls the actual tool. We execute it and send the result back to the LLM, which then uses it to generate the final answer.</strong></p>
<h2 id="bd-building-a-travel-assistant-example" data-id="building-a-travel-assistant-example">3. Building a Travel Assistant Example</h2>
<div class="bd-anchor" id="building-a-travel-assistant-example"></div>
<p data-start="54" data-end="187">Let&#8217;s build a travel assistant that helps users plan trips. We connect multiple tools such as flights, hotels, weather, and attractions. <strong>We use the Tool Search Tool approach to avoid sending all tools to the LLM upfront. Instead, we discover tools dynamically at runtime.</strong></p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">3.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p>We start by adding <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/tool-search-tool">tool search</a> dependency support:</p>
<pre><code class="language-">&lt;dependency&gt;
    &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
    &lt;artifactId&gt;tool-search-tool&lt;/artifactId&gt;
    &lt;version&gt;${tool-search-tool.version}&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>Also, let&#8217;s add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/tool-searcher-regex/2.1.0">regex searcher</a> dependency:</p>
<pre><code class="language-">&lt;dependency&gt;
    &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
    &lt;artifactId&gt;tool-searcher-regex&lt;/artifactId&gt;
    &lt;version&gt;${tool-search-tool.version}&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Using it, we&#8217;ll have a <em>regex</em> tool search strategy. The other available strategies can be found in the project repository.</p>
<h3 id="bd-2-flight-tools" data-id="2-flight-tools">3.2. Flight Tools</h3>
<div class="bd-anchor" id="2-flight-tools"></div>
<p>Let’s create a simple <em>FlightTools</em>. We’ll use this tool to retrieve available flight options. In addition, we’ll create a bunch of artificial tools to simulate context overloading<em>:</em></p>
<pre><code class="language-java">public class FlightTools {
    @Tool(description = "Searches available flights between two cities")
    public List&lt;FlightOption&gt; searchFlights(String from, String to, String departureDate) {
        return List.of(
          new FlightOption(
            "Romania Airlines",
            from,
            to,
            departureDate,
            249.99
          )
        );
    }
}</code></pre>
<p>Here we return a single flight option.</p>
<h3 id="bd-3-tokencounteradvisor" data-id="3-tokencounteradvisor">3.3. <em>TokenCounterAdvisor</em></h3>
<div class="bd-anchor" id="3-tokencounteradvisor"></div>
<p>Now let’s create a simple <em>TokenCounterAdvisor</em> that counts the number of tokens used to produce the final result. We’ll use it to compare token usage between different setups, with and without tool search enabled:</p>
<pre><code class="language-java">public class TokenCounterAdvisor implements BaseAdvisor {
    private static final Logger log = LoggerFactory.getLogger(TokenCounterAdvisor.class);
    private final AtomicInteger totalTokenCounter = new AtomicInteger(0);
    @Override
    public String getName() {
        return "TokenCounterAdvisor";
    }
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 1;
    }
    @Override
    public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
        return chatClientRequest;
    }
    @Override
    public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
        var usage = chatClientResponse.chatResponse().getMetadata().getUsage();
        totalTokenCounter.addAndGet(usage.getTotalTokens());
        log.info("Total tokens spent: {}", totalTokenCounter.get());
        return chatClientResponse;
    }
}</code></pre>
<p>Here we store the number of tokens in an <em>AtomicInteger</em> field and log this information during execution. <strong>We attach this advisor to the maximum order, so it runs at the end of the processing pipeline. As a result, it captures the total token usage after all other advisors complete.</strong></p>
<h3 id="bd-4-configuration" data-id="4-configuration">3.4. Configuration</h3>
<div class="bd-anchor" id="4-configuration"></div>
<p>Next, we add the <em>TravelAssistantConfig</em> implementation:</p>
<pre><code class="language-java">@Configuration
public class TravelAssistantConfig {
    @Bean
    ToolSearcher toolSearcher() {
        return new RegexToolSearcher();
    }
    @Bean
    ToolSearchToolCallAdvisor toolSearchToolCallAdvisor(ToolSearcher toolSearcher) {
        return ToolSearchToolCallAdvisor.builder()
          .toolSearcher(toolSearcher)
          .maxResults(5)
          .build();
    }
    @Bean
    ChatClient chatClient(ToolSearchToolCallAdvisor toolSearchToolCallAdvisor, OpenAiChatModel model) {
        return ChatClient.builder(model)
          .defaultTools(
            new FlightTools(),
            new RandomTools()
          )
          .defaultAdvisors(toolSearchToolCallAdvisor, new TokenCounterAdvisor())
          .build();
    }
    @Bean
    ChatClient chatClientWithoutToolsSearch(OpenAiChatModel model) {
        return ChatClient.builder(model)
          .defaultTools(
            new FlightTools(),
            new RandomTools()
          )
          .defaultAdvisors(new TokenCounterAdvisor())
          .build();
    }
}</code></pre>
<p>We configure a travel assistant that uses dynamic tool discovery instead of loading all tools into the LLM. Next, we set up a <em>ToolSearcher</em> with a <em>RegexToolSearcher</em> implementation. This allows us to match tools based on naming patterns and fast keyword-like queries. Then, we create a <em>ToolSearchToolCallAdvisor</em> and connect it to the searcher. After that, we build the <em>ChatClient</em> with the flight tools registered.</p>
<p><span style="margin: 0px;padding: 0px">By design, we&#8217;ve added <em>RandomTools</em>, which includes many unrelated tool definitions</span>.  However, we do not send these tool definitions to the LLM initially. Instead, we only index them in the system. <strong>Finally, we expose only the Tool Search Tool to the model at the start. The model then uses it to discover which tools it actually needs for a given request.</strong> Additionally, we&#8217;ve configured a separate <em>ChatClient</em> bean that doesn&#8217;t use the <em>ToolSearchToolCallAdvisor</em>.</p>
<h3 id="bd-5-call-the-travelassistant" data-id="5-call-the-travelassistant">3.5. Call the TravelAssistant</h3>
<div class="bd-anchor" id="5-call-the-travelassistant"></div>
<p>Finally, let&#8217;s create a <em>ToolsSearchToolLiveTest</em> with similar test cases for both clients:</p>
<pre><code class="language-java">@SpringBootTest
@ActiveProfiles("toolsearchtool")
class ToolsSearchToolLiveTest {
    @Autowired
    private ChatClient chatClient;
    @Autowired
    private ChatClient chatClientWithoutToolsSearch;
    @Test
    void shouldFindFlightsBetweenRomaniaAndCroatiaUsingToolsSearch() {
        String response = getClientResponseString(chatClient);
        assetClientResponse(response);
    }
    @Test
    void shouldFindFlightsBetweenRomaniaAndCroatiaWithoutToolsSearch() {
        String response = getClientResponseString(chatClientWithoutToolsSearch);
        assetClientResponse(response);
    }
    
    private static void assetClientResponse(String response) {
        assertThat(response).isNotBlank();
        assertThat(response).containsIgnoringCase("Croatia");
        assertThat(response).containsIgnoringCase("flight");
    }
    private String getClientResponseString(ChatClient chatClientWithoutToolsSearch) {
        return chatClientWithoutToolsSearch.prompt()
          .user("""
                  Find available flights from Romania to Croatia next week.
                  """)
          .call()
          .content();
    }
}</code></pre>
<p>We&#8217;ve called our travel advisor clients with the same prompt and obtained the same verified results. Now, let&#8217;s compare the token usage in both of them:</p>
<pre><code class="language-">[2026-05-24 11:39:07] [INFO] [c.b.s.t.TokenCounterAdvisor] - Total tokens spent: 974 //With tools search tool
[2026-05-24 11:39:10] [INFO] [c.b.s.t.TokenCounterAdvisor] - Total tokens spent: 3685 //Without tools search tool</code></pre>
<p>As we can see, the difference in token usage is crucial. <strong>The more tools we have in our system, the greater the token savings the Tool Search Tool will provide.</strong></p>
<h2 id="bd-conclusion" data-id="conclusion">4. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we reviewed the Tool Search Tool and demonstrated how it helps reduce token usage in real scenarios. Using it, we can build large AI-integrated systems with hundreds of attached tools and use them efficiently, without wasting tokens. Additionally, we can explore other tool search strategies, such as vector search, or even build our own custom strategy to make tool discovery even more efficient.</p>
<p>As always, the code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-4">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-dynamic-tool-discovery">Spring AI’s Dynamic Tool Discovery</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958677014/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958677014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958677014/0/baeldung~Spring-AIs-Dynamic-Tool-Discovery/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-26-new-features</feedburner:origLink>
		<title>New Features in Java 26</title>
		<link>https://feeds.feedblitz.com/~/958677017/0/baeldung~New-Features-in-Java</link>
					<comments>https://feeds.feedblitz.com/~/958677017/0/baeldung~New-Features-in-Java#respond</comments>
		
		<dc:creator><![CDATA[Karthikeya Tatavarthi]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 02:46:15 +0000</pubDate>
				<category><![CDATA[Core Java]]></category>
		<category><![CDATA[>= Java 26]]></category>
		<category><![CDATA[popular]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-26-new-features</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Explore all key features introduced in Java 26.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958677017/0/baeldung~New-Features-in-Java">New Features in Java 26</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958677017/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-26-new-features#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-26-new-features/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>Oracle released Java 26 in March, which offered meaningful features that boost performance, new APIs, and preview features that may become permanent features in future releases. <strong>This release primarily focused on improvements in areas like garbage collection, networking, startup performance, cryptography, and other language features.</strong></p>
<p>In this tutorial, we’ll briefly talk about all key features introduced in Java 26.</p>
<h2 id="bd-restricts-reflective-modification-of-final-fields" data-id="restricts-reflective-modification-of-final-fields"><strong>2. Restricts Reflective Modification of <em>final</em> Fields</strong></h2>
<div class="bd-anchor" id="restricts-reflective-modification-of-final-fields"></div>
<p>We create <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-final"><em>final</em> variables</a> to ensure developers that their values can&#8217;t be changed after initialization. This will help us create immutable objects and help the JVM perform certain optimizations. However, this can be breached using reflections.</p>
<p><strong>There are libraries and frameworks that use reflections to modify final fields after an object has been created.</strong> This will invalidate the assumptions made by developers, and JVM optimizations may no longer hold true.</p>
<p>Starting from Java 26, the platform takes another step towards stronger encapsulation and integrity. When code tries to modify a final field using deep reflection, the JVM shows warnings indicating to the developer that the operation relies on a behavior that is being phased out. Even though this is currently not enforced, the direction is clear; future Java releases are expected to impose stronger restrictions.</p>
<p>This will impact certain serialization libraries, dependency injection frameworks, mocking tools, and legacy code that bypasses constructors to populate object state.</p>
<p><strong>Developers should now start to audit and identify reflective writes to final fields and migrate toward supported alternatives such as constructors, builders, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/factory-method-vs-factory-vs-abstract-factory">factory methods</a>, or records or use framework-provided APIs designed for immutable objects.</strong></p>
<h2 id="bd-removal-of-applet-api" data-id="removal-of-applet-api"><strong>3. Removal of Applet API</strong></h2>
<div class="bd-anchor" id="removal-of-applet-api"></div>
<p>Applets were historically used to run Java applications in browsers. However, modern browsers don&#8217;t support applets due to performance, compatibility, and security concerns. This is considered an outdated technology, and this API has been marked deprecated since Java 9. Developers nowadays use much better alternatives like Swing/<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/javafx">JavaFX</a> for UI.</p>
<p>With Java 26, the applet API is no longer existent.<strong> This update helps Oracle keep Java streamlined and secured while also reducing the maintenance burden. Its removal will affect only very old legacy applications that still depend on it.</strong></p>
<h2 id="bd-ahead-of-time-object-caching-with-any-gc" data-id="ahead-of-time-object-caching-with-any-gc"><strong>4. Ahead of Time Object Caching with Any GC</strong></h2>
<div class="bd-anchor" id="ahead-of-time-object-caching-with-any-gc"></div>
<p>Java is known for building distributed cloud-native applications that are frequently deployed, scaled, started, and stopped. Oracle is working towards bringing enhancements that reduce the start-up overhead, which allows applications to reach their peak performance more quickly. In Java 26, Oracle introduced one such enhancement.</p>
<p>Java has Ahead of Time (AOT) object caching, which identifies the objects that are expensive to create during startup and stores them in a cache. <strong>When the application starts again, these objects are loaded directly from the cache instead of recreating them, thereby reducing the start-up overhead.</strong></p>
<p>The benefits of Ahead of Time (AOT) are limited to specific GC configurations. <strong>However, in Java 26, this feature is available to any garbage collector, including low-latency collectors such as Z Garbage Collector (ZGC).</strong></p>
<p>This enhancement allows developers to take advantage of AOT object caching without giving up their preferred garbage collector.</p>
<h2 id="bd-http3-for-the-http-client-api" data-id="http3-for-the-http-client-api"><strong>5. HTTP/3 for the HTTP Client API</strong></h2>
<div class="bd-anchor" id="http3-for-the-http-client-api"></div>
<p>Network resilience and latency are key factors for applications that communicate heavily over the network, like microservices, real-time applications, and cloud-native services. <strong>With Java 26, the HTTP Client API now supports HTTP/3, which is the latest version of the HTTP protocol.</strong></p>
<p>While HTTP/1.1 and HTTP/2 were built on top of TCP, HTTP/3 uses the QUIC transport protocol, which runs on UDP and addresses some of the limitations of the TCP protocol. With TCP, a packet loss can delay all streams that are sharing the same connection, while QUIC allows streams to progress independently. QUIC achieves this by combining both transport and security handshakes to reduce the number of network round trips required before data starts flowing.</p>
<p><strong>Another practical advantage is automatic protocol negotiation. If the target server supports HTTP/3, the client can use it. If not, it can seamlessly fall back to HTTP/2 or HTTP/1.1, ensuring compatibility with existing infrastructure.</strong></p>
<p>With the introduction of HTTP/3, applications can now experience reduced latency and improved responsiveness. This is especially useful for real-time and cloud-native applications.</p>
<p>Let&#8217;s now take a look at a quick example:</p>
<pre><code class="language-java">@Test
void givenHttp3Client_whenSendingARequest_thenShouldReceiveSuccessfulResponse()
    throws IOException, InterruptedException {
    HttpClient client = HttpClient.newBuilder()
      .version(HttpClient.Version.HTTP_3)
      .build();
    HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://example.com"))
      .GET()
      .build();
    HttpResponse&lt;String&gt; response =
      client.send(request, HttpResponse.BodyHandlers.ofString());
    assertEquals(200, response.statusCode());
}</code></pre>
<p>To use HTTP/3, we configure the <em>HttpClient</em> by specifying <em>HttpClient.Version.HTTP_3</em> while building the client. We then create a simple <em>GET</em> request to <em>https://example.com</em> and send it synchronously using the <em>send()</em> method. Finally, we verify that the request completes successfully by asserting that the response status code is <em>200</em>.</p>
<p>It&#8217;s worth noting that configuring the client for HTTP/3 doesn&#8217;t guarantee that the connection will use HTTP/3. If the target server doesn&#8217;t support HTTP/3, the client automatically falls back to an earlier HTTP version (HTTP/2 or HTTP/1.1), while still completing the request successfully.</p>
<h2 id="bd-g1-garbage-collector-throughput-improvements" data-id="g1-garbage-collector-throughput-improvements"><strong>6. G1 Garbage Collector Throughput Improvements</strong></h2>
<div class="bd-anchor" id="g1-garbage-collector-throughput-improvements"></div>
<p>For quite some time, the Garbage First (G1) garbage collector has been Java&#8217;s default <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jvm-garbage-collectors">garbage collector</a>. It offers an impressive blend of throughput, memory, and predictability of pause times.</p>
<p>A typical modern Java application will constantly create or update object references. G1 is responsible for tracking object reference changes to perform garbage collection. Ultimately, there is some overhead in this tracking, particularly in applications with high allocation rates or frequent object updates.</p>
<p>With Java 26, G1 now has a set of internal changes that improve the throughput of an application. <strong>One such change is reducing the need for mutator application threads and garbage collection (GC) threads to synchronize when tracking the changes of which object references have been changed. </strong> Because of these improvements, application threads will synchronously wait less time related to GC and more time performing application logic.</p>
<h2 id="bd-pem-encodings-of-cryptographic-objects-second-preview" data-id="pem-encodings-of-cryptographic-objects-second-preview"><strong>7. PEM Encodings of Cryptographic Objects (Second Preview)</strong></h2>
<div class="bd-anchor" id="pem-encodings-of-cryptographic-objects-second-preview"></div>
<p>Applications that deal with security often need to handle certificates, public and private keys, and certificate chains. <strong>Cryptographic objects are usually stored and transmitted using PEM (Privacy Enhanced Mail)</strong>. PEM is a text-based format that allows binary security data to be formatted in a compact manner.</p>
<p>Java developers have historically been required to deploy third-party libraries or write code of their own to read and write PEM files. This has made security-related applications both complex and cumbersome due to additional dependencies and code that serves little or no purpose.</p>
<p>Java 26 introduces the second preview of a newly proposed API to PEM format cryptographic objects that adds the capability to Java SE.<strong> Using the proposed API brings the capability to Java developers to read and write certificates and keys using the standard Java SDK instead of using libraries.</strong></p>
<p>The proposed API enhances Java&#8217;s capability to integrate with standard security protocols and infrastructure. It also simplifies the management of certificates and keys. In addition, the API reduces the time and effort to write custom code for applications. Even if it is still a preview, it provides a more consistent and pragmatic approach to Java security.</p>
<h2 id="bd-structured-concurrency-sixth-preview" data-id="structured-concurrency-sixth-preview"><strong>8. Structured Concurrency (Sixth Preview)</strong></h2>
<div class="bd-anchor" id="structured-concurrency-sixth-preview"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-structured-concurrency">Structured concurrency</a> adds to Project Loom&#8217;s collection of tools to simplify concurrent programming in Java by proposing that multiple related tasks be treated as a unit of work, rather than requiring developers to manage multiple threads separately.</p>
<p>In traditional concurrent code, coordinating threads, handling failures, and cleaning up resources can become complex. <strong>Structured concurrency allows related tasks to run within the defined scope, and if one task fails, other related tasks will be cancelled automatically</strong>. This helps prevent wasted work.</p>
<p>Java 26 includes the sixth preview of Structured Concurrency, bringing further refinements based on real-world usage and community feedback as the feature moves closer to becoming a permanent part of the platform in the future.</p>
<h2 id="bd-lazy-constants-second-preview" data-id="lazy-constants-second-preview"><strong>9. Lazy Constants (Second Preview)</strong></h2>
<div class="bd-anchor" id="lazy-constants-second-preview"></div>
<p>In a normal scenario, class constants are created when the class gets loaded, even though the application never uses them. This contributes to increased startup time and creation of unnecessary objects.</p>
<p>To address this, developers used to introduce their own code to create constants only when they were actually needed. Often this code is complex, less readable, and harder to maintain.</p>
<p><strong>With lazy constants, this mechanism is now built in. Developers can now reap the benefits of lazy loading without having to introduce complex code.</strong> By creating objects only when they are first used, applications can now start faster and use fewer resources. Java 26 includes the second preview of this feature.</p>
<h2 id="bd-10-vector-api-eleventh-incubator" data-id="10-vector-api-eleventh-incubator"><strong>10. Vector API (Eleventh Incubator)</strong></h2>
<div class="bd-anchor" id="10-vector-api-eleventh-incubator"></div>
<p>Vector API in Java 26 is an incubator feature, which allows developers to take advantage of SIMD (Single Instruction, Multiple Data).</p>
<p>Normally, a processor performs an operation on one value at a time. With SIMD, the same operation can be performed on multiple values at once. This helps applications achieve performance closer to native code while remaining portable across different CPU architectures.</p>
<p><strong>This will significantly improve the performance for computation-heavy workloads such as scientific computing, data analytics, image processing, machine learning, and financial calculations.</strong></p>
<h2 id="bd-11-primitive-types-in-patterns-instanceof-and-switch-fourth-preview" data-id="11-primitive-types-in-patterns-instanceof-and-switch-fourth-preview"><strong>11. Primitive Types in Patterns, instanceof, and switch (Fourth Preview)</strong></h2>
<div class="bd-anchor" id="11-primitive-types-in-patterns-instanceof-and-switch-fourth-preview"></div>
<p>Pattern matching makes code easier to read and reduces the need for manual type checks and casting. In previous releases, this is only supported for reference types.</p>
<p><strong>However, with Java 26, this is also included for primitive types like int, long, float, and double.</strong> This allows developers to work with primitive values more naturally in pattern matching constructs, including switch statements and other pattern-based control flow.</p>
<h2 id="bd-12-conclusion" data-id="12-conclusion"><strong>12. Conclusion</strong></h2>
<div class="bd-anchor" id="12-conclusion"></div>
<p>We’ve seen various improvements and new features introduced in Java 26. While they are a mix of permanent and preview features, it’s evident that the Java community is moving towards making Java ideal for modern-day enterprise application development.</p>
<p><span style="font-weight: 400">The source code for the examples can be found </span><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-26-new-features"><span style="font-weight: 400">over on GitHub</span></a><span style="font-weight: 400">.</span></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-26-new-features">New Features in Java 26</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958677017/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958677017/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-26-new-features#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-26-new-features/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958677017/0/baeldung~New-Features-in-Java/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-apache-paimon-guide</feedburner:origLink>
		<title>A Guide to Apache Paimon Java API</title>
		<link>https://feeds.feedblitz.com/~/958675751/0/baeldung~A-Guide-to-Apache-Paimon-Java-API</link>
					<comments>https://feeds.feedblitz.com/~/958675751/0/baeldung~A-Guide-to-Apache-Paimon-Java-API#respond</comments>
		
		<dc:creator><![CDATA[Parthiv Pradhan]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 02:37:29 +0000</pubDate>
				<category><![CDATA[Persistence]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-apache-paimon-guide</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>Explore the Java API for Apache Paimon and learn how to perform CRUD operations on a Paimon database.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958675751/0/baeldung~A-Guide-to-Apache-Paimon-Java-API">A Guide to Apache Paimon Java API</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958675751/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958675751/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-apache-paimon-guide#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-apache-paimon-guide/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>In this tutorial, we&#8217;ll explore the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://paimon.apache.org/docs/1.4/program-api/java-api/">Java API </a><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://paimon.apache.org/docs/1.4/program-api/java-api/" target="_blank" rel="noopener">for Apache Paimon,</a> which is essential for managing the Paimon database from custom applications. We&#8217;ll use it to create a data lake with a table in HDFS storage and perform <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/crud-operations">CRUD</a> operations on it.</p>
<p><strong>This open-source data lakehouse format provides a unified storage layer for real-time streaming, batch processing, and OLAP</strong>. It can serve as a message queue for a streaming application and as a HIVE table for a batch processing application. It supports building data lakes on a variety of storage systems, including object storage, HDFS, and local file systems. Furthermore, it integrates seamlessly with popular distributed data processing engines such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-spark">Apache Spark</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-flink">Apache Flink</a>, and Trino to build scalable data pipelines.</p>
<h2 id="bd-prerequisites" data-id="prerequisites">2. Prerequisites</h2>
<div class="bd-anchor" id="prerequisites"></div>
<p>Let&#8217;s start with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.paimon/paimon-bundle">Maven dependencies</a> required in a Java application to integrate with Apache Paimon:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.paimon&lt;/groupId&gt;
    &lt;artifactId&gt;paimon-bundle&lt;/artifactId&gt;
    &lt;version&gt;1.4.1&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>For our example, we&#8217;ll use HDFS as the storage, and hence, let&#8217;s include the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client-runtime">runtime library</a> for interacting with it:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.hadoop&lt;/groupId&gt;
    &lt;artifactId&gt;hadoop-client-runtime&lt;/artifactId&gt;
    &lt;version&gt;3.4.3&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-create-data-lake" data-id="create-data-lake">3. Create Data Lake</h2>
<div class="bd-anchor" id="create-data-lake"></div>
<p>Let&#8217;s consider a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/data-lake-introduction">data lake</a> to store IT infrastructure performance metrics collected by monitoring tools that poll devices. These metrics can quickly accumulate in volume, and data lakes such as Paimon can reliably store them for long periods.</p>
<p>Let&#8217;s define the data model:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/metrics-datamodel.png"><img decoding="async" class="alignnone size-full wp-image-247667" src="https://www.baeldung.com/wp-content/uploads/2026/06/metrics-datamodel.png" alt="Metrics data model" /></a>
<p data-start="52" data-end="325">Before ingesting monitoring data into Apache Paimon, we need to set up the storage layer by creating a catalog and defining the table schema. <strong>In Paimon, the catalog acts as the entry point to the data lake, while tables define how data is organized, typed, and versioned</strong>.</p>
<p data-start="327" data-end="467">Moving on, let’s look at a simple utility class responsible for initializing the data lake and creating a metrics table:</p>
<pre><code class="language-java">public class PaimonDatabaseManager {
    public static Catalog createCatalog(String warehousePath) {
        CatalogContext context = CatalogContext.create(new Path(warehousePath));
        return CatalogFactory.createCatalog(context);
    }
    public static Identifier createTable(Catalog catalog) throws Exception {
        Schema schema = Schema.newBuilder()
          .column("device_id", DataTypes.STRING())
          .column("metrics_name", DataTypes.STRING())
          .column("metrics_value", DataTypes.DOUBLE())
          .column("source", DataTypes.STRING())
          .column("create_time", DataTypes.TIMESTAMP(3))
          .column("state", DataTypes.STRING())
          .primaryKey("device_id", "metrics_name", "create_time")  
          .build();
        Identifier tableId = Identifier.create("metric_db", "metrics");
        catalog.createDatabase("metric_db", true);
        catalog.createTable(tableId, schema, false);
        return tableId;
    }
}</code></pre>
<p><strong>In the class, <em>PaimonDatabaseManager.createCatalog()</em> creates a <em>Catalog</em> instance backed by the local filesystem, using a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-common/1.4.1/org/apache/paimon/catalog/CatalogContext.html"><em>CatalogContext</em></a> to configure the warehouse location.</strong> Finally, <em>CatalogFactory.createCatalog()</em> uses the context to create the Paimon database.</p>
<p>Furthermore, the <em>PaimonDatabaseManager.createTable()</em> method builds a <em>Schema</em> for the metrics table and uses an <em>Identifier</em> to register it under the <em>metric_db</em> database. The table schema includes typed columns and a composite primary key.</p>
<p>Now, we&#8217;ll use the <em>PaimonDatabaseManager</em> class in a program to create a Paimon database table:</p>
<pre><code class="language-java">void whenCallCreateTable_thenTableCreated() throws Exception {
    catalog = PaimonDatabaseManager.createCatalog(WAREHOUSE_PATH);
    assertNotNull(catalog);
    
    tableId = PaimonDatabaseManager.createTable(catalog);
    assertNotNull(tableId);
    assertTrue(catalog.listTables("metric_db").contains("metrics"));
}</code></pre>
<p>In the program, we first call <em>PaimonDatabaseManager.createCatalog(WAREHOUSE_PATH)</em> to create a Paimon database catalog. The argument <em>WAREHOUSE_PATH</em> contains the path to a temporary directory created for the database. Next, we call <em>PaimonDatabaseManager.createTable()</em> to create a <em>metrics</em> table in the catalog. Finally, we invoke <em>Catalog.listTables()</em> to list all the tables in the <em>metric_db</em> database and confirm that it contains the <em>metrics</em> table<em>. </em></p>
<h2 id="bd-insert-records-in-table" data-id="insert-records-in-table">4. Insert Records in Table</h2>
<div class="bd-anchor" id="insert-records-in-table"></div>
<p>In this section, we&#8217;ll learn about the API to insert metric records into the Paimon <em>metric</em> table:</p>
<pre><code class="language-java">public class PaimonTableDataManager {
    public static void insert(Catalog catalog, Identifier tableId, List metrics) throws Exception {
        Table table = catalog.getTable(tableId);
        BatchWriteBuilder builder = table.newBatchWriteBuilder();
        BatchTableWrite write = builder.newWrite();
        metrics.forEach(metric -&gt; {
          try { 
              GenericRow row = createGenericRow(metric);
              write.write(row, 0);
          } catch (Exception e) {
              logger.error("Error writing metric", e);
          }
        });
        List messages = write.prepareCommit();
        BatchTableCommit commit = builder.newCommit();
        commit.commit(messages);
    }
}</code></pre>
<p>First, we retrieve the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/Table.html"><em>Table</em></a> object associated with the <em>tableId</em> from the <em>Catalog</em> object. Next, we get an instance of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/sink/BatchWriteBuilder.html"><em>BatchWriteBuilder</em></a> class by invoking <em>Table#newBatchWriteBuilder().</em> <strong>The builder object helps create a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/sink/BatchTableWrite.html"><em>BatchTableWrite</em></a> object that executes write operations on tables</strong>.</p>
<p>Further, we transform each metric record into a <em>GenericRow</em> object in the <em>createGenericRow()</em> method:</p>
<pre><code class="language-java">private static GenericRow createGenericRow(Metric metric) {
    GenericRow row = GenericRow.of(
      BinaryString.fromString(metric.getDeviceId()),
      BinaryString.fromString(metric.getMetricsName()),
      metric.getMetricsValue(),
      BinaryString.fromString(metric.getSource()),
      convertToTimestamp(metric.getCreateTime()),
      BinaryString.fromString(metric.getCreatedBy()),
      BinaryString.fromString(metric.getState())
    );
    return row;
}</code></pre>
<p>During this process, each column value is converted to its corresponding Paimon-compatible data type, such as <em>BinaryString</em> and <em>Timestamp</em>.</p>
<p>After creating the <em>GenericRow</em> object in the insert method, <em>BatchTableWrite.write()</em> writes each row into the <em>metrics</em> table. Finally, the data is committed in a single batch outside the loop.</p>
<p>Let&#8217;s run the <em>PaimonTableDataManager.insert()</em> to insert some records into the <em>metrics</em> table:</p>
<pre><code class="language-java">void whenCallInsertRecords_thenRecordsInserted() throws Exception {
    assertTrue(catalog.listTables("metric_db").contains("metrics"));
    PaimonTableDataManager.insert(catalog, tableId, getMetrics());
}</code></pre>
<p>First, with the assert method, we ensure that the <em>metric</em> table exists in the catalog. The arguments, <em>catalog</em>, and <em>tableId</em> hold the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/catalog/Catalog.html"><em>Catalog</em></a> and table <em>Identifier</em> objects that we retrieved and stored while creating the <em>metrics</em> table. The <em>getMetrics()</em> method could be a service fetching the metric data from a monitoring tool. Moreover, for our example, we retrieve the synthetic data from a CSV file <em>metrics.out</em>:</p>
<pre><code class="language-bash">device_id,metric_name,create_time,metric_value,source
dev_101,cpu_usage,2026-04-21 18:57:01,72.5,agent
dev_102,cpu_usage,2026-04-21 18:57:02,65.1,agent
dev_103,memory_usage,2026-04-21 18:57:03,58.3,agent
...</code></pre>
<p>However, real-world applications usually poll monitoring tools at regular intervals to fetch the latest metrics. Finally, we invoke the insert method to create new records into the metrics table.</p>
<h2 id="bd-query-records-from-table" data-id="query-records-from-table">5. Query Records From Table</h2>
<div class="bd-anchor" id="query-records-from-table"></div>
<p>After the raw metrics are collected, they must be fetched from the database and then processed to derive actionable analytics. The API supports filter pushdown and column projections, helping enhance performance. Additionally, the API supports both batch reads and streaming data for analytics and real-time use cases.</p>
<p>Let&#8217;s use the Paimon Java API to implement a batch read operation:</p>
<pre><code class="language-java">public static List&lt;Metric&gt; fetchMetricsBySourceAndDateRange(Catalog catalog, Identifier tableId,
      String source, String startDate, String endDate) throws Exception {
    Table table = catalog.getTable(tableId);
    RowType rowType = table.rowType();
    PredicateBuilder predicateBuilder = new PredicateBuilder(rowType);
    int[] projection = new int[] {0, 1, 2, 3, 4};
    Predicate sourcePredicate = predicateBuilder.equal(3, BinaryString.fromString(source));
    Predicate dateRangePredicate = predicateBuilder.between(
      4, convertToTimestamp(startDate), convertToTimestamp(endDate)
    );
    Predicate predicate = PredicateBuilder.and(sourcePredicate, dateRangePredicate);
    ReadBuilder readBuilder = table.newReadBuilder().withFilter(predicate).withProjection(projection);
    List&lt;Split&gt; splits = readBuilder.newScan().plan().splits();
    TableRead read = readBuilder.newRead().executeFilter();
    RecordReader&lt;InternalRow&gt; reader = read.createReader(splits);
    List&lt;Metric&gt; results = new ArrayList&lt;&gt;();
    reader.forEachRemaining(internalRow -&gt; {
        String deviceId = internalRow.getString(0).toString();
        String metricsName = internalRow.getString(1).toString();
        double metricsValue = internalRow.getDouble(2);
        String sourceValue = internalRow.getString(3).toString();
        Timestamp timestamp = internalRow.getTimestamp(4, 3);
        String createTime = ConvertTimestampToStr(timestamp); 
        Metric metric = new Metric(deviceId, metricsName, metricsValue, sourceValue, createTime, null);
        logger.info("Fetched Metric: {}", metric);
        results.add(metric);
    });
    return results;
}</code></pre>
<p><strong>The <em>fetchMetricsBySourceAndDateRange()</em> method queries the <em>metrics</em> tables to fetch metric data originating from a specific source within a given date range</strong>. In the method, the <em>PredicateBuilder</em> helps create <em>Predicate</em> objects with filter conditions on the <em>source</em> and <em>create_time</em> columns. The <em>Table#newReadBuilder()</em> creates a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/source/ReadBuilder.html"><em>ReadBuilder</em></a> object, and subsequently, <em>ReadBuilder#withFilter()</em> sets the necessary predicates. Additionally, we define a <em>projection</em> array to fetch only the required columns.</p>
<p>Moving on, the builder creates data splits that can be distributed across multiple threads or processes, enabling faster execution. The splits are comparable to partitions or data files corresponding to a table. Next, <em>ReadBuilder#newRead()</em> creates a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/source/TableRead.html"><em>TableRead</em></a> object on which we invoke the <em>executeFilter()</em> method. After this, <em>TableRead#createReader()</em> creates a <em>RecordReader</em> for the generated splits. Multiple readers can process different splits concurrently, enabling scalable and efficient parallel reads. Finally, we call <em>RecordReader#forEachRemaining()</em> to iterate through the rows and fetch the metric attributes.</p>
<p>Now, let&#8217;s run the program to fetch a metric from a certain source and within a date range:</p>
<pre><code class="language-java">void whenCallReadRecords_thenRecordsRead() throws Exception {
    List&lt;Metric&gt; metrics = PaimonTableDataManager.fetchMetricsBySourceAndDateRange(
      catalog, tableId, "collector", "2026-04-21 18:58:19", "2026-04-21 18:58:30");
    assertFalse(metrics.isEmpty());
    assertTrue(metrics.size() == 4);
}</code></pre>
<p>The program fetched four metric rows with the source <em>collector</em> and created between <em>2026-04-21 18:58:19</em> and <em>2026-04-21 18:58:30</em>.</p>
<h2 id="bd-update-table-records" data-id="update-table-records">6. Update Table Records</h2>
<div class="bd-anchor" id="update-table-records"></div>
<p>There&#8217;s no particular Java API for updating records in a Paimon Table. However, <strong>when we perform a write operation, just like in the case of performing an insert on an existing record with the primary key, instead of a new row, the existing row gets updated</strong>.</p>
<p>To understand the update operation, we&#8217;ve defined the method in the <em>PaimonTableDataManager </em>class:</p>
<pre><code class="language-java">public static void updateMetricStateByDeviceIdMetricNameAndCreatedDate(Catalog catalog,
      Identifier tableId, String deviceId, String metricName, String newState, String createdDate)
    throws Exception {
    Metric metric = fetchMetricByDeviceIdMetricNameAndCreatedDate(
      catalog, tableId, deviceId, metricName, createdDate
    );
    Table table = catalog.getTable(tableId);
    BatchWriteBuilder builder = table.newBatchWriteBuilder();
    BatchTableWrite write = builder.newWrite();
    metric.setState(newState);
    write.write(createGenericRow(metric), 0);
    List&lt;CommitMessage&gt; messages = write.prepareCommit();
    BatchTableCommit commit = builder.newCommit();
    commit.commit(messages);
}</code></pre>
<p>First, we fetch a metric record filtered by the primary key, composed of metric attributes such as device ID, metric name, and created date, by invoking the <em>fetchMetricByDeviceIdMetricNameAndCreatedDate()</em> method<em>.</em> The method is in the same line as the method described in the previous section, which queries records from the metric table.</p>
<p>Then, we update the <em>Metric#state</em> attribute with a new state. The remaining steps are largely similar to the insert method described earlier. However, instead of creating a new record, the existing metric&#8217;s state is updated with a new state.</p>
<p>Let&#8217;s execute <em>updateMetricStateByDeviceIdMetricNameAndCreatedDate()</em>:</p>
<pre><code class="language-java">void whenCallUpdateRecord_thenRecordUpdated() throws Exception {
    Metric metric = PaimonTableDataManager
      .fetchMetricByDeviceIdMetricNameAndCreatedDate(
        catalog, tableId, 
        "dev_137", "cpu_usage",
        "2026-04-21 18:58:27"
    );
    assertNotNull(metric);
    assertEquals("active", metric.getState());
    PaimonTableDataManager.updateMetricStateByDeviceIdMetricNameAndCreatedDate(
        catalog, tableId, "dev_137",
            "cpu_usage", "inactive", 
            "2026-04-21 18:58:27"
    );
    metric = PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate(
        catalog, tableId, "dev_137", 
        "cpu_usage", "2026-04-21 18:58:27"
    );
    assertNotNull(metric);
    assertEquals("inactive", metric.getState());
}</code></pre>
<p>We fetched a metric record by its composite primary key and updated its state to <em>inactive</em>. Later, we again fetched the record and confirmed that the state has been updated to <em>inactive</em>.</p>
<h2 id="bd-delete-table-records" data-id="delete-table-records">7. Delete Table Records</h2>
<div class="bd-anchor" id="delete-table-records"></div>
<p>Moving on, Paimon Java API&#8217;s also supports the delete operation:</p>
<pre><code class="language-java">public static void deleteRecordsByDeviceIdMetricNameAndCreatedDate(Catalog catalog, 
    Identifier tableId, String deviceId, 
    String metricsName, String createdDate) throws Exception {
    Metric metric = PaimonTableDataManager
     .fetchMetricByDeviceIdMetricNameAndCreatedDate(
       catalog, tableId, deviceId, metricsName, createdDate
    );
    Table table = catalog.getTable(tableId);
    BatchWriteBuilder builder = table.newBatchWriteBuilder();
    BatchTableWrite write = builder.newWrite();
    GenericRow deleteRow = createGenericRow(metric);
    deleteRow.setRowKind(RowKind.DELETE);
    write.write(deleteRow, 0);
    List&lt;CommitMessage&gt; messages = write.prepareCommit();
    builder.newCommit().commit(messages);
}</code></pre>
<p>First, we fetched a metric record by calling <em>PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate(). </em>The next set of steps is almost similar to the update method, except that we don&#8217;t modify any field in the metric record. Later, <strong>before invoking <em>BatchTableWrite#write()</em>, we flag the operation as delete by calling the method <em>BatchTableWrite#setRowKind(RowKind.DELETE)</em></strong>.</p>
<p>Let&#8217;s verify the <em>deleteRecordsByDeviceIdMetricNameAndCreatedDate()</em> method by executing it:</p>
<pre><code class="language-java">void whenCallDeleteRecord_thenRecordDeleted() throws Exception {
    Metric metric = PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate(
      catalog, tableId, 
      "dev_136", "disk_io", "2026-04-21 18:58:26"
    );
    assertNotNull(metric);
    PaimonTableDataManager.deleteRecordsByDeviceIdMetricNameAndCreatedDate(
      catalog, tableId, 
      "dev_136", "disk_io", "2026-04-21 18:58:26"
    );
    metric = PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate(
      catalog, tableId, "dev_136", "disk_io", "2026-04-21 18:58:26"
    );
    assertTrue(metric == null);
}</code></pre>
<p>First, we fetch a metric record corresponding to a device by invoking <em>PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate()</em>. After ensuring the record isn&#8217;t <em>null</em>, we invoke the delete method. Finally, we query the metric table again and confirm that no such record is present in the Paimon DB.</p>
<h2 id="bd-conclusion" data-id="conclusion">8. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored the core Java APIs for managing a Paimon database, including creating tables, performing batch insertions, updates, deletes, and executing filtered data queries. <strong>We used the local file system to create the Paimon DB, but the APIs are standard and effective irrespective of the chosen storage layer</strong>.</p>
<p>As usual, the source code can be found <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/persistence-modules/apache-paimon">over on GitHub.</a></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-apache-paimon-guide">A Guide to Apache Paimon Java API</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958675751/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958675751/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958675751/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-apache-paimon-guide#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-apache-paimon-guide/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/~/958675751/0/baeldung~A-Guide-to-Apache-Paimon-Java-API/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-kafka-commitfailedexception</feedburner:origLink>
		<title>Understanding and Avoiding CommitFailedException in Kafka</title>
		<link>https://feeds.feedblitz.com/~/958534139/0/baeldung~Understanding-and-Avoiding-CommitFailedException-in-Kafka</link>
					<comments>https://feeds.feedblitz.com/~/958534139/0/baeldung~Understanding-and-Avoiding-CommitFailedException-in-Kafka#respond</comments>
		
		<dc:creator><![CDATA[Saikat Chakraborty]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:53:49 +0000</pubDate>
				<category><![CDATA[Data]]></category>
		<category><![CDATA[Kafka]]></category>
		<category><![CDATA[Messaging]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-kafka-commitfailedexception</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="A robot with a vacuum hand that is pulling in various bits of data and outputing graphs and charts with its other hand. On the far left are the words &quot;Data on Baeldung&quot;." style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn about what causes the CommitFailedException when working with Kafka in Java and some approaches to help avoid it occurring.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958534139/0/baeldung~Understanding-and-Avoiding-CommitFailedException-in-Kafka">Understanding and Avoiding CommitFailedException in Kafka</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958534139/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-05-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958534139/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-kafka-commitfailedexception#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-kafka-commitfailedexception/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/Data-Featured-Image-05-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="A robot with a vacuum hand that is pulling in various bits of data and outputing graphs and charts with its other hand. On the far left are the words &quot;Data on Baeldung&quot;." style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05.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>Kafka provides a high degree of flexibility in committing offsets for processed messages in the consumer. It includes synchronous, asynchronous, and auto-commit options.</p>
<p>While this reduces the complexity of the commit process, unexpected errors can still occur.</p>
<p>In this tutorial, we’ll learn about a commit offset-related failure in a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-kafka">Kafka</a> consumer application. We&#8217;ll debug the root cause of the resulting <em>CommitFailedException</em> and implement a few solutions to avoid it where possible.</p>
<h2 id="bd-implementing-a-consumer-service-to-demonstrate-the-issue" data-id="implementing-a-consumer-service-to-demonstrate-the-issue">2. Implementing a Consumer Service to Demonstrate the Issue</h2>
<div class="bd-anchor" id="implementing-a-consumer-service-to-demonstrate-the-issue"></div>
<p>Let&#8217;s build a simple <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/kafka-create-listener-consumer-api">consumer</a> application that processes records sequentially.</p>
<p>We&#8217;ll implement the <em>consume</em> method in the <em>KafkaConsumerService </em>class:</p>
<pre><code class="language-java">public class KafkaConsumerService {
    private final KafkaConsumer&lt;String, String&gt; consumer;
    private final AtomicBoolean running = new AtomicBoolean(true);
    public void consume() {
        try {
            while (running.get()) {
                ConsumerRecords&lt;String, String&gt; records = consumer.poll(Duration.ofMillis(100));
                if (records.isEmpty()) {
                    continue;
                }
                records.forEach(this::simulateDBCall);
                consumer.commitSync();
            }
        } catch (WakeupException ex) {
            if (running.get()) {
                log.error("Error in the Kafka Consumer with exception", ex);
                throw ex;
            }
        } finally {
            consumer.close();
        }
    }
}</code></pre>
<p>In the code above, we process records sequentially and then commit the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/kafka-consumer-offset">offset</a>.</p>
<p>To simulate a database call, we&#8217;ll add a delay in the above <em>simulateDBCall </em>method:</p>
<pre><code class="language-java">private void simulateDBCall(ConsumerRecord&lt;String, String&gt; record) {
    try {
        log.info("Simulating a DB call - record key {} value {}", record.key(), record.value());
        Thread.sleep(150L);
    } catch (InterruptedException ex) {
        Thread.currentThread()
          .interrupt();
        throw new RuntimeException(ex);
    }
}</code></pre>
<p>The delay will cause the consumer to wait around 150 ms before the next record.</p>
<h2 id="bd-testing-the-service" data-id="testing-the-service">3. Testing the Service</h2>
<div class="bd-anchor" id="testing-the-service"></div>
<p>Next, let&#8217;s test the consumer service. Note that we use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/docker-test-containers">Testcontainers</a> in our test harness to provide a local Kafka broker for testing; full details are available in the supporting GitHub repository linked at the end of this article.</p>
<p>First, we&#8217;ll include the consumer-related configs in the test class:</p>
<pre><code class="language-java">private static Properties getConsumerConfig() {
    Properties consumerProperties = new Properties();
    consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_CONTAINER.getBootstrapServers());
    consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "consumer-app");
    consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
    consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
    consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
    consumerProperties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
    consumerProperties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1);
    consumerProperties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 50);
    
    return consumerProperties;
}</code></pre>
<p>In the config above, we&#8217;re polling for one record with a maximum poll interval of 50 ms and manually committing the offset.</p>
<p>We&#8217;ll implement a test case by producing, consuming a message, and verifying the offset:</p>
<pre><code class="language-java">@Test
void givenProducerMessageIsSent_whenConsumerIsRunning_thenConsumerOffsetIsCommitted() {
    KafkaConsumerService kafkaConsumerService = new KafkaConsumerService(getConsumerConfig(), "test-topic");
    Thread th = new Thread(kafkaConsumerService::consume);
    th.start();
    try (KafkaProducer&lt;String, String&gt; producer = new KafkaProducer&lt;&gt;(getProducerConfig())) {
        producer.send(new ProducerRecord&lt;&gt;("test-topic", "x1", "test"));
        producer.flush();
    }
    Awaitility.await()
      .atMost(30, TimeUnit.SECONDS)
      .pollInterval(2, TimeUnit.SECONDS)
      .untilAsserted(() -&gt; {
          TopicPartition topicPartition = new TopicPartition("test-topic", 0);
          Map&lt;TopicPartition, OffsetAndMetadata&gt; committedOffsets;
          try (AdminClient adminClient = AdminClient.create(getAdminProps())) {
              ListConsumerGroupOffsetsResult result = adminClient.listConsumerGroupOffsets("consumer-app");
              committedOffsets = result.partitionsToOffsetAndMetadata()
                .get();
          }
          assertNotNull(committedOffsets);
          assertNotNull(committedOffsets.get(topicPartition));
          assertEquals(1L, committedOffsets.get(topicPartition)
            .offset());
      });
    kafkaConsumerService.shutdown();
}</code></pre>
<p>Instead of offset committed, we&#8217;ll see the following error log with the test case also failing:</p>
<pre><code class="language-java">org.apache.kafka.clients.consumer.CommitFailedException: Offset commit cannot be completed since the consumer is not part of an active group for auto partition assignment; it is likely that the consumer was kicked out of the group.
	at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.sendOffsetCommitRequest(ConsumerCoordinator.java:1280)
	...
	at org.apache.kafka.clients.consumer.KafkaConsumer.commitSync(KafkaConsumer.java:918)
	at com.baeldung.kafka.commitfailure.sequential.KafkaConsumerService.consume(KafkaConsumerService.java:34)
</code></pre>
<p>From the log above, it&#8217;s obvious that the <em>CommitFailedException</em> was thrown while committing the offset in the <em>consume</em> method. Also, it says that the consumer is no longer part of the group. Kafka gives some hints in the class <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://kafka.apache.org/39/javadoc/org/apache/kafka/clients/consumer/CommitFailedException.html">documentation</a> on the possible reason.</p>
<h2 id="bd-root-cause-of-the-commitfailedexception" data-id="root-cause-of-the-commitfailedexception">4. Root Cause of the <em>CommitFailedException</em></h2>
<div class="bd-anchor" id="root-cause-of-the-commitfailedexception"></div>
<p>We can monitor and debug the root cause with multiple options, including container logs, CLI, and Kafka UI tools.</p>
<p>First, we&#8217;ll check the Kafka container logs for the error:</p>
<pre><code class="language-bash">[2026-06-08 16:53:05,349] INFO [GroupCoordinator 1]: Preparing to rebalance group consumer-app in state PreparingRebalance with old generation 1 (__consumer_offsets-0) (reason: Removing member consumer-consumer-app-1-3792090b-be55-4e19-bdbd-a3c66b968168 on LeaveGroup; client reason: consumer poll timeout has expired.)
...
[2026-06-08 16:53:05,354] INFO [GroupCoordinator 1]: Member MemberMetadata(memberId=consumer-consumer-app-1-3792090b-be55-4e19-bdbd-a3c66b968168, groupInstanceId=None, clientId=consumer-consumer-app-1, clientHost=/172.17.0.1, sessionTimeoutMs=45000, rebalanceTimeoutMs=50, supportedProtocols=List(range, cooperative-sticky)) has left group consumer-app through explicit `LeaveGroup`; client reason: consumer poll timeout has expired...</code></pre>
<p>We&#8217;ll also confirm the consumer group&#8217;s state using the <em>kafka-consumer-groups</em> command:</p>
<pre><code class="language-bash">sh-4.4$ /usr/bin/kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group consumer-app --state
Consumer group 'consumer-app' has no active members.
GROUP                     COORDINATOR (ID)          ASSIGNMENT-STRATEGY  STATE           #MEMBERS
consumer-app              8db993586dab:9092  (1)                         Empty           0</code></pre>
<p>Based on the above logs and status, we can see that <strong>the consumer was removed due to a client-specific error: <em>consumer poll timeout has expired</em>.</strong></p>
<p>Kafka has multiple ways to check if the consumer is healthy and running. This includes a background heartbeat check, which should run before the session timeout.</p>
<p>Additionally,<strong> Kafka expects the consumer to poll once within the specified <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://kafka.apache.org/39/configuration/consumer-configs/#consumerconfigs_max.poll.interval.ms">max poll interval time period</a>.</strong> If the consumer does not poll for any reason, <strong>it will be considered dead, and the partition will be reassigned to another consumer</strong>.</p>
<p>By further debugging and re-verifying the application logs, we observe that the record processing was clearly taking more time, and <strong>the consumer could not call the poll method within the specified <em>MAX_POLL_INTERVAL_MS_CONFIG</em> time period</strong>. Consequently, the consumer was removed by the Kafka group coordinator.</p>
<p>So, any further attempt to commit an offset failed with the error. It makes sense as the consumer is no longer the partition owner, and a commit can corrupt the offset state.</p>
<p>We&#8217;ll explore a few practical ways to prevent the error.</p>
<h2 id="bd-approaches-to-avoid-the-problem" data-id="approaches-to-avoid-the-problem">5. Approaches to Avoid the Problem</h2>
<div class="bd-anchor" id="approaches-to-avoid-the-problem"></div>
<p>With the consumer polling understanding, we&#8217;ll try to solve it by analyzing the consumer code and configuration.</p>
<p><strong>We may consider enabling auto-commit config</strong>, but that brings a possibility of data loss and irregular commit timing. Moreover, this approach does not ensure at-least-once processing.</p>
<p>Instead, we&#8217;ll explore some relevant manual commit solutions.</p>
<h3 id="bd-1-tuning-the-poll-duration-and-batch-size" data-id="1-tuning-the-poll-duration-and-batch-size">5.1. Tuning the Poll Duration and Batch Size</h3>
<div class="bd-anchor" id="1-tuning-the-poll-duration-and-batch-size"></div>
<p>By debugging the code, we found that the actual processing time was significantly longer than the specified maximum poll interval.</p>
<p>We&#8217;ll increase the maximum poll interval config:</p>
<pre><code class="language-java">consumerProperties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1);
consumerProperties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 300);</code></pre>
<p>We can also fine-tune the batch size according to our batch processing timing and throughput requirements.</p>
<p>Let&#8217;s test the updated consumer and confirm that no <em>CommitFailedException</em> is thrown.</p>
<p>We should note that it’s <strong>good to set the poll interval to about 3 times the batch processing time</strong> to ensure a safe buffer.</p>
<p>While changing the poll interval works, we may still face the issue in more complex setups, such as processing involving database transactions or network calls.</p>
<h3 id="bd-2-async-processing" data-id="2-async-processing">5.2. Async Processing</h3>
<div class="bd-anchor" id="2-async-processing"></div>
<p>If we&#8217;re processing records in the same thread that performs I/O operations, the above solution reduces throughput and increases latency.</p>
<p>In a production environment, <strong>we can&#8217;t guarantee a predictable database execution time, so configuring just the polling duration might be insufficient</strong>.</p>
<p>We can consider asynchronous processing of the records, so the consumer thread blocks for much shorter periods.</p>
<p>In Kafka, this can be done by processing the records asynchronously with virtual threads.</p>
<p>First, we&#8217;ll include a worker thread pool and a per-partition offset tracking map in the consumer service:</p>
<pre><code class="language-java">public class KafkaConsumerService {
    private final KafkaConsumer&lt;String, String&gt; consumer;
    private final AtomicBoolean running = new AtomicBoolean(true);
    private final ExecutorService workers;
    private final Map&lt;TopicPartition, AtomicLong&gt; committableOffsets = new ConcurrentHashMap&lt;&gt;();
    public KafkaConsumerService(Properties consumerProps, String topic) {
        this.consumer = new KafkaConsumer&lt;&gt;(consumerProps);
        consumer.subscribe(List.of(topic));
        workers = Executors.newVirtualThreadPerTaskExecutor();
    }
}</code></pre>
<p>Next, we&#8217;ll update the <em>consume</em> method with async processing using the worker threads:</p>
<pre><code class="language-java">public void consume() {
    try {
        while (running.get()) {
            ConsumerRecords&lt;String, String&gt; records = consumer.poll(Duration.ofMillis(100));
            if (records.isEmpty()) {
                continue;
            }
            List&lt;CompletableFuture&lt;Void&gt;&gt; futures = processAsync(records);
            try {
                CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new))
                  .orTimeout(700L, TimeUnit.MILLISECONDS)
                  .join();
            } catch (CompletionException ex) {
                log.error("Batch processing timed out or failed", ex);
            }
            commitOffsets();
        }
    } catch (WakeupException ex) {
        if (running.get()) {
            log.error("Error in the Kafka Consumer with exception", ex);
            throw ex;
        }
    } finally {
        commitOffsets();
        consumer.close();
    }
}</code></pre>
<p>We&#8217;ll implement the <em>processAsync</em> method to asynchronously process the records:</p>
<pre><code class="language-java">private List&lt;CompletableFuture&lt;Void&gt;&gt; processAsync(ConsumerRecords&lt;String, String&gt; records) {
    return StreamSupport.stream(records.spliterator(), false)
      .map(record -&gt; CompletableFuture.runAsync(() -&gt; simulateDBUpdate(record), workers)
        .whenComplete((ignored, ex) -&gt; {
            if (ex == null) {
                markComplete(record);
            } else {
                log.error("Failed offset and send to DLQ {} {} {}", 
                  record.offset(), record.key(), ex.getMessage());
            }
        })
        .exceptionally(ex -&gt; null))
      .toList();
}</code></pre>
<p>In the code above, the consumer waits for the workers to complete processing, then commits the processed offset. If any record fails during processing, we log the failure message and can send it to a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/kafka-spring-dead-letter-queue">Dead Letter Queue (DLQ)</a> topic.</p>
<p>Next, let&#8217;s implement the <em>markComplete</em> method to update the processed record offset into the<i> committableOffsets</i> map:</p>
<pre><code class="language-java">private void markComplete(ConsumerRecord&lt;String, String&gt; record) {
    TopicPartition tp = new TopicPartition(record.topic(), record.partition());
    committableOffsets.computeIfAbsent(tp, k -&gt; new AtomicLong(-1L))
      .accumulateAndGet(record.offset() + 1L, Math::max);
}</code></pre>
<p>Finally, we&#8217;ll implement the <em>commitOffsets</em> method to commit the processed offset:</p>
<pre><code class="language-java">private void commitOffsets() {
    Map&lt;TopicPartition, OffsetAndMetadata&gt; toCommit = new HashMap&lt;&gt;();
    committableOffsets.forEach((tp, atomicOffset) -&gt; {
        long val = atomicOffset.get();
        if (val != -1L) {
            toCommit.put(tp, new OffsetAndMetadata(val));
        }
    });
    if (toCommit.isEmpty()) {
        return;
    }
    consumer.commitSync(toCommit);
    toCommit.forEach((tp, meta) -&gt; {
        AtomicLong ref = committableOffsets.get(tp);
        if (ref != null) {
            ref.compareAndSet(meta.offset(), -1L);
        }
    });
}</code></pre>
<p>In the method above, we build the committed offset snapshot map for all the processed records and commit it. We also atomically mark the committed offset as completed.</p>
<p>Now, let&#8217;s configure the consumer with an increased batch size and max poll interval in the test class:</p>
<pre><code class="language-java">consumerProperties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 30);
consumerProperties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 1000);</code></pre>
<p>Finally, we&#8217;ll implement a test case by producing and consuming multiple messages and verifying the offset:</p>
<pre><code class="language-java">@Test
void givenProducerMessagesAreSent_whenConsumerIsRunning_thenConsumerOffsetIsCommitted() {
    KafkaConsumerService kafkaConsumerService = new KafkaConsumerService(getConsumerConfig(), "test-topic");
    Thread th = new Thread(kafkaConsumerService::consume);
    th.start();
    try (KafkaProducer&lt;String, String&gt; producer = new KafkaProducer&lt;&gt;(getProducerConfig())) {
        for (int num = 0; num &lt; 100; num++) {
            producer.send(new ProducerRecord&lt;&gt;("test-topic", "x1" + num, "test" + num));
        }
        producer.flush();
    }
    Awaitility.await()
      .atMost(30, TimeUnit.SECONDS)
      .pollInterval(2, TimeUnit.SECONDS)
      .untilAsserted(() -&gt; {
          TopicPartition topicPartition = new TopicPartition("test-topic", 0);
          Map&lt;TopicPartition, OffsetAndMetadata&gt; committedOffsets;
          try (AdminClient adminClient = AdminClient.create(getAdminProps())) {
              ListConsumerGroupOffsetsResult result = adminClient.listConsumerGroupOffsets("consumer-app");
              committedOffsets = result.partitionsToOffsetAndMetadata()
                .get();
          }
          assertNotNull(committedOffsets);
          assertNotNull(committedOffsets.get(topicPartition));
          assertEquals(100L, committedOffsets.get(topicPartition)
            .offset());
      });
    kafkaConsumerService.shutdown();
}</code></pre>
<p>By running the test above, we have successfully verified that the committed offset matches the total number of consumed messages.</p>
<h3 id="bd-3-implementing-a-consumerrebalancelistener" data-id="3-implementing-a-consumerrebalancelistener">5.3. Implementing a <em>ConsumerRebalanceListener</em></h3>
<div class="bd-anchor" id="3-implementing-a-consumerrebalancelistener"></div>
<p>We should note that <strong>it&#8217;s always good practice to implement <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-kafka-reset-consumer-offset#1-implement-the-consumer-rebalance-listener"><em>ConsumerRebalanceListener</em></a> in the consumer to commit any in-flight offset and clean any data</strong>.</p>
<p>Let&#8217;s implement the <em>ConsumerRebalanceListener</em> in the <em>KafkaConsumer</em> object in the <em>KafkaConsumerService</em> class:</p>
<pre><code class="language-java">consumer.subscribe(List.of(topic), new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection&lt;TopicPartition&gt; partitions) {
        try {
            commitOffsets(partitions);
        } catch (Exception ex) {
            log.error("Commit failed during rebalance", ex);
        } finally {
            partitions.forEach(committableOffsets::remove);
        }
    }
    @Override
    public void onPartitionsAssigned(Collection&lt;TopicPartition&gt; partitions) {
        partitions.forEach(committableOffsets::remove);
    }
});</code></pre>
<p>We&#8217;ll also need to implement the <em>commitOffsets</em> method to commit offsets for the specific partitions:</p>
<pre><code class="language-java">private void commitOffsets(Collection&lt;TopicPartition&gt; partitions) {
    Map&lt;TopicPartition, OffsetAndMetadata&gt; toCommit = new HashMap&lt;&gt;();
    partitions.forEach(tp -&gt; {
        AtomicLong ref = committableOffsets.get(tp);
        if (ref != null) {
            long val = ref.get();
            if (val != -1L) {
                toCommit.put(tp, new OffsetAndMetadata(val));
            }
        }
    });
    if (toCommit.isEmpty()) {
        return;
    }
    consumer.commitSync(toCommit);
}</code></pre>
<p>In the code above, we&#8217;re committing the offset by creating a snapshot map from the specific partition&#8217;s offset.</p>
<p>Now, let&#8217;s run the previous section&#8217;s test method to verify the offset for the updated consumer implementation:</p>
<pre><code class="language-bash">13:01:21.571 [virtual-158] INFO  c.b.k.c.f.async.KafkaConsumerService - Simulating a db call - record key x199 value test99
</code></pre>
<p>From the test above, we confirm that all messages were processed and committed.</p>
<h3 id="bd-4-best-practices" data-id="4-best-practices">5.4. Best Practices</h3>
<div class="bd-anchor" id="4-best-practices"></div>
<p>While we cannot prevent exceptions entirely, we can protect the system from any reprocessing duplication, data loss, or silent message drops.</p>
<p>We should implement idempotency on both the producer and the consumer, validate database constraints, and ensure side-effect-free reprocessing. Additionally, we should send the failed messages to a retry or DLQ topic.</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 one of the common offset commit-related exceptions in the Consumer application. We explored why the resulting <em>CommitFailedException</em> happens and how to prevent it. We also implemented a preventive solution by tuning the maximum poll interval and switching to asynchronous processing logic.</p>
<p>As always, the example code can be found <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/apache-kafka-4">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-kafka-commitfailedexception">Understanding and Avoiding CommitFailedException in Kafka</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958534139/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958534139/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-05-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958534139/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-kafka-commitfailedexception#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-kafka-commitfailedexception/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/~/958534139/0/baeldung~Understanding-and-Avoiding-CommitFailedException-in-Kafka/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/hibernate-query-regexp</feedburner:origLink>
		<title>Regular Expression Support in HQL</title>
		<link>https://feeds.feedblitz.com/~/958534142/0/baeldung~Regular-Expression-Support-in-HQL</link>
					<comments>https://feeds.feedblitz.com/~/958534142/0/baeldung~Regular-Expression-Support-in-HQL#respond</comments>
		
		<dc:creator><![CDATA[Kai Yuan]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:48:47 +0000</pubDate>
				<category><![CDATA[JPA]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[HQL]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/hibernate-query-regexp</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-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 HQL to query a string column against a regular expression.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958534142/0/baeldung~Regular-Expression-Support-in-HQL">Regular Expression Support in HQL</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958534142/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-query-regexp#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-query-regexp/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/Persistence-Featured-Image-02-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/Persistence-Featured-Image-02-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02.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>Hibernate 7.2 introduces first-class regular expression support in HQL via the <em>like regexp</em> operator. Before this addition, matching a string column against a non-trivial pattern meant writing a native query. This new operator delegates regex matching to the database engine, avoiding native queries and post-filtering in Java.</p>
<p>In this tutorial, we&#8217;ll walk through a small <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-start">Spring Boot</a> example that demonstrates <em>like regexp</em>. We&#8217;ll look under the hood to understand how Hibernate translates it to SQL and what happens when the underlying database doesn&#8217;t support regex.</p>
<h2 id="bd-preparing-a-simple-spring-boot-example" data-id="preparing-a-simple-spring-boot-example">2. Preparing a Simple Spring Boot Example</h2>
<div class="bd-anchor" id="preparing-a-simple-spring-boot-example"></div>
<p>Before we can demonstrate the <em>like regexp</em> operator, we need a tiny project to run queries against. We&#8217;ll set up the dependencies, an entity, some seed data, the application properties, and a Spring Data repository. For simplicity, we&#8217;ll not show all the code here.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">2.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p>Our project uses Spring Boot 3.3 as its parent. Spring Boot 3.3&#8217;s BOM pins Hibernate to 6.5, so we override the version and a couple of its transitive dependencies that Hibernate 7 requires newer versions of:</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.2.Final&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.jboss.logging&lt;/groupId&gt;
    &lt;artifactId&gt;jboss-logging&lt;/artifactId&gt;
    &lt;version&gt;3.6.1.Final&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;jakarta.persistence&lt;/groupId&gt;
    &lt;artifactId&gt;jakarta.persistence-api&lt;/artifactId&gt;
    &lt;version&gt;3.2.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>Beyond that, we pull in the usual <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/the-persistence-layer-with-spring-data-jpa"><em>spring-boot-starter-data-jpa</em></a> and the H2 driver, as we&#8217;ll use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-h2-database">H2 in-memory database</a> to keep the demo self-contained.</p>
<h3 id="bd-2-the-entity" data-id="2-the-entity">2.2. The Entity</h3>
<div class="bd-anchor" id="2-the-entity"></div>
<p>We&#8217;ll keep things minimal with a single-column entity:</p>
<pre><code class="language-java">@Entity
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String content;
    // constructors, getters ...
}
</code></pre>
<h3 id="bd-3-seed-data" data-id="3-seed-data">2.3. Seed Data</h3>
<div class="bd-anchor" id="3-seed-data"></div>
<p>We load test data through Spring&#8217;s <em>data.sql</em> mechanism. The values are short phrases designed to exercise regex patterns:</p>
<pre><code class="language-sql">INSERT INTO message (content) VALUES ('quick brown fox jumps over');
INSERT INTO message (content) VALUES ('hello world from spring boot');
INSERT INTO message (content) VALUES ('Order 1234 shipped on Monday');
INSERT INTO message (content) VALUES ('Please call 555 today');
INSERT INTO message (content) VALUES ('ERROR connection refused remotely');
INSERT INTO message (content) VALUES ('warning low disk space detected');
</code></pre>
<h3 id="bd-4-application-properties" data-id="4-application-properties">2.4. Application Properties</h3>
<div class="bd-anchor" id="4-application-properties"></div>
<p>The test profile (<em>application-test.yaml</em>) wires up H2 and asks Spring to run <em>data.sql</em> after Hibernate creates the schema:</p>
<pre><code>spring:
  datasource:
    url: jdbc:h2:mem:testdb
    ...
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: create-drop
    defer-datasource-initialization: true
    show-sql: true
    ...
  sql:
    init:
      mode: always
</code></pre>
<p><strong>The <em>defer-datasource-initialization: true</em> setting ensures that <em>data.sql</em> runs after the schema is created. </strong>It&#8217;s also worth mentioning that we enabled <em>show-sql</em> option to log translated SQL. This helps us understand how the <em>like regexp</em> operator works. We&#8217;ll discuss it later.</p>
<h3 id="bd-5-the-repository" data-id="5-the-repository">2.5. The Repository</h3>
<div class="bd-anchor" id="5-the-repository"></div>
<p>The repository is the part we&#8217;ll actually be exercising. It declares a pair of HQL queries using the new <em>like regexp</em>/<em>not like regexp</em> operators:</p>
<pre><code class="language-java">public interface MessageRepository extends JpaRepository&lt;Message, Long&gt; {
    @Query("select m from Message m where m.content like regexp :pattern")
    List&lt;Message&gt; findByContentMatchingRegex(@Param("pattern") String pattern);
    @Query("select m from Message m where m.content not like regexp :pattern")
    List&lt;Message&gt; findByContentNotMatchingRegex(@Param("pattern") String pattern);
}
</code></pre>
<p>As always, we&#8217;ll use unit tests to demonstrate how each operator behaves.</p>
<h2 id="bd-demonstration" data-id="demonstration">3. Demonstration</h2>
<div class="bd-anchor" id="demonstration"></div>
<p>In our defined repository methods, the <em>pattern</em> parameter accepts a full regex pattern. To find every message that contains at least one digit, we use .*<em>\d+.*</em>:</p>
<pre><code class="language-java">@Test
void givenContainsDigitRegex_whenLikeRegexp_thenReturnsMatching() {
    List&lt;Message&gt; results = repository.findByContentMatchingRegex(".*\\d+.*");
    assertThat(results)
      .extracting(Message::getContent)
      .containsExactlyInAnyOrder(
        "Order 1234 shipped on Monday",
        "Please call 555 today");
}
</code></pre>
<p>Similarly, if we pass the same regex to the <em>findByContentNotMatchingRegex()</em> method, we&#8217;ll get all rows that don&#8217;t contain any digits.</p>
<pre><code class="language-java">@Test
void givenContainsDigitRegex_whenNotLikeRegexp_thenReturnsNonMatching() {
    List&lt;Message&gt; results = repository.findByContentNotMatchingRegex(".*\\d+.*");
    assertThat(results)
      .extracting(Message::getContent)
      .containsExactlyInAnyOrder(
        "quick brown fox jumps over",
        "hello world from spring boot",
        "ERROR connection refused remotely",
        "warning low disk space detected");
}
</code></pre>
<p>If we run these two tests, they will pass. The same pattern parameter drives both variants, so we don&#8217;t need a separate regex for each side of the predicate. Next, let&#8217;s understand how Hibernate&#8217;s like regexp operator works under the hood.</p>
<h2 id="bd-under-the-hood" data-id="under-the-hood">5. Under The Hood</h2>
<div class="bd-anchor" id="under-the-hood"></div>
<p>Having seen the operator in action, let&#8217;s look at how Hibernate actually implements it and where the portability boundaries lie.</p>
<h3 id="bd-1-how-like-regexp-is-translated" data-id="1-how-like-regexp-is-translated">5.1. How <em>like regexp</em> Is Translated</h3>
<div class="bd-anchor" id="1-how-like-regexp-is-translated"></div>
<p>When Hibernate parses <em>m.content like regexp :pattern</em>, it doesn&#8217;t translate to a <em>LIKE</em> statement at all. Instead, <strong>it routes the predicate through a Semantic Query Model (SQM) function that each dialect registers under the name <em>regexp_like</em></strong>. The default <em>H2Dialect</em> maps <em>regexp_like(x, y)</em> to H2&#8217;s <em>regexp_like(x, y)</em> SQL function, so if we check the log output of the example test above, we can see the translated SQL:</p>
<pre><code>select m1_0.id, m1_0.content
from message m1_0
where regexp_like(m1_0.content, ?)
</code></pre>
<p>In other words, <em>like regexp</em> is syntactic sugar at the HQL level, while the heavy lifting happens in the database. Other dialects bind the same SQM function to whatever native construct they support, such as <em>REGEXP_LIKE</em> in Oracle, the <em>~</em> operator in PostgreSQL, <em>REGEXP</em> in MySQL, and so on.</p>
<p>Some dialects, for example, SQL Server prior to 2025, have no native regular expression function at all. In that case, the dialect simply doesn&#8217;t register <em>regexp_like</em>, and<strong> any HQL using <em>like regexp</em> fails at SQL execution time because Hibernate emits a call to a function the database doesn&#8217;t know</strong>.</p>
<h3 id="bd-2-the-portability-caveat" data-id="2-the-portability-caveat">5.2. The Portability Caveat</h3>
<div class="bd-anchor" id="2-the-portability-caveat"></div>
<p>Because each dialect delegates to the database&#8217;s native regex engine, the <em>flavor</em> of regex we can use is whatever the underlying database supports. Let&#8217;s see a few examples:</p>
<ul>
<li>H2 uses <em>java.util.regex</em> (Perl-compatible, <em>Matcher.find()</em> semantics — no implicit anchoring).</li>
<li>PostgreSQL uses POSIX regular expressions, which differ in subtle ways. For example, it has no <em>\d</em> shorthand without the embedded <em>(?x)</em> flag.</li>
<li>Oracle uses POSIX with a few extensions and does not support <em>\d</em> either. We must write <em>[[:digit:]]</em> or <em>[0-9]</em> instead.</li>
<li>MySQL (8.x) uses ICU regex, again with its own quirks.</li>
</ul>
<p><strong>So while the HQL stays portable, the patterns we feed into <em>like regexp</em> generally don&#8217;t</strong>. For example, a regex tested against H2 may fail or behave differently when the application is pointed at Oracle. If our application targets multiple databases, we should stick to a conservative regex subset.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>Hibernate 7.2&#8217;s <em>like regexp</em> operator lets us express regex predicates directly in HQL and have them pushed down to the database without resorting to native queries. The HQL surface is portable across dialects, but the actual regex flavor depends on the underlying database engine.</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/blob/master/persistence-modules/hibernate7">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-query-regexp">Regular Expression Support in HQL</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958534142/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958534142/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-query-regexp#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-query-regexp/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/~/958534142/0/baeldung~Regular-Expression-Support-in-HQL/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-bean-background-init</feedburner:origLink>
		<title>Bean Background Initialization in Spring Framework</title>
		<link>https://feeds.feedblitz.com/~/958532825/0/baeldung~Bean-Background-Initialization-in-Spring-Framework</link>
					<comments>https://feeds.feedblitz.com/~/958532825/0/baeldung~Bean-Background-Initialization-in-Spring-Framework#respond</comments>
		
		<dc:creator><![CDATA[Umara Mushtaq]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:42:09 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[Spring 7]]></category>
		<category><![CDATA[Spring Boot 4]]></category>
		<category><![CDATA[Spring Core Basics]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-bean-background-init</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to initialize Spring beans in the background using <em>@Bean(bootstrap = BACKGROUND)</em> to reduce startup latency while preserving dependency safety and lifecycle consistency.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958532825/0/baeldung~Bean-Background-Initialization-in-Spring-Framework">Bean Background Initialization in Spring Framework</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958532825/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958532825/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-bean-background-init#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-bean-background-init/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-bean">Bean</a> background initialization in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-tutorial">Spring Framework</a> provides a way to initialize selected beans asynchronously during application context startup. <strong>Instead of blocking the startup process on every bean initialization, Spring initializes selected beans in the background and continues processing the remaining application context</strong>. This improves application startup time and reduces perceived latency in applications with expensive initialization logic.</p>
<p>In this tutorial, we&#8217;ll explore how Bean background initialization works using the Spring native <em>bootstrap</em> attribute. We&#8217;ll demonstrate how to configure it correctly, how Spring manages lifecycle consistency during asynchronous initialization, and how this approach improves startup performance in real-world applications that rely on heavy resources such as caches, external services, or computation-heavy components.</p>
<h2 id="bd-why-background-initialization-matters-in-spring" data-id="why-background-initialization-matters-in-spring">2. Why Background Initialization Matters in Spring</h2>
<div class="bd-anchor" id="why-background-initialization-matters-in-spring"></div>
<p>Spring initializes <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-bean-scopes">singleton beans</a> during the <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-application-context">ApplicationContext</a></em> refresh phase in a strictly synchronous manner. During startup, the container manages the complete bean lifecycle and processes each bean in sequence:</p>
<ol>
<li>bean instantiation</li>
<li>dependency injection</li>
<li>initialization callback execution</li>
<li>marking the bean as ready for use</li>
</ol>
<p><strong>This approach provides predictable and deterministic startup behavior by ensuring that each bean reaches a fully initialized state before the context refresh proceeds</strong>. However, it introduces performance constraints when beans execute expensive operations during initialization.</p>
<h3 id="bd-1-synchronous-bean-initialization-model" data-id="1-synchronous-bean-initialization-model">2.1. Synchronous Bean Initialization Model</h3>
<div class="bd-anchor" id="1-synchronous-bean-initialization-model"></div>
<p><strong>Expensive initialization increases the duration of the <em>ApplicationContext</em> refresh phase by forcing the container to complete the lifecycle of each bean before moving forward</strong>. It reduces throughput in the bean creation pipeline and increases overall startup latency. As a result, even non-critical initialization logic delays the application from becoming available.</p>
<h3 id="bd-2-single-threaded-initialization-bottleneck" data-id="2-single-threaded-initialization-bottleneck">2.2. Single-Threaded Initialization Bottleneck</h3>
<div class="bd-anchor" id="2-single-threaded-initialization-bottleneck"></div>
<p><strong>The default startup model executes all bean initialization on the same thread that drives the context refresh process</strong>. Consequently, Spring executes independent initialization tasks sequentially and prevents them from running concurrently. This limits startup throughput and prevents Spring from taking advantage of available parallelism during application bootstrap.</p>
<h2 id="bd-background-initialization-mechanism" data-id="background-initialization-mechanism">3. Background Initialization Mechanism</h2>
<div class="bd-anchor" id="background-initialization-mechanism"></div>
<p>Spring introduces background initialization using the <em>bootstrap</em> attribute to reduce startup blocking caused by expensive singleton bean creation.</p>
<p>During application context refresh, Spring applies a modified execution strategy to selected beans:</p>
<ul>
<li><strong>Async delegation</strong>: Spring delegates the initialization phase of selected beans to a container-managed executor (the <em>bootstrap executor</em>), enabling execution to occur on a separate thread without blocking the main refresh process.</li>
<li><strong>Dependency coordination:</strong> When another bean depends on a background-initialized bean through a non-lazy dependency, Spring suspends dependency resolution for that bean and waits for initialization to complete before injecting the bean.</li>
<li><strong>Lifecycle preservation</strong>: Spring preserves the standard lifecycle order.</li>
</ul>
<p>This design ensures safe background execution while decoupling heavy initialization from the main startup thread, enabling earlier application readiness without affecting bean consistency.</p>
<h2 id="bd-example-setup" data-id="example-setup">4. Example Setup</h2>
<div class="bd-anchor" id="example-setup"></div>
<p>This section introduces a product preload class that simulates an expensive startup workload in Spring applications. This can be cache warming, dataset loading, or external resource preparation, which typically increases application startup time.</p>
<p>Let&#8217;s define the class to demonstrate this behavior:</p>
<pre><code class="language-java">public class ProductCatalogInitializer {
    public ProductCatalogInitializer() {
        loadProducts();
    }
    private void loadProducts() {
        System.out.println("Starting product preload");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Product preload completed");
    }
    public String getStatus() {
        return "Catalog ready";
    }
}</code></pre>
<p>The <em>loadProducts()</em> method represents a long-running initialization task executed during bean creation. Spring runs this logic on the main startup thread, which blocks the <em>ApplicationContext</em> refresh until completion.</p>
<p>This component acts as the baseline for demonstrating how Spring shifts such initialization work to background execution using the bootstrap mechanism in the next section.</p>
<h2 id="bd-background-initialization-using-bootstrap-mode" data-id="background-initialization-using-bootstrap-mode">5. Background Initialization Using Bootstrap Mode</h2>
<div class="bd-anchor" id="background-initialization-using-bootstrap-mode"></div>
<p>Spring applies background initialization at the bean definition level using the <em>bootstrap</em> attribute. Selected beans then get initialized concurrently without blocking the main startup thread.</p>
<h3 id="bd-1-enabling-background-initialization" data-id="1-enabling-background-initialization">5.1. Enabling Background Initialization</h3>
<div class="bd-anchor" id="1-enabling-background-initialization"></div>
<p>At this point, let&#8217;s create a configuration that marks <em>ProductCatalogInitializer</em> as a Spring-managed bean and enables background initialization using <em>BACKGROUND</em> bootstrap mode:</p>
<pre><code class="language-java">@Configuration
public class AppConfig {
    @Bean(bootstrap = Bean.Bootstrap.BACKGROUND)
    public ProductCatalogInitializer productCatalogInitializer() {
        return new ProductCatalogInitializer();
    }
}</code></pre>
<p>Spring detects the <em>BACKGROUND</em> bootstrap mode and offloads bean initialization to a container-managed <em>bootstrapExecutor</em>, allowing it to run on a separate thread instead of blocking the main application context refresh. Hence, this mechanism relies on a configured bootstrap executor. Without it, Spring can&#8217;t execute background initialization, and the affected beans fall back to normal synchronous initialization during startup.</p>
<h3 id="bd-2-bean-injection" data-id="2-bean-injection">5.2. Bean Injection</h3>
<div class="bd-anchor" id="2-bean-injection"></div>
<p>A background-initialized bean can be injected into other Spring-managed components in the same way as any standard singleton bean, since background initialization doesn&#8217;t change the core dependency injection model.</p>
<p>In most cases, direct injection remains sufficient because Spring coordinates the lifecycle and ensures that the bean becomes fully available within the application context startup phase.<strong> However, in scenarios where access may occur before the initialization process has completed, Spring provides <em>ObjectProvider</em> as an optional mechanism for deferred retrieval of the bean at runtime.</strong>
<br>
To that end, let&#8217;s create a service that demonstrates the injection of a background-initialized bean:</p>
<pre><code class="language-java">@Service
public class ProductService {
    private final ObjectProvider&lt;ProductCatalogInitializer&gt;
            initializerProvider;
    public ProductService(
            ObjectProvider&lt;ProductCatalogInitializer&gt; initializerProvider) {
                this.initializerProvider = initializerProvider;
    }
    public void printStatus() {
        ProductCatalogInitializer initializer = initializerProvider.getObject();
        System.out.println("Bean status: " + initializer.getStatus());
    }
}</code></pre>
<p>Now, the dependency isn&#8217;t resolved during bean creation. Instead, Spring retains the reference and defers resolution until <em>getObject()</em> is invoked. At that point, the actual bean instance is retrieved, so access occurs only after background initialization has completed, while the injection phase remains independent of startup timing.</p>
<h3 id="bd-3-application-startup-behaviorwith-background-initialization" data-id="3-application-startup-behaviorwith-background-initialization">5.3. Application Startup Behavior with Background Initialization</h3>
<div class="bd-anchor" id="3-application-startup-behaviorwith-background-initialization"></div>
<p>With <em>BACKGROUND</em> bootstrap mode enabled, Spring offloads selected bean initialization to a background thread, allowing the application context refresh to proceed without waiting for these beans. As a result, startup becomes non-blocking for the selected initialization workload, while execution continues in parallel.</p>
<p>Despite this, non-lazy background-initialized beans remain part of the Spring container lifecycle, and their completion is coordinated within the overall startup process.</p>
<h3 id="bd-4-performance-impact" data-id="4-performance-impact">5.4. Performance Impact</h3>
<div class="bd-anchor" id="4-performance-impact"></div>
<p>This behavior directly reduces startup latency. Spring moves heavy bean initialization off the main refresh thread. As a result, the application becomes operational earlier while background tasks continue executing in parallel. Beans that load large caches, establish connection pools, or run schema validation benefit the most.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored background initialization in the Spring Framework using the <em>BACKGROUND</em> bootstrap mode, which enables selected beans to initialize asynchronously during application context refresh.</p>
<p><strong>The mechanism decouples expensive bean initialization from the main startup thread while preserving the Spring lifecycle semantics.</strong> Rather than launching uncontrolled threads, Spring keeps full awareness of bean dependencies. If a non-lazy bean depends on one that is still initializing in the background, Spring ensures it waits, preventing access to incomplete or half-built objects. In addition, using <em>ObjectProvider</em> helps delay bean retrieval until it is actually needed, adding an extra layer of safety for early-access scenarios.</p>
<p><strong>This approach improves application startup responsiveness in systems with heavy initialization workloads by enabling parallel execution during context refresh.</strong> It maintains dependency safety and lifecycle consistency while reducing blocking on the main thread. Background initialization is best suited for non-critical beans such as cache warmers, external integrations, and data preloading components.</p>
<p>The code backing this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-core-5">over on Github.</a></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-bean-background-init">Bean Background Initialization in Spring Framework</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958532825/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958532825/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958532825/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-bean-background-init#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-bean-background-init/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/~/958532825/0/baeldung~Bean-Background-Initialization-in-Spring-Framework/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-jcasbin-authorization</feedburner:origLink>
		<title>Authorization using jCasbin</title>
		<link>https://feeds.feedblitz.com/~/958532828/0/baeldung~Authorization-using-jCasbin</link>
					<comments>https://feeds.feedblitz.com/~/958532828/0/baeldung~Authorization-using-jCasbin#respond</comments>
		
		<dc:creator><![CDATA[Graham Cox]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:36:54 +0000</pubDate>
				<category><![CDATA[Security]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-jcasbin-authorization</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Security-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 how to use jCasbin to define an access control model for Java applications, making use of standard approaches such as ACLs and RBAC.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958532828/0/baeldung~Authorization-using-jCasbin">Authorization using jCasbin</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958532828/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSecurity-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 title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958532828/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-jcasbin-authorization#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-jcasbin-authorization/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/Security-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/Security-Featured-Image-06-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-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 tutorial, we’ll take a look at <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/casbin-jcasbin">jCasbin</a> &#8211; the Java official port of the popular Apache <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://casbin.apache.org/">Casbin</a> authorization library. We’ll see what it is, how to use it and what we can do with it.</p>
<h2 id="bd-what-is-jcasbin" data-id="what-is-jcasbin"><strong>2. What is jCasbin</strong></h2>
<div class="bd-anchor" id="what-is-jcasbin"></div>
<p><strong>jCasbin is the Java version of the powerful Casbin access control library. This allows us to easily answer questions about access to resources within our applications.</strong></p>
<p>jCasbin, and the entire suite of Casbin libraries, work using a configuration model that allows us to describe the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-access-control-models">access control model</a> we want to use &#8211; such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://en.wikipedia.org/wiki/Access-control_list">ACLs</a> or <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/role-vs-permission-based-access-control">RBAC</a> &#8211; and a separate set of policy data applied to that configuration model.</p>
<p>Typically, this works by defining:</p>
<ul>
<li>Objects that we want to control access to.</li>
<li>Subjects who want to access these objects.</li>
<li>Actions that the objects want to perform.</li>
</ul>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/jcasbin-access-control.png"><img loading="lazy" decoding="async" class="alignnone wp-image-252658" src="https://www.baeldung.com/wp-content/uploads/2026/06/jcasbin-access-control-1024x382.png" alt="" width="536" height="200" /></a>
<p>However, jCasbin is flexible enough that we can use any structure we want, as long as we can model it correctly in our configuration and policy data.</p>
<h2 id="bd-dependencies" data-id="dependencies"><strong>3. Dependencies</strong></h2>
<div class="bd-anchor" id="dependencies"></div>
<p><strong>Before using jCasbin, we need to include the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.casbin/jcasbin">latest version</a> in our build, which is <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.casbin/jcasbin/1.99.0">1.99.0</a> at the time of writing.</strong></p>
<p>If we’re using Maven, we can include this dependency in our pom.xml file:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.casbin&lt;/groupId&gt;
    &lt;artifactId&gt;jcasbin&lt;/artifactId&gt;
    &lt;version&gt;1.99.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>At this point, we’re ready to start using it in our application.</p>
<h2 id="bd-creating-an-enforcer" data-id="creating-an-enforcer"><strong>4. Creating an Enforcer</strong></h2>
<div class="bd-anchor" id="creating-an-enforcer"></div>
<p>Once we have jCasbin in our application, we&#8217;re ready to use it. <strong>The central class for this is the <em>Enforcer</em>. Constructing this requires two data sources: our configuration model and our policy data.</strong> The easiest way to manage this is through passing in filenames:</p>
<pre><code class="language-java">Enforcer enforcer = new Enforcer("path/to/model.conf", "path/to/policy.csv");</code></pre>
<p>However, having our policy data be a local file can be difficult. <strong>As such, jCasbin allows the use of <em>Adapter</em>s here instead</strong>. These provide a means to load and manipulate the policy data from any data source we like. We also have a set of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://casbin.apache.org/docs/adapters/">standard adapters</a> that can be used for our application. These include familiar technologies such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/casbin-jcasbin-jdbc-adapter">JDBC</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jcasbin/hibernate-adapter">Hibernate</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jcasbin/jcasbin-mongo-adapter">MongoDB</a> and many others:</p>
<pre><code class="language-java">Adapter jdbcAdapter = new JDBCAdapter(dataSource);
Enforcer enforcer = new Enforcer("path/to/model.conf", jdbcAdapter);</code></pre>
<p>Exactly how these adapters work depends on the adapter in question and is out of scope of this article.</p>
<p>When specifying policy data as a filename, this internally uses the <em>FileAdapter </em>instead. We can also use this to load files available from other sources by providing an<em> InputStream</em>:</p>
<pre><code class="language-">FileAdapter fileAdapter = new FileAdapter(getClass().getResourceAsStream("/com/baeldung/jcasbin/policy.csv"));
Enforcer enforcer = new Enforcer("path/to/model.conf", fileAdapter);
</code></pre>
<p><strong>In exactly the same way, we can provide our model data using a <em>Model </em>instance instead.</strong> This allows us to load our model data from alternative sources:</p>
<pre><code class="language-java">String content = new String(getClass().getClassLoader().getResourceAsStream("com/baeldung/jcasbin/model.conf").readAllBytes());
Model model = new Model();
model.loadModelFromText(content);
Enforcer enforcer = new Enforcer(model, "path/to/policy.csv");
</code></pre>
<p>Once we&#8217;ve created our <em>Enforcer</em>, we&#8217;re ready to start using it to check permissions.</p>
<h2 id="bd-enforcing-permissions" data-id="enforcing-permissions"><strong>5. Enforcing Permissions</strong></h2>
<div class="bd-anchor" id="enforcing-permissions"></div>
<p><strong>Now that we&#8217;ve got our <em>Enforcer</em>, we can start checking permissions. We do this using the <em>enforce()</em> method.</strong> This typically involves passing in our subject, object, and action to check:</p>
<pre><code class="language-java">if (enforcer.enforce("alice", "data1", "read")) {
    // permit alice to read data1
} else {
    // deny the request
}</code></pre>
<p>Note that the <em>enforce()</em> method actually takes an arbitrary set of objects, depending on how our configuration model is defined.</p>
<p>The exact way permission checking works then depends on both the structure of our configuration model and our policy data.</p>
<h3 id="bd-1-access-control-lists" data-id="1-access-control-lists"><strong>5.1. Access Control Lists</strong></h3>
<div class="bd-anchor" id="1-access-control-lists"></div>
<p><strong>One of the easiest ways to configure jCasbin is through Access Control Lists (ACLs).</strong> A typical configuration model might look like this:</p>
<pre><code class="language-plaintext"># Request definition
[request_definition]
r = sub, obj, act
# Policy definition
[policy_definition]
p = sub, obj, act
# Matchers
[matchers]
m = r.sub == p.sub &amp;&amp; r.obj == p.obj &amp;&amp; r.act == p.act
# Policy effect
[policy_effect]
e = some(where (p.eft == allow))</code></pre>
<p>Here, the <em>request_definition </em>section tells us what the shape of the data passed into <em>enforcer.enforce()</em> should be. It gives us names for the parameters we pass in &#8211; here, &#8220;sub&#8221;, &#8220;obj&#8221; and &#8220;act&#8221; are used as shorthand for &#8220;subject&#8221;, &#8220;object&#8221; and &#8220;action&#8221;.</p>
<p>The <em>policy_definition </em>section then uses the exact same names to map onto the policy data. If not specified, there&#8217;s also an implicit value called &#8220;eft&#8221; &#8211; short for &#8220;effect&#8221; &#8211; which defaults to &#8220;allow&#8221;.</p>
<p>The <em>matchers</em> section then explains how we can match policy rows to requests. In this case, we&#8217;re simply saying that every field must have exactly the same value.</p>
<p>Finally, the <em>policy_effect</em> section tells us how to determine the overall effect of our policy. The action is only allowed if this returns a positive match.</p>
<p><strong>In this mode, our policy data is a straight list of subjects, objects and allowed actions &#8211; as defined by our <em>policy_definition:</em></strong></p>
<pre><code class="language-plaintext">p, alice, data1, read
p, bob, data2, write</code></pre>
<p>This indicates that the subject &#8220;alice&#8221; can perform the action &#8220;read&#8221; on the object &#8220;data1&#8221;, whereas the subject &#8220;bob&#8221; can perform the action &#8220;write&#8221; on the object &#8220;data2&#8221;.</p>
<pre><code class="language-java">assertTrue(enforcer.enforce("alice", "data1", "read"));
assertTrue(enforcer.enforce("bob", "data2", "write"));</code></pre>
<p>Any other checks are rejected because they don&#8217;t match our configuration.</p>
<h3 id="bd-2-super-users" data-id="2-super-users"><strong>5.2. Super Users</strong></h3>
<div class="bd-anchor" id="2-super-users"></div>
<p><strong>Sometimes we might want super users &#8211; users who can do anything regardless of what the policy data specifies.</strong></p>
<p>We express this in our configuration model by adjusting how the <em>matchers </em>section is defined:</p>
<pre><code class="language-plaintext">[matchers]
m = r.sub == p.sub &amp;&amp; r.obj == p.obj &amp;&amp; r.act == p.act || r.sub == "root"
</code></pre>
<p>Here, in addition to the same matching as before, we also match when the subject has the exact value &#8220;root&#8221;, regardless of what the rest of the policy expression says. <strong>Using this, a subject of &#8220;root&#8221; will always pass every check regardless:</strong></p>
<pre><code class="language-java">assertTrue(enforcer.enforce("root", "data2", "write"));</code></pre>
<p>This introduces some risks into our permission checks, so we need to use it carefully.</p>
<h3 id="bd-3-role-based-access-control" data-id="3-role-based-access-control"><strong>5.3. Role-Based Access Control</strong></h3>
<div class="bd-anchor" id="3-role-based-access-control"></div>
<p><strong>Role-Based Access Control (RBAC) introduces an additional layer of indirection into our configuration model. In this case, we assign roles to users, and can define permissions based on roles.</strong></p>
<p>We start by adding a <em>role_definition </em>section:</p>
<pre><code class="language-plaintext">[role_definition]
g = _, _</code></pre>
<p>This defines the <em>g()</em> function that we can use to define role membership.</p>
<p>We also need to update our <em>matchers</em> section to make use of this:</p>
<pre><code class="language-plaintext">[matchers]
m = g(r.sub, p.sub) &amp;&amp; r.obj == p.obj &amp;&amp; r.act == p.act</code></pre>
<p><strong>Instead of directly comparing the policy subjects with the request, this says they must match via the role definitions.</strong></p>
<p>We can now make use of this within our policy data:</p>
<pre><code class="language-plaintext">p, alice, data1, read
p, data2_admin, data2, read
p, data2_admin, data2, write
g, bob, data2_admin</code></pre>
<p>All of our rows that start with <em>p</em> define how permissions are assigned, either directly to users or else to roles. Here we&#8217;re assigning one permission directly to the subject &#8220;alice&#8221;, and two permissions to the role &#8220;data2_admin&#8221;.</p>
<p>The rows starting with <em>g </em>&#8211; from our <em>role_definition </em>section above &#8211; define how role membership works. Here, we&#8217;re defining that the subject &#8220;bob&#8221; is assigned the role &#8220;data2_admin&#8221;<em>. </em>This then means that this subject implicitly has all of the permissions assigned to this role as well:</p>
<pre><code class="language-java">assertTrue(enforcer.enforce("bob", "data2", "write"));</code></pre>
<p>We can also nest roles. That is, we can assign one role to another, generating an entire hierarchy of permissions:</p>
<pre><code class="language-plaintext">g, superuser, data2_admin
g, carol, superuser
</code></pre>
<p>Here, the role &#8220;data2_admin&#8221; has been assigned to the role &#8220;superuser&#8221;, and the role &#8220;superuser&#8221; has been assigned to the subject &#8220;carol&#8221;. This means that this subject has all the permissions all the way up:</p>
<pre><code class="language-java">assertTrue(enforcer.enforce("carol", "data2", "read"));</code></pre>
<p>We never gave &#8220;carol&#8221; permission on &#8220;data2&#8221; &#8211; either directly or indirectly. Instead, she inherited it through her role &#8220;superuser&#8221;.</p>
<h2 id="bd-management-api" data-id="management-api"><strong>6. Management API</strong></h2>
<div class="bd-anchor" id="management-api"></div>
<p><strong>In addition to checking permissions, we also have management APIs for working with our setup.</strong></p>
<p>We do this from the same <em>Enforcer </em>instance that we&#8217;ve already been using.</p>
<h3 id="bd-1-querying-subjects-objects-and-actions" data-id="1-querying-subjects-objects-and-actions"><strong>6.1. Querying Subjects, Objects and Actions</strong></h3>
<div class="bd-anchor" id="1-querying-subjects-objects-and-actions"></div>
<p><strong>The easiest thing we can do is query all the subjects and objects in our policy data.</strong> We do this using the <em>getAllSubjects()</em>, <em>getAllObjects()</em> and <em>getAllActions()</em> methods:</p>
<pre><code class="language-java">List&lt;String&gt; subjects = enforcer.getAllSubjects();
List&lt;String&gt; objects = enforcer.getAllObjects();
List&lt;String&gt; actions = enforcer.getAllActions();</code></pre>
<p>We can also query the actions that any given subject can perform on any given object:</p>
<pre><code class="language-java">Set&lt;String&gt; subjects = enforcer.getPermittedActions("alice", "data1");</code></pre>
<p>This works correctly for both ACL and RBAC setups, as well as for hierarchical roles.</p>
<h3 id="bd-2-querying-rbac-roles" data-id="2-querying-rbac-roles"><strong>6.2. Querying RBAC Roles</strong></h3>
<div class="bd-anchor" id="2-querying-rbac-roles"></div>
<p><strong>With an RBAC setup, we can also query exactly which roles are assigned to which subjects:</strong></p>
<pre><code class="language-java">List&lt;String&gt; roles = enforcer.getRolesForUser("carol");
// superuser</code></pre>
<p>This returns only the roles directly assigned, not those included in the configured hierarchy.</p>
<p>However, we can also query which roles are assigned to other roles in the same way:</p>
<pre><code class="language-java">List&lt;String&gt; roles = enforcer.getRolesForUser("superuser");
// data2_admin</code></pre>
<p>This means we can traverse the hierarchy if needed.</p>
<h3 id="bd-3-managing-permissions-and-roles" data-id="3-managing-permissions-and-roles"><strong>6.3. Managing Permissions and Roles</strong></h3>
<div class="bd-anchor" id="3-managing-permissions-and-roles"></div>
<p><strong>We also have a set of methods we can use to directly manage the permissions and roles that subjects have.</strong></p>
<p>We can directly add and remove permissions between a subject and an object:</p>
<pre><code class="language-java">enforcer.addPermissionForUser("alice", "data2", "read");
enforcer.deletePermissionForUser("alice", "data2", "read");</code></pre>
<p>These apply directly once called, and permission checks take effect straight away.</p>
<p><strong>When we&#8217;re working in RBAC mode, we can additionally add and remove roles from subjects:</strong></p>
<pre><code class="language-java">enforcer.addRoleForUser("alice", "superuser");
enforcer.deleteRoleForUser("alice", "superuser");</code></pre>
<p>We apply all of these changes in memory. We need to use the <em>savePolicy()</em> method to write them back to our backing store:</p>
<pre><code class="language-java">enforcer.savePolicy();</code></pre>
<p>However, we can only do this if we&#8217;re using certain adapters. It also depends on how the data was loaded. For example, we can&#8217;t write back if we loaded from an <em>InputStream</em>.</p>
<h2 id="bd-summary" data-id="summary"><strong>7. Summary</strong></h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we took a very quick look at jCasbin. There’s a lot more that we can do with this. Next time you need to manage access controls for your applications, why not give it a try?</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/libraries-7">GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jcasbin-authorization">Authorization using jCasbin</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958532828/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958532828/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSecurity-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 title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958532828/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-jcasbin-authorization#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-jcasbin-authorization/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/~/958532828/0/baeldung~Authorization-using-jCasbin/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/hibernate-embedded-table</feedburner:origLink>
		<title>Guide to @EmbeddedTable in Hibernate</title>
		<link>https://feeds.feedblitz.com/~/958532831/0/baeldung~Guide-to-EmbeddedTable-in-Hibernate</link>
					<comments>https://feeds.feedblitz.com/~/958532831/0/baeldung~Guide-to-EmbeddedTable-in-Hibernate#respond</comments>
		
		<dc:creator><![CDATA[Abderrahim Azhrioun]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:33:15 +0000</pubDate>
				<category><![CDATA[JPA]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[JPA Annotations]]></category>
		<category><![CDATA[JPA Entities]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/hibernate-embedded-table</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use the new <em>@EmbeddedTable</em> annotation of Hibernate to map embeddable objects to secondary tables more cleanly.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958532831/0/baeldung~Guide-to-EmbeddedTable-in-Hibernate">Guide to @EmbeddedTable in Hibernate</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958532831/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2018%2f10%2fSocial-Javaee-5-k-featured-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-embedded-table#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-embedded-table/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/2018/10/Social-Javaee-5-k-featured-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/2018/10/Social-Javaee-5-k-featured-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured.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>The <em>@EmbeddedTable</em> annotation is a new Hibernate feature that simplifies the logic of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-mapping-single-entity-to-multiple-tables">mapping an embedded object to a secondary table</a>.</p>
<p>In this short tutorial, we&#8217;ll shed light on the annotation and delve into its usage. First, we&#8217;ll start with some insight into the motivation behind <em>@EmbeddedTable</em>. Then, we’ll demonstrate how to use it in practice.</p>
<h2 id="bd-the-problem-with-traditional-mapping" data-id="the-problem-with-traditional-mapping">2. The Problem With Traditional Mapping</h2>
<div class="bd-anchor" id="the-problem-with-traditional-mapping"></div>
<p>Before this new annotation, JPA provided multiple annotations to map a single entity into multiple tables. However, this traditional approach requires <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-mapping-single-entity-to-multiple-tables#single-entity">@SecondaryTable</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-embedded-embeddable#embedded">@Embedded</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-embedded-embeddable#embeddable">@Embeddable</a></em>, and multiple <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-attributeoverride"><em>@AttributeOverride</em></a> annotations to override each column individually. <strong>This can quickly become verbose as the number of columns grows</strong>.</p>
<p>Let&#8217;s say we have a single <em>Person</em> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-entities">entity</a> that stores personal and address information. Here, <strong>we assume that we want to store the address details in a separate secondary table</strong>.</p>
<p>First, let&#8217;s create the <em>Address</em> object we want to embed inside the <em>Person</em> entity:</p>
<pre><code class="language-java">@Embeddable
public class Address {
	
    private String street;
    private String city;
    private String code;
    // setters
    
}</code></pre>
<p>Now, we create the <em>Person</em> entity:</p>
<pre><code class="language-java">@Entity
@SecondaryTable(name = "person_address")
public class Person {
    
    @Id
    private int id;
    
    @Column(name= "first_name")
    private String firstName;
    
    @Column(name = "last_name")
    private String lastName;
    
    @Embedded 
    private Address address;
    // setters
}
</code></pre>
<p>That&#8217;s not all, <strong>as mapping the <em>Address</em> fields to a secondary table requires the <em>@AttributeOverride</em> annotation for each field</strong>:</p>
<pre><code class="language-java">@Embedded 
@AttributeOverride(name = "street", column = @Column(table = "person_address")) 
@AttributeOverride(name = "city", column = @Column(table = "person_address"))
@AttributeOverride(name = "code", column = @Column(table = "person_address"))
private Address address;</code></pre>
<p>This approach quickly becomes cumbersome and error-prone since missing a single override can silently map a column to the wrong table. So, this is where <em>@EmbeddedTable</em> comes into play.</p>
<h2 id="bd-using-embeddedtable" data-id="using-embeddedtable">3. Using <em>@EmbeddedTable</em></h2>
<div class="bd-anchor" id="using-embeddedtable"></div>
<p>Starting with Hibernate 7.2, we can implement the same mapping we did in the previous section far more concisely with <em>@EmbeddedTable</em>:</p>
<pre><code class="language-java">@EmbeddedTable(value = "person_address")
private Address address;</code></pre>
<p>In a nutshell, <strong>Hibernate automatically maps every field of the embedded object</strong> <em>Address</em> to the specified secondary table <em>person_address</em> without overriding each one individually.</p>
<p><strong>This is particularly useful when dealing with large embeddable objects containing several fields</strong>.</p>
<p>So, let&#8217;s add a test case to confirm that everything works:</p>
<pre><code class="language-java">@Test
void whenUsingEmbeddedTableThenMapIntoTwoSeparateTables() {
    Address address = new Address();
    address.setStreet("12 Av. Tamassint center");
    address.setCity("Tamassint");
    address.setCode("10000");
    Person person = new Person();
    person.setId(1);
    person.setFirstName("Azhrioun");
    person.setLastName("Abderrahim");
    person.setAddress(address);
    session.persist(person);
    session.flush();
    Object[] addressRow = (Object[]) session.createNativeQuery(
      "select street, city from person_address",
      Object[].class
    ).uniqueResult();
    assertEquals("12 Av. Tamassint center", addressRow[0]);
    assertEquals("Tamassint", addressRow[1]);
    Object[] personRow = (Object[]) session.createNativeQuery(
      "select first_name, last_name from person",
      Object[].class
    ).uniqueResult();
    assertEquals("Azhrioun", personRow[0]);
    assertEquals("Abderrahim", personRow[1]);
}</code></pre>
<p>As expected, the test case runs successfully.</p>
<h2 id="bd-the-limitations" data-id="the-limitations">4. The Limitations</h2>
<div class="bd-anchor" id="the-limitations"></div>
<p>Although convenient, there are a couple of points worth keeping in mind when using <em>@EmbeddedTable</em>.</p>
<p>First, it&#8217;s a Hibernate-specific annotation and not part of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jakarta-persistence-3-2">Jakarta Persistence specification</a>. <strong>If portability across JPA providers matters, the <em>@AttributeOverride</em> annotation is always the safer choice</strong>.</p>
<p>Additionally, <em>@EmbeddedTable</em> is marked <em>@Incubating</em>, which means the API is still evolving and not yet considered stable and mature.</p>
<p>Typically, <em>@EmbeddedTable</em> is intended for top-level embedded objects. So, using it on nested objects isn&#8217;t supported yet.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this short article, we explored the new Hibernate <em>@EmbeddedTable</em> annotation.</p>
<p>Along the way, we learned that it provides a cleaner alternative to multiple <em>@AttributeOverride</em> annotations when mapping embedded objects to secondary tables.</p>
<p>The full source is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/azhwani/tutorials/tree/master/persistence-modules/hibernate-annotations">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-embedded-table">Guide to @EmbeddedTable in Hibernate</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958532831/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958532831/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2018%2f10%2fSocial-Javaee-5-k-featured-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-embedded-table#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-embedded-table/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/~/958532831/0/baeldung~Guide-to-EmbeddedTable-in-Hibernate/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-652</feedburner:origLink>
		<title>Java Weekly, Issue 652</title>
		<link>https://feeds.feedblitz.com/~/958459364/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/958459364/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 12:57:04 +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=204137</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>The agent harness seems to be the one thing in 2026. For really good reason.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958459364/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 652</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958459364/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958459364/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-652#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-652/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://lucumr.pocoo.org/2026/6/23/the-coming-loop/">&gt;&gt; The Coming Loop</a></strong> [<span style="color: #993300;">lucumr.pocoo.org</span>]</p>
<p>A frame for the next phase of AI-assisted development &#8211; an agent harness, which generates, runs, inspects, corrects, and repeat quickly enough that the system becomes genuinely useful instead of just impressive. Anecdotally I&#8217;ve been running one for a few months now <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;" /> A good read.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/23/spring-ai-self-correcting-structured-output" target="_blank" rel="noopener"><strong>Self-Correcting Structured Output in Spring AI 2.0</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://inside.java/2026/06/21/better-tools-immutable-data/" target="_blank" rel="noopener"><strong>Better Tools for Immutable Data</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://www.infoq.com/news/2026/06/block-450-jvm-monorepo-migration/" target="_blank" rel="noopener"><strong>Behind the Scenes: Block 450 JVM Repositories into Monorepo to Reduce Dependency Drift</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/jurassic-jdk-migrate-or-extinct/" target="_blank" rel="noopener"><strong>Jurassic JDK: Migrate or Extinct</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.frankel.ch/programming-languages-targets-platforms/" target="_blank" rel="noopener"><strong>On programming languages, targets, and platforms</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.jetbrains.com/amper/2026/06/kotlin-toolchain-0-11/" target="_blank" rel="noopener"><strong>Kotlin Toolchain 0.11: The Next Step for Amper</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://foojay.io/today/pi4j-drivers-simplifying-sensor-and-hardware-integration-in-java/" target="_blank" rel="noopener"><strong>Pi4J Drivers: Simplifying Sensor and Hardware Integration in Java</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/systematic-ai-coding-my-takeaways-from-the-eclipse-foundation-workshop-in-brussels/" target="_blank" rel="noopener"><strong>Systematic AI Coding: My Takeaways from the Eclipse Foundation Workshop in Brussels</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://advancedweb.hu/backups-with-restic-2-year-retrospective/" target="_blank" rel="noopener"><strong>Backups with Restic: 2-year retrospective</strong></a> [<span style="color: #800000;">advancedweb.hu</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mnot.net/blog/2026/well_known_uris" target="_blank" rel="noopener"><strong>So You Want To Define a Well-Known URI</strong></a> [<span style="color: #800000;">mnot.net</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/06/18/working-effectively-with-claude-code.html" target="_blank" rel="noopener"><strong>Working Effectively with Claude Code</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/06/19/sustainable-acceleration-and-the-agentic-software-development-life-cycle.html" target="_blank" rel="noopener"><strong>Sustainable acceleration and the Agentic Software Development Life Cycle</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/how-netflix-simplified-batch-compute-with-kueue-87860682629c" target="_blank" rel="noopener"><strong>How Netflix Simplified Batch Compute with Kueue</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.com/toward-more-controllable-ai-video-editing-an-early-research-exploration-at-netflix-eb8160ed60a2" target="_blank" rel="noopener"><strong>Toward More Controllable AI Video Editing: An Early Research Exploration at Netflix</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/quarkus-unpacked-insights-from-the-foojay-podcast/" target="_blank" rel="noopener"><strong>Quarkus Unpacked: Insights from the Foojay Podcast</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/06/25/podcast-060/" target="_blank" rel="noopener"><strong>Episode 60 &#8220;How JEPs Drive Java&#8217;s Evolution&#8221; [AtA]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/25/a-bootiful-podcast-francesco-ciulla" target="_blank" rel="noopener"><strong>A Bootiful Podcast: My friend Francesco Ciulla on developer advocacy and more</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://spring.io/blog/2026/06/25/spring-boot-3-5-16-available-now" target="_blank" rel="noopener"><strong>Spring Boot 3.5.16 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/06/24/spring-data-2025-0-13-released" target="_blank" rel="noopener"><strong>Spring Data 2025.0.13 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://quarkus.io/blog/CVE-2026-50559/" target="_blank" rel="noopener"><strong>Emergency releases to fix CVE-2026-50559 in all supported streams</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.36.3" target="_blank" rel="noopener"><strong>Quarkus 3.36.3</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.33.2.1" target="_blank" rel="noopener"><strong>3.33.2.1</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.27.4.1" target="_blank" rel="noopener"><strong>3.27.4.1</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.20.6.2" target="_blank" rel="noopener"><strong>3.20.6.2</strong></a> [<span style="color: #800000;">github.com/quarkusio</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.1.3" target="_blank" rel="noopener"><strong>Vert.x 5.1.3</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.17" target="_blank" rel="noopener"><strong>Elasticsearch 8.19.17</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.3.6" target="_blank" rel="noopener"><strong>9.3.6</strong></a> [<span style="color: #800000;">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.16" target="_blank" rel="noopener"><strong>Zuul v3.6.16</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.15" target="_blank" rel="noopener"><strong>v3.6.15</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.2" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.2</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/3.2.18" target="_blank" rel="noopener"><strong>Helidon 3.2.18</strong></a> [<span style="color: #800000;">github.com/helidon-io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wildfly.org/news/2026/06/19/WildFly-40-0-1-is-released/" target="_blank" rel="noopener"><strong>WildFly 40.0.1 is released!</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">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://yusufaytas.com/old-software-was-fast-because-it-had-no-choice">&gt;&gt; Old Software Was Fast Because It Had No Choice</a></strong> [<span style="color: #993300;">yusufaytas.com</span>]</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-652">Java Weekly, Issue 652</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958459364/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/958459364/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/958459364/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-652#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-652/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/~/958459364/0/baeldung~Java-Weekly-Issue/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
</channel></rss>

