<?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, 22 Aug 2026 23:26:23 +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-fix-jackson-json-parse-error</feedburner:origLink>
		<title>How to Fix Jackson JSON Parse Error Can not construct instance of java.time.LocalDate</title>
		<link>https://feeds.feedblitz.com/~/968126756/0/baeldung~How-to-Fix-Jackson-JSON-Parse-Error-Can-not-construct-instance-of-javatimeLocalDate</link>
					<comments>https://feeds.feedblitz.com/~/968126756/0/baeldung~How-to-Fix-Jackson-JSON-Parse-Error-Can-not-construct-instance-of-javatimeLocalDate#respond</comments>
		
		<dc:creator><![CDATA[Hiks Gerganov]]></dc:creator>
		<pubDate>Sat, 22 Aug 2026 23:26:23 +0000</pubDate>
				<category><![CDATA[Jackson]]></category>
		<category><![CDATA[Java Dates]]></category>
		<category><![CDATA[Exception]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-fix-jackson-json-parse-error</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" fetchpriority="high" /><p>Learn why sometimes Jackson can't construct an instance of LocalDate or LocalDateTime and how to fix the error it throws.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/968126756/0/baeldung~How-to-Fix-Jackson-JSON-Parse-Error-Can-not-construct-instance-of-javatimeLocalDate">How to Fix Jackson JSON Parse Error <em>Can not construct instance of java.time.LocalDate</em></a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968126756/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/968126756/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/968126756/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/968126756/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/968126756/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-fix-jackson-json-parse-error#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-fix-jackson-json-parse-error/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-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11.jpg 1200w" sizes="(max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>When working with JSON in Java, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson-object-mapper-tutorial">Jackson</a> usually handles Java 8 date and time types without much configuration. Yet, we can still encounter problems such as one of the common <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jackson-deserialize-particular-type">deserialization</a> parse errors:</p>
<pre><code class="language-text">JSON parse error: Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value</code></pre>
<p>In newer Jackson versions, the error text takes another form:</p>
<pre><code class="language-text">Cannot deserialize value of type java.time.LocalDate from String</code></pre>
<p>Although the wording differs slightly, both errors indicate that Jackson is unable to convert the JSON value into a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-creating-localdate-with-values"><em>LocalDate</em></a> or <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-date-to-localdate-and-localdatetime"><em>LocalDateTime</em></a>.</p>
<p>In this tutorial, we&#8217;ll explore the JSON deserialization exception and ways around it. First, we demonstrate the issue with an example. After that, we go through different reasons for the problem at hand and how they can be addressed.</p>
<h2 id="bd-understanding-the-problem" data-id="understanding-the-problem">2. Understanding the Problem</h2>
<div class="bd-anchor" id="understanding-the-problem"></div>
<p>Let&#8217;s write a basic <em>User</em> class:</p>
<pre><code class="language-java">public class User {
    private String name;
    private LocalDate dob;
    // getters and setters
}</code></pre>
<p>Now, let&#8217;s assume we receive a matching JSON:</p>
<pre><code class="language-json">{
  "name": "x",
  "dob": "2006-06-06"
}</code></pre>
<p>Instead of successfully deserializing the JSON, Jackson throws an exception similar to:</p>
<pre><code class="language-text">Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value</code></pre>
<p>Notably, <strong>the JSON data appears to be valid, because the date already follows the ISO-8601 format expected by <em>LocalDate</em></strong>. The problem usually isn&#8217;t the JSON itself but the configuration of Jackson. In fact, the root cause may be a missing Java Time module, an unsupported date format, or using the wrong Java date-time type for the incoming JSON.</p>
<h2 id="bd-verify-jackson-version" data-id="verify-jackson-version">3. Verify Jackson Version</h2>
<div class="bd-anchor" id="verify-jackson-version"></div>
<p>Support for the Java Time API has evolved considerably across Jackson releases. Before delving into specific technical issues and code changes, <strong>we might consider the versions we&#8217;re using</strong>.</p>
<p>In Jackson <em>2.x</em>, Java Time support is provided by the separate <em>jackson-datatype-jsr310</em> module. <strong>In Jackson <em>3.x</em>, this support is built into <em>jackson-databind</em>, so separate module registration isn’t required</strong>.</p>
<p>If the project uses an outdated Jackson version, upgrading Jackson may resolve deserialization issues without any code changes. When it comes to Jackson <em>2.x</em>, we should also verify that the Java Time module is present and properly configured.</p>
<h2 id="bd-ensure-java-time-support" data-id="ensure-java-time-support">4. Ensure Java Time Support</h2>
<div class="bd-anchor" id="ensure-java-time-support"></div>
<p>The most common cause for the JSON error is that the Jackson configuration doesn&#8217;t support the Java 8 Date and Time API.</p>
<p>Unlike older types such as <em>java.util.Date</em>, classes like <em>LocalDate</em>, <em>LocalDateTime</em>, and <em>Instant</em> are provided through the Jackson Java Time module.</p>
<p>If this module is missing, Jackson attempts to instantiate <em>LocalDate</em> like a regular Java object. Since <em>LocalDate</em> has no default constructor or string constructor, deserialization fails.</p>
<p>So, assuming we decide on Jackson <em>2.x</em>, let&#8217;s include the Java Time module dependency:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.datatype&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-datatype-jsr310&lt;/artifactId&gt;
    &lt;version&gt;2.22.1&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>If an <em>ObjectMapper</em> is created manually, it should register the module:</p>
<pre><code class="language-java">ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());</code></pre>
<p>Still, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-customize-jackson-objectmapper">Spring Boot</a> performs this registration automatically when the module is present on the classpath.</p>
<h2 id="bd-match-json-date-format" data-id="match-json-date-format">5. Match JSON Date Format</h2>
<div class="bd-anchor" id="match-json-date-format"></div>
<p>Another common cause for JSON deserialization errors is the exact date format. If the incoming JSON doesn&#8217;t use the default ISO-8601 format expected by <em>LocalDate</em> and <em>LocalDateTime</em>, we might see an error.</p>
<p>For example, let&#8217;s see a JSON string that can&#8217;t be parsed automatically:</p>
<pre><code class="language-json">{
  "time": "10/20/2020 10:00:10 AM"
}</code></pre>
<p>With the Java Time module registered, Jackson expects an ISO-8601 date-time for a <em>LocalDateTime</em> field by default:</p>
<pre><code class="language-text">2020-10-20T10:00:10</code></pre>
<p>In such cases, when the JSON data uses another format, the field should specify the expected pattern:</p>
<pre><code class="language-java">public class ImportTrans {
    @JsonFormat(pattern = "M/d/yyyy h:mm:ss a")
    private LocalDateTime time;
}</code></pre>
<p>However, this example also contains another hint. The <strong>JSON data can actually contain both a date and a time</strong>, while Java <em>LocalDate</em> fields only store a date.</p>
<h2 id="bd-correct-java-time-type" data-id="correct-java-time-type">6. Correct Java Time Type</h2>
<div class="bd-anchor" id="correct-java-time-type"></div>
<p><em>LocalDate</em> represents only a calendar date. In particular, it doesn&#8217;t contain any information about time of the day.</p>
<p><strong>If the JSON data includes hours, minutes, or seconds, <em>LocalDate</em> is no longer the appropriate target type</strong>:</p>
<pre><code class="language-java">private LocalDate time;</code></pre>
<p>Instead, we should probably use <em>LocalDateTime</em>:</p>
<pre><code class="language-java">private LocalDateTime time;</code></pre>
<p>Furthermore, the matching format can then be declared with <em>@JsonFormat</em>:</p>
<pre><code class="language-java">@JsonFormat(pattern = "M/d/yyyy h:mm:ss a")
private LocalDateTime time;</code></pre>
<p>Choosing the Java type that accurately represents the JSON payload is often the simplest solution.</p>
<h2 id="bd-localdatetime-has-similar-requirements" data-id="localdatetime-has-similar-requirements">7. <em>LocalDateTime</em> Has Similar Requirements</h2>
<div class="bd-anchor" id="localdatetime-has-similar-requirements"></div>
<p>The same type of exception frequently appears when using <em>LocalDateTime</em>:</p>
<pre><code class="language-text">Can not deserialize value of type
java.time.LocalDateTime from String</code></pre>
<p>Let&#8217;s consider the following JSON:</p>
<pre><code class="language-json">{
  "creationTime": "2016-06-16 06:56:00"
}</code></pre>
<p>Same as before, since this format differs from the default ISO-8601 representation, Jackson requires an explicit pattern:</p>
<pre><code class="language-java">@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime creationTime;</code></pre>
<p>The <em>ObjectMapper</em> must also have the Java Time module registered, while the explicit pattern is still required for this non-ISO format.</p>
<h2 id="bd-comparison" data-id="comparison">8. Comparison</h2>
<div class="bd-anchor" id="comparison"></div>
<p>Now that we know different reasons for errors around parsing JSON with date-time values with Jackson, let&#8217;s see an overview of what we found out:</p>
<table class="table-styled" style="width: 100%">
<thead>
<tr>
<th style="width: 16.8878%">Problem</th>
<th style="width: 41.3752%">Typical Error Message</th>
<th style="width: 41.6164%">Likely Cause</th>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 16.8878%">Missing module</td>
<td style="width: 41.3752%"><em>Java 8 date/time type java.time.LocalDate not supported by default</em></td>
<td style="width: 41.6164%">The Java Time module isn&#8217;t registered when using Jackson 2.x</td>
</tr>
<tr>
<td style="width: 16.8878%">Wrong format</td>
<td style="width: 41.3752%"><em>Cannot deserialize value of type java.time.LocalDate from String</em></td>
<td style="width: 41.6164%">The input string doesn&#8217;t match the expected date format</td>
</tr>
<tr>
<td style="width: 16.8878%">Wrong JSON type</td>
<td style="width: 41.3752%"><em>Cannot deserialize value of type java.time.LocalDate from Object value</em></td>
<td style="width: 41.6164%">The JSON value has an incompatible type instead of the expected string</td>
</tr>
</tbody>
</table>
<p>So, we can check the reference table whenever we encounter an unexpected error.</p>
<h2 id="bd-conclusion" data-id="conclusion">9. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored the error stating that Jackson can&#8217;t construct an instance of <em>LocalDate</em> or <em>LocalDateTime</em>.</p>
<p>Specifically, we established that it generally indicates Jackson can&#8217;t determine how to convert the incoming JSON into a Java date-time object.</p>
<p>In conclusion, once the Java type, JSON payload format, and Jackson configuration are aligned, such deserialization errors should disappear and Java Time types should be handled transparently.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-fix-jackson-json-parse-error">How to Fix Jackson JSON Parse Error <em>Can not construct instance of java.time.LocalDate</em></a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/968126756/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968126756/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/968126756/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/968126756/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/968126756/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/968126756/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-fix-jackson-json-parse-error#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-fix-jackson-json-parse-error/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/~/968126756/0/baeldung~How-to-Fix-Jackson-JSON-Parse-Error-Can-not-construct-instance-of-javatimeLocalDate/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-microks-api-mocking-testing</feedburner:origLink>
		<title>API Mocking and Testing With Microcks</title>
		<link>https://feeds.feedblitz.com/~/968126759/0/baeldung~API-Mocking-and-Testing-With-Microcks</link>
					<comments>https://feeds.feedblitz.com/~/968126759/0/baeldung~API-Mocking-and-Testing-With-Microcks#respond</comments>
		
		<dc:creator><![CDATA[Andrei Branza]]></dc:creator>
		<pubDate>Sat, 22 Aug 2026 23:20:24 +0000</pubDate>
				<category><![CDATA[Testing]]></category>
		<category><![CDATA[Testcontainers]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204680</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 use Testcontainers integration to spin up Microcks inside a JUnit5 test.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/968126759/0/baeldung~API-Mocking-and-Testing-With-Microcks">API Mocking and Testing With Microcks</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968126759/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/968126759/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/968126759/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/968126759/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/968126759/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-microks-api-mocking-testing#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-microks-api-mocking-testing/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>In this tutorial, we&#8217;ll look at Microcks, an open-source, Kubernetes-native platform for turning API and microservices contracts into live mocks. We&#8217;ll also see how to reuse the same contracts to run conformance tests against a real implemenation.</p>
<p>We&#8217;ll cover the core concepts first, then use the Testcontainers integration to spin up Microcks inside a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/junit-5">JUnit5</a> test, import an OpenAPI contract, call the generated mock, and validate a running service against it.</p>
<h2 id="bd-what-is-microcks" data-id="what-is-microcks">2. What Is Microcks?</h2>
<div class="bd-anchor" id="what-is-microcks"></div>
<p><strong>Microcks is a CNCF (Cloud Native Computing Foundation) project for API mocking and testing. </strong>It consumes API and microservice artifacts, such as OpenAPI specs, AsyncAPI specs, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/grpc-introduction">gRPC</a> protobuf files, GraphQL schemas, Postman collections, and SoapUI projects. Then, it turns them into live, stateful mocks in seconds.</p>
<p><strong>Beyond mocking, Microcks reuses these same artifacts to run contract conformance and non-regression tests against a real API implementation. </strong>It also integrates with Jenkins, GitHub Actions and Tekton pipelines through a CLI, so both mocking and testing can be automated across the whole delivery chain.</p>
<p>We can run Microcks as a standalone server, using Docker Compose or a Kubernetes/Helm deployment. For local development and CI, though, it&#8217;s more convenient to embed it directly inside a test using the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/docker-test-containers">Testcontainers</a> module.</p>
<p>Microcks isn&#8217;t limited to synchronous REST APIs, either. Through its ensemble mode, it can also mock and contract-test SOAP, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/graphql">GraphQL</a>, and gRPC services, as well as event-driven APIs described with AsyncAPI, over brokers like <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-kafka">Kafka</a>, SQS or SNS. <strong>That means the same tool and the same workflow can cover most of the API styles a typical microservices architecture is likely to use.</strong></p>
<h2 id="bd-setting-up-microcks-with-testcontainers" data-id="setting-up-microcks-with-testcontainers">3. Setting Up Microcks With Testcontainers</h2>
<div class="bd-anchor" id="setting-up-microcks-with-testcontainers"></div>
<p>The <em>microcks-testcontainers </em>library lets us start a lightweight, throwaway Microcks instance for the lifetime of a test class.</p>
<p>First, let&#8217;s add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/io.github.microcks/microcks-testcontainers">Maven</a> dependency:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;io.github.microcks&lt;/groupId&gt;
    &lt;artifactId&gt;microcks-testcontainers&lt;/artifactId&gt;
    &lt;version&gt;0.4.4&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Next, let&#8217;s create and start a <em>MicrocksContainer, </em>pointing it to an OpenAPI contract for our API:</p>
<pre><code class="language-java">MicrocksContainer microcks = new MicrocksContainer(
  DockerImageName.parse("quay.io/microcks/microcks-uber:1.14.0"))
  .withMainArtifacts("apipastries-openapi.yaml");
microcks.start();</code></pre>
<p>The <em>uber </em>image bundles everything Microcks needs without an external MongoDB dependency, which keeps startup fast. We can also import artifacts after startup, using <em>importAsMainArtifact() </em>or <em>importAsSecondaryArtifact() </em>on the running container.</p>
<h2 id="bd-mocking-the-api" data-id="mocking-the-api">4. Mocking the API</h2>
<div class="bd-anchor" id="mocking-the-api"></div>
<p>Once the container has imported the contract, Microcks automatically exposes a mock endpoint for every operation. We can retrieve the base URL and call it like any real API:</p>
<pre><code class="language-java">String baseApiUrl = microcks.getRestMockEndpoint("API Pastries", "0.0.1");
HttpResponse&lt;String&gt; response = client.send(
  HttpRequest.newBuilder(URI.create(baseApiUrl + "/pastries/Millefeuille")).GET().build(),
  HttpResponse.BodyHandlers.ofString());
assertEquals(200, response.statusCode());</code></pre>
<p><strong>Microcks generates realistic responses straight from the examples defined in the OpenAPI contract.</strong> Then, it cycles through them so repeated calls don&#8217;t always return the same payload. This means consumers can be developed and tested against a dependency that doesn&#8217;t exist yet, or isn&#8217;t reacheable from a test environment.</p>
<p>Furthermore, there are ways we can confirm a mock was actually called. We&#8217;ll do this by either calling <em>microcks.verify(&#8220;API Pastries&#8221;,&#8221;0.0.1&#8243;)</em>, or by checking the exact invocation count through <em>microcks.getServiceInvocationsCount(&#8220;API Pastries&#8221;,&#8221;0.0.1&#8243;)</em>.</p>
<h2 id="bd-running-contract-tests" data-id="running-contract-tests">5. Running Contract Tests</h2>
<div class="bd-anchor" id="running-contract-tests"></div>
<p>The same contract can validate a real implementation running on our machine. First, we need to expose the port with Testcontainers.exposeHostPorts() before starting the container, not after. We do this since the Microcks container needs to reach back out to our host:</p>
<pre><code class="language-java">Testcontainers.exposeHostPorts(port);
microcks.start();</code></pre>
<p>Then, let&#8217;s launch a conformance test against our locally running server:</p>
<pre><code class="language-java">TestRequest testRequest = new TestRequest.Builder()
  .serviceId("API Pastries:0.0.1")
  .runnerType(TestRunnerType.OPEN_API_SCHEMA.name())
  .testEndpoint("http://host.testcontainers.internal:" + port)
  .filteredOperations(List.of("GET /pastries/{name}"))
  .timeout(Duration.ofSeconds(2))
  .build();
TestResult testResult = microcks.testEndpoint(testRequest);
assertTrue(testResult.isSuccess());</code></pre>
<p>We scoped the test to a single operation with filteredOperations(). Without it, Microcks also replays the GET /pastries and PATCH /pastries/{name} examples from the contract, which fail against an implementation that doesn&#8217;t cover them yet.</p>
<p><strong>Here, Microcks replays every example from the OpenAPI contract against our running application and checks each response against the schema. </strong>For a more JUnit-friendly failure report, we can use the <em>Assertions </em>helper instead, <em>Assertions.assertSuccess(testResult);</em></p>
<p>This single call runs the same conformance check that a CI pipeline would run against a live deployment, only locally and in milliseconds.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we introduced Microcks as a tool for turning API contracts into live mocks and reusing those same contracts for conformance testing. We used its Testcontainers integration to start a throwaway Microcks instance inside a JUnit test, import an OpenAPI contract, call the generated mock, and run a contract test against a live implementation.</p>
<p><strong>Because mocks and tests are dervide from the same source contract, both stay in sync with the API definition. </strong>That removes a common source of drift between what an API promises and what it actually does.</p>
<p>As always, the code is available over on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/testing-modules">Github</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-microks-api-mocking-testing">API Mocking and Testing With Microcks</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/968126759/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968126759/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/968126759/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/968126759/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/968126759/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/968126759/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-microks-api-mocking-testing#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-microks-api-mocking-testing/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/~/968126759/0/baeldung~API-Mocking-and-Testing-With-Microcks/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-boot-http-client-ssrf-mitigation-inetaddressfilter</feedburner:origLink>
		<title>HTTP Client SSRF Mitigation with InetAddressFilter in Spring Boot</title>
		<link>https://feeds.feedblitz.com/~/968126762/0/baeldung~HTTP-Client-SSRF-Mitigation-with-InetAddressFilter-in-Spring-Boot</link>
					<comments>https://feeds.feedblitz.com/~/968126762/0/baeldung~HTTP-Client-SSRF-Mitigation-with-InetAddressFilter-in-Spring-Boot#respond</comments>
		
		<dc:creator><![CDATA[Hamid Reza Sharifi]]></dc:creator>
		<pubDate>Sat, 22 Aug 2026 23:16:19 +0000</pubDate>
				<category><![CDATA[Spring Boot]]></category>
		<category><![CDATA[Spring Boot 4]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204677</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 the InetAddressFilter class in a Spring-based application to limit the address that an HTTP client can call.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/968126762/0/baeldung~HTTP-Client-SSRF-Mitigation-with-InetAddressFilter-in-Spring-Boot">HTTP Client SSRF Mitigation with InetAddressFilter in Spring Boot</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968126762/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/968126762/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/968126762/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/968126762/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/968126762/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-boot-http-client-ssrf-mitigation-inetaddressfilter#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-boot-http-client-ssrf-mitigation-inetaddressfilter/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>It’s sometimes useful to limit the remote addresses that an HTTP client is permitted to call. It helps prevent applications from connecting to unintended destinations and provides an effective defense against Server-Side Request Forgery (<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/server-side-request-forgery">SSRF</a>) attacks. <strong>To limit the address that an HTTP client can call, we can use an <em>InetAddressFilter.</em> The filter determines which addresses are allowed for outgoing requests.</strong> In this tutorial, we’ll explore how to use the <em>InetAddressFilter</em> class in a Spring-based application.</p>
<h2 id="bd-understanding-ssrf-in-the-context-of-http-clients" data-id="understanding-ssrf-in-the-context-of-http-clients">2. Understanding SSRF in the Context of HTTP Clients</h2><div class="bd-anchor" id="understanding-ssrf-in-the-context-of-http-clients"></div>
<p><strong>SSRF is a critical security vulnerability that allows an attacker to force a server to make requests to unintended locations. These requests may target internal systems, cloud metadata endpoints, or loopback addresses.</strong> To demonstrate how SSRF can occur in an HTTP client, let&#8217;s consider a simple endpoint that accepts a URL and uses <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-restclient"><em>RestClient</em></a> to retrieve its contents:</p>
<pre><code class="language-java">@GetMapping("/fetch")
public String fetch(@RequestParam String url) {
    return restClient.get()
      .uri(url)
      .retrieve()
      .body(String.class);
}</code></pre>
<p>The endpoint accepts a URL as a request parameter and forwards it to <em>RestClient</em>. The intention is to allow users to retrieve content from public URLs. For example, a legitimate request might look like:</p>
<pre><code class="language-plaintext">curl --location http://127.0.0.1:8080/fetch?url=https://www.baeldung.com</code></pre>
<p>However, an attacker could provide a URL that points to an internal resource instead:</p>
<pre><code class="language-plaintext">curl --location http://127.0.0.1:8080/fetch?url=admin</code></pre>
<p>Instead of contacting a public website, our server now makes requests to its own internal network. This can expose internal admin APIs, databases, Kubernetes services, etc. This class of vulnerability is known as SSRF. Historically, mitigating SSRF in Spring Boot applications required custom <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-rest-template-interceptor#interceptor"><em>ClientHttpRequestInterceptor</em></a> implementations or complex network-level firewalls. <strong>Spring Boot 4.1.0 introduces first-class support for SSRF mitigation via the <em>InetAddressFilter</em> interface</strong>.</p>
<h2 id="bd-maven-dependency" data-id="maven-dependency">3. Maven Dependency</h2><div class="bd-anchor" id="maven-dependency"></div>
<p>Let’s start by importing the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-restclient"><em>spring-boot-starter-restclient</em></a> dependency to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-restclient&lt;/artifactId&gt;
    &lt;version&gt;4.1.1-SNAPSHOT&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-introducing-inetaddressfilter" data-id="introducing-inetaddressfilter">4. Introducing <em>InetAddressFilter</em></h2><div class="bd-anchor" id="introducing-inetaddressfilter"></div>
<p>The <em>InetAddressFilter</em> interface is a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-8-functional-interfaces">functional interface</a>. Unlike validating the hostname or URL, <em>InetAddressFilter</em> performs its checks after DNS resolution. Spring Boot provides several built-in filters for common scenarios, such as allowing only public addresses (<em>InetAddressFilter.externalAddresses()</em>) or blocking special-purpose network ranges (<em>InetAddressFilter.internalAddresses()</em>). <strong>We can combine these filters using logical operators</strong>:</p>
<pre><code class="language-java">InetAddressFilter.of("192.168.0.0/16")
  .andNot("192.168.1.100");</code></pre>
<p>The API also supports <em>and()</em>, <em>or()</em>, and <em>negate()</em> for composing more complex rules:</p>
<pre><code class="language-java">InetAddressFilter.externalAddresses()
  .or(InetAddressFilter.of("10.20.0.0"));</code></pre>
<p>This enables expressive allowlists and blocklists without custom networking code.</p>
<h2 id="bd-applying-the-filter-globally" data-id="applying-the-filter-globally">5. Applying the Filter Globally</h2><div class="bd-anchor" id="applying-the-filter-globally"></div>
<p><strong>The simplest and most common approach is to declare an <em>InetAddressFilter</em> bean</strong>. Spring Boot then automatically applies it to every auto-configured HTTP client builder (<em>RestClient</em>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-5-webclient"><em>WebClient</em></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/rest-template"><em>RestTemplate</em></a>, etc.). Let&#8217;s configure the <em>InetAddressFilter</em> to allow connections only to external IP addresses:</p>
<pre><code class="language-java">@Configuration(proxyBeanMethods = false)
public class HttpClientSecurityConfig {
    @Bean
    public InetAddressFilter httpClientInetAddressFilter() {
        return InetAddressFilter.externalAddresses();
    }
}</code></pre>
<p>With this single bean, any client created from the auto-configured builders will reject requests whose resolved address is not external. Now, we can create a <em>RestClient</em> without any additional security configuration:</p>
<pre><code class="language-java">@Bean
RestClient restClient(RestClient.Builder builder) {
    return builder.build();
}</code></pre>
<p>Because the <em>RestClient.Builder</em> has already been customized by Spring Boot, the resulting <em>RestClient</em> automatically uses the configured <em>InetAddressFilter</em>. As a result, any request whose resolved destination is not an external address—for example, a loopback or private IP address—is rejected before a connection is established. This approach centralizes the application&#8217;s outbound network policy in a single place and ensures that all auto-configured HTTP clients consistently enforce the same SSRF protection.</p>
<h2 id="bd-applying-the-filter-per-client" data-id="applying-the-filter-per-client">6. Applying the Filter Per-Client</h2><div class="bd-anchor" id="applying-the-filter-per-client"></div>
<p>In some scenarios, applying a global <em>InetAddressFilter</em> may be too restrictive. For example, an application might communicate with both public APIs and specific internal services, each requiring different outbound access rules. In such cases, <strong>we can apply an <em>InetAddressFilter</em> to an individual HTTP client by configuring the underlying <em>HttpClientSettings</em> when creating the client</strong>. Let&#8217;s create a <em>RestClient</em> that only allows requests to external addresses:</p>
<pre><code class="language-java">@Bean
RestClient restClient(RestClient.Builder builder) {
    InetAddressFilter onlyExternalAddresses = InetAddressFilter.externalAddresses();
    HttpClientSettings settings = HttpClientSettings.defaults()
      .withInetAddressFilter(onlyExternalAddresses);
    ClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryBuilder
      .jdk().build(settings);
    return builder
      .requestFactory(requestFactory)
      .build();
}</code></pre>
<p>Here, the filter is attached only to this specific <em>RestClient</em> instance. Other HTTP clients in the application can use different security policies or remain unaffected. This approach is useful when different integrations have different network requirements. For example, a client used to call third-party APIs can be restricted to public addresses, while another client used for communication with trusted internal services can use a more specific allowlist. By configuring the filter at the client level, we can apply fine-grained outbound request controls without changing the behavior of the entire application.</p>
<h2 id="bd-test-1" data-id="test-1">7. Test</h2><div class="bd-anchor" id="test-1"></div>
<p>To verify that the configured <em>InetAddressFilter</em> is applied, we can write a unit test that uses the configured <em>RestClient</em> bean to access a loopback address.<strong> The application is configured with <em>InetAddressFilter.externalAddresses()</em>, which permits only publicly routable IP addresses.</strong> Since <em>127.0.0.1</em> is a loopback address, the request is rejected before an HTTP connection is established. Let&#8217;s create a test case:</p>
<pre><code class="language-java">@Test
void whenCallingLoopbackAddress_thenFilteredHostExceptionIsThrown() {
    assertThatThrownBy(() -&gt;
        restClient.get()
          .uri("https://127.0.0.1:8080")
          .retrieve()
          .toBodilessEntity()
    ).isInstanceOf(FilteredHostException.class)
     .hasMessageContaining("127.0.0.1");
}</code></pre>
<p>When the test executes, the <em>RestClient</em> resolves the destination host and passes the resulting <em>InetAddress</em> to the configured <em>InetAddressFilter</em>. Because the address does not satisfy the <em>externalAddresses()</em> policy, Spring Boot throws a <em>FilteredHostException</em> and aborts the request before opening a network connection.</p>
<h2 id="bd-conclusion" data-id="conclusion">8. Conclusion</h2><div class="bd-anchor" id="conclusion"></div>
<p>In this tutorial, we explored how SSRF can affect applications that make outbound HTTP requests based on user-controlled input. We also looked at how Spring Boot 4.1 simplifies SSRF mitigation with <em>InetAddressFilter</em>. It allows applications to validate the resolved destination address before establishing an outbound connection. The feature integrates with Spring&#8217;s HTTP client infrastructure, including both blocking and reactive clients, and provides a flexible API for composing allowlists and blocklists. By applying <em>InetAddressFilter</em>, we can enforce outbound network access policies at the HTTP client level. This reduces the risk of unauthorized access to internal resources while keeping the application code clean and maintainable.</p>
<p>As always, the source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-client-2">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-http-client-ssrf-mitigation-inetaddressfilter">HTTP Client SSRF Mitigation with InetAddressFilter in Spring Boot</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/968126762/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968126762/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/968126762/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/968126762/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/968126762/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/968126762/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-boot-http-client-ssrf-mitigation-inetaddressfilter#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-boot-http-client-ssrf-mitigation-inetaddressfilter/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/~/968126762/0/baeldung~HTTP-Client-SSRF-Mitigation-with-InetAddressFilter-in-Spring-Boot/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-db-scheduler</feedburner:origLink>
		<title>DB-Scheduler: A Persistent, Cluster-Friendly Scheduler for Java</title>
		<link>https://feeds.feedblitz.com/~/968126534/0/baeldung~DBScheduler-A-Persistent-ClusterFriendly-Scheduler-for-Java</link>
					<comments>https://feeds.feedblitz.com/~/968126534/0/baeldung~DBScheduler-A-Persistent-ClusterFriendly-Scheduler-for-Java#respond</comments>
		
		<dc:creator><![CDATA[Graham Cox]]></dc:creator>
		<pubDate>Sat, 22 Aug 2026 23:11:36 +0000</pubDate>
				<category><![CDATA[Persistence]]></category>
		<category><![CDATA[Quartz]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-db-scheduler</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-04-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 db-scheduler library to store, manage and execute persistent scheduled tasks in Java.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/968126534/0/baeldung~DBScheduler-A-Persistent-ClusterFriendly-Scheduler-for-Java">DB-Scheduler: A Persistent, Cluster-Friendly Scheduler for Java</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968126534/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/968126534/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-04-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/968126534/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/968126534/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/968126534/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-db-scheduler#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-db-scheduler/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-04-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-04-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-04-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-04-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-04-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-04-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-04.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/kagkarlsson/db-scheduler">db-scheduler</a>, a simpler alternative to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/quartz">Quartz</a>. We’ll see what it is, how to use it and what we can do with it.</p>
<h2 id="bd-what-is-db-scheduler" data-id="what-is-db-scheduler"><strong>2. What Is DB-Scheduler?</strong></h2>
<div class="bd-anchor" id="what-is-db-scheduler"></div>
<p><strong>DB-Scheduler is a cluster-aware, persistent scheduling library</strong>. It&#8217;s designed to be simple to include in our application. Our only requirements are Java 17+ and a JDBC connection to a database with a single database table in which we store and manage our tasks.</p>
<p>Using db-scheduler, we can easily support:</p>
<ul>
<li>One-time tasks: tasks that are scheduled to run once at a specific point in time</li>
<li>Simple recurring tasks: tasks that are set up to run on a statically defined regular schedule</li>
<li>Dynamic recurring tasks: tasks that run on a regular schedule, but that we can schedule dynamically at runtime</li>
</ul>
<p>The db-scheduler library will then handle everything for us. <strong>This includes ensuring that these jobs run correctly across a cluster of services without dropping them or running them multiple times.</strong></p>
<h2 id="bd-setting-up" data-id="setting-up"><strong>3. Setting Up</strong></h2>
<div class="bd-anchor" id="setting-up"></div>
<p>Before we can use db-scheduler, we need to first set it up in our application. This includes adding it to our project and getting it running.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies"><strong>3.1. Dependencies</strong></h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p><strong>Before using db-scheduler, we need to include the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.github.kagkarlsson/db-scheduler">latest version</a> in our build, which is <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.github.kagkarlsson/db-scheduler/16.12.0">16.12.0</a> at the time of writing.</strong></p>
<p>If we’re using Maven, we can include this dependency in our <em>pom.xml</em> file:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.github.kagkarlsson&lt;/groupId&gt;
    &lt;artifactId&gt;db-scheduler&lt;/artifactId&gt;
    &lt;version&gt;16.12.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>This will bring in everything that we need to be able to use it in our application. The only required dependencies are <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jspecify-null-safety">JSpecify</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/slf4j-with-log4j2-logback">SLF4J</a> &#8211; everything else is self-contained.</p>
<h3 id="bd-2-database-schema" data-id="2-database-schema"><strong>3.2. Database Schema</strong></h3>
<div class="bd-anchor" id="2-database-schema"></div>
<p><strong>In order to work, db-scheduler needs a single database table in the database we connect it to.</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/kagkarlsson/db-scheduler#database-compatibility">DDL scripts</a> are provided for most major RDBMS engines, though it might work on others as well. For example, to use with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/kagkarlsson/db-scheduler/blob/master/db-scheduler/src/test/resources/postgresql_tables.sql">PostgreSQL</a>, we&#8217;d use DDL like this:</p>
<pre><code class="language-sql">CREATE TABLE scheduled_tasks (
  task_name TEXT NOT NULL,
  task_instance TEXT NOT NULL,
  task_data BYTEA,
  execution_time TIMESTAMP WITH TIME ZONE NOT NULL,
  picked BOOLEAN NOT NULL,
  picked_by TEXT,
  last_success TIMESTAMP WITH TIME ZONE,
  last_failure TIMESTAMP WITH TIME ZONE,
  consecutive_failures INT,
  last_heartbeat TIMESTAMP WITH TIME ZONE,
  version BIGINT NOT NULL,
  priority SMALLINT,
  PRIMARY KEY (task_name, task_instance)
);
CREATE INDEX execution_time_idx ON scheduled_tasks (execution_time);
CREATE INDEX last_heartbeat_idx ON scheduled_tasks (last_heartbeat);
CREATE INDEX priority_execution_time_idx on scheduled_tasks (priority desc, execution_time asc);</code></pre>
<p>This creates our <em>scheduled_tasks </em>table with the expected structure, ready for db-scheduler to use.</p>
<h3 id="bd-3-scheduler" data-id="3-scheduler"><strong>3.3. Scheduler</strong></h3>
<div class="bd-anchor" id="3-scheduler"></div>
<p>Finally, before we can use db-scheduler, we need to actually set up a <em>Scheduler </em>instance. <strong>This is the central class that manages scheduling all of our jobs.</strong></p>
<p>We create an instance of this by providing a <em>DataSource </em>pointing to the database containing our <em>scheduled_tasks</em> table, and a collection of all the tasks that we want to manage. We&#8217;ll see later how to create these:</p>
<pre><code class="language-java">Scheduler scheduler = Scheduler.create(dataSource)
  .startTasks(tasks)
  .registerShutdownHook()
  .build();</code></pre>
<p>This comes with a reasonable set of default configuration values:</p>
<ul>
<li>Poll every 10 seconds</li>
<li>Heartbeat every 5 minutes</li>
<li>Consider a task dead if it misses 6 heartbeats</li>
<li>Run jobs across 10 worker threads</li>
</ul>
<p>And many more. We can configure all of these when we create our scheduler:</p>
<pre><code class="language-java">Scheduler scheduler = Scheduler.create(dataSource)
  .startTasks(tasks)
  .registerShutdownHook()
  .pollingInterval(Duration.ofSeconds(2))
  .heartbeatInterval(Duration.ofMinutes(2))
  .missedHeartbeatsLimit(10)
  .threads(5)
  .build();</code></pre>
<p><strong>It&#8217;s important that multiple instances in the same cluster use at least the same heartbeat settings.</strong> If not, db-scheduler might incorrectly consider some jobs to be dead.</p>
<p>Once we have a scheduler, we need to start it running:</p>
<pre><code class="language-java">scheduler.start();</code></pre>
<p>At this point, the scheduler will now manage all of the tasks assigned to it. This includes ensuring they&#8217;re correctly scheduled and running them at the appropriate times.</p>
<h2 id="bd-simple-recurring-tasks" data-id="simple-recurring-tasks"><strong>4. Simple Recurring Tasks</strong></h2>
<div class="bd-anchor" id="simple-recurring-tasks"></div>
<p><strong>The easiest tasks to work with are simple recurring tasks. For these, we need a unique name, a frequency at which the task runs, and the task itself:</strong></p>
<pre><code class="language-java">RecurringTask&lt;Void&gt; task = Tasks.recurring("my-hourly-task", FixedDelay.ofHours(1))
  .execute((instance, context) -&gt; {
    LOG.info("Executed!");
  });</code></pre>
<p>If this is a new task that the scheduler has never seen before, it will be scheduled to run immediately. After that, the scheduler will run it according to the provided schedule. In this case, that means 1 hour after the previous iteration finished. We can also use <em>Daily </em>for running at a particular time every day, and <em>CronSchedule </em>for using a more complicated <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cron-expressions">cron expression</a>.</p>
<p>Our task itself is provided as an instance of <em>VoidExecutionHandler&lt;T&gt;</em>, which is itself a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-8-lambda-expressions-tips#functionalInterface">functional interface</a> and can be implemented by a lambda if we wish. The only method of this interface takes two parameters:</p>
<ul>
<li><em>TaskInstance&lt;T&gt; instance</em> &#8211; Details about this instance of the task. Useful if we&#8217;ve got multiple schedules using the same task implementation.</li>
<li><em>ExecutionContext context </em>&#8211; Details about the executor itself. Useful if we want to access the scheduler itself from within our task.</li>
</ul>
<p>Note that we have <em>VoidExecutionHandler</em> and <em>RecurringTask&lt;Void&gt;</em> here. This is because our example task is stateless. We can also support stateful tasks, which have an initial state that can be updated as a result of the task executing. However, these are out of the scope of this article.</p>
<h2 id="bd-one-time-tasks" data-id="one-time-tasks"><strong>5. One-Time Tasks</strong></h2>
<div class="bd-anchor" id="one-time-tasks"></div>
<p><strong>One-time tasks work a bit differently. In this case, we need to distinguish between the task itself and a descriptor that identifies the task:</strong></p>
<pre><code class="language-java">TaskDescriptor&lt;String&gt; taskDescriptor = TaskDescriptor.of("my-onetime-task", String.class);
Task&lt;String&gt; task = Tasks.oneTime(taskDescriptor)
  .execute((inst, ctx) -&gt; {
    LOG.info("Executed! Custom data {}, Instance {}", inst.getData(), inst.getId());
  });
</code></pre>
<p>This task definition accepts some data that we provide when we schedule the task, and can act on that data as appropriate. In this case, the data is a <em>String</em>, but it can be any type that db-scheduler is able to serialize. By default, this uses <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-serialization">Java Serialization</a> but db-scheduler also supports other mechanisms, such as Jackson and Gson.</p>
<p>We then need to register the task with our scheduler. However, we do this by passing the task to the <em>Scheduler.create()</em> method so that db-scheduler knows about it but doesn&#8217;t immediately start running it:</p>
<pre><code class="language-java">Scheduler scheduler = Scheduler.create(dataSource, task)
  .build();
</code></pre>
<p><strong>At this point, we can run our task at any time using our scheduler and task descriptor:</strong></p>
<pre><code class="language-java">scheduler.schedule(taskDescriptor.instance(UUID.randomUUID().toString())
  .data("Hello")
  .scheduledTo(Instant.now().plusSeconds(5))
);
</code></pre>
<p>Here we have to provide a unique instance ID for our task, and indicate when the task should run. We&#8217;ve also provided some data for our task to act on. The scheduler will then ensure that this task runs correctly at the desired time.</p>
<h2 id="bd-dynamic-recurring-tasks" data-id="dynamic-recurring-tasks"><strong>6. Dynamic Recurring Tasks</strong></h2>
<div class="bd-anchor" id="dynamic-recurring-tasks"></div>
<p><strong>Dynamic recurring tasks are tasks that are registered dynamically at runtime, similar to our one-time tasks. However, once registered, they&#8217;ll continue to run on a schedule similar to our simple recurring tasks.</strong></p>
<p>We create and start these exactly the same as for our one-time task, only using <em>Tasks.recurring()</em> instead:</p>
<pre><code class="language-java">TaskDescriptor&lt;String&gt; taskDescriptor = TaskDescriptor.of("my-dynamic-recurring-task", String.class);
Task&lt;String&gt; task = Tasks.recurring(taskDescriptor, new CronSchedule("*/5 * * * * ?", ZoneId.of("UTC")))
  .execute((inst, ctx) -&gt; {
    LOG.info("Executed! Custom data {}, Instance {}", inst.getData(), inst.getId());
  });
scheduler.schedule(taskDescriptor.instance(UUID.randomUUID().toString())
  .data("Hello")
  .scheduledTo(Instant.now().plusSeconds(15))
);
</code></pre>
<p>This will now start our task at the given time and then, once started, it&#8217;ll run at the given schedule. In this case, it first runs 15 seconds after the current time, and then every 5 seconds after that.</p>
<p>If we wish, we can schedule as many different instances of these as we need, and they&#8217;ll all run on their own schedule without interfering with each other.</p>
<h2 id="bd-use-with-spring-boot" data-id="use-with-spring-boot"><strong>7. Use With Spring Boot</strong></h2>
<div class="bd-anchor" id="use-with-spring-boot"></div>
<p><strong>If we&#8217;re using Spring Boot, db-scheduler provides a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-starters">starter</a> dependency that we can use:</strong></p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.github.kagkarlsson&lt;/groupId&gt;
    &lt;artifactId&gt;db-scheduler-spring-boot-4-starter&lt;/artifactId&gt;
    &lt;version&gt;16.12.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>If we&#8217;re using Spring Boot 4.x then we need to use <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.github.kagkarlsson/db-scheduler-spring-boot-4-starter">db-scheduler-spring-boot-4-starter</a></em>. If we&#8217;re still on Spring Boot 3.x then instead it&#8217;s <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.github.kagkarlsson/db-scheduler-spring-boot-starter">db-scheduler-spring-boot-starter</a></em>.</p>
<p>This will automatically create and start our <em>Scheduler </em>instance as a Spring bean. On doing this, it&#8217;ll also automatically discover any tasks that have been created as Spring beans and register them with this scheduler. This means that the only thing we need to do is create the tasks themselves.</p>
<p>If we want to configure the <em>Scheduler </em>in any way, we can do this using properties in our standard way:</p>
<pre><code class="language-properties">db-scheduler.enabled=true
db-scheduler.polling-interval=5s
db-scheduler.heartbeat-interval=2m
db-scheduler.missed-heartbeats-limit=10
db-scheduler.threads=5</code></pre>
<p><strong>Everything else about using db-scheduler, including how we create and schedule tasks, is exactly the same as before</strong>. The starter just makes it simpler to get started in the first place.</p>
<h2 id="bd-conclusion" data-id="conclusion"><strong>8. Conclusion</strong></h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we took a very quick look at db-scheduler, including how to schedule recurring and one-time tasks. There’s a lot more that we can do with this. Next time we need to manage scheduled jobs 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><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-db-scheduler">DB-Scheduler: A Persistent, Cluster-Friendly Scheduler for Java</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/968126534/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968126534/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/968126534/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-04-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/968126534/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/968126534/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/968126534/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-db-scheduler#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-db-scheduler/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/~/968126534/0/baeldung~DBScheduler-A-Persistent-ClusterFriendly-Scheduler-for-Java/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-04-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-660</feedburner:origLink>
		<title>Java Weekly, Issue 660</title>
		<link>https://feeds.feedblitz.com/~/968116088/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/968116088/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sat, 22 Aug 2026 11:26:18 +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=204721</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>A clear Hibernate speedup and a new JSON API coming to Java.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/968116088/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 660</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968116088/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/968116088/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/968116088/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/968116088/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/968116088/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-660#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-660/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://in.relation.to/2026/08/19/hibernate-immutable-2lc-9x-speedup/">&gt;&gt; When @Immutable Made All the Difference: A 3.2x Speedup with Hibernate Second-Level Cache</a></strong> [<span style="color: #993300;">relation.to</span>]</p>
<p>Hibernate’s second-level cache jumps from 286 seconds to 90 seconds when <code>@Immutable</code>, <code>@Cache(READ_ONLY)</code>, and field-level <code>@Mutability(Immutability.class)</code> work together. This case study also shows why omitting the JSONB field annotation caused an 8.1x slowdown. Interesting read.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/08/java-native-json-api/">&gt;&gt; JEP 540 Proposed to Target JDK 28 with a Simple JSON API</a></strong> [<span style="color: #993300;">infoq.com</span>]</p>
<p>JEP 540 is moving toward JDK 28 with an incubating, dependency-free, deliberately-small JSON API for straightforward parsing and generation.</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/gradle-metadata-renovate-integration/" target="_blank" rel="noopener"><strong>Solving Gradle metadata and Renovate integration</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/introducing-boxlang-ai-explorer-a-local-catalog-for-every-ai-pattern/" target="_blank" rel="noopener"><strong>Introducing BoxLang AI Explorer: A Local Catalog for Every AI Pattern</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/vibe-coding-maven-and-the-dependencies-you-didnt-choose/" target="_blank" rel="noopener"><strong>Vibe Coding, Maven, and the Dependencies You Didn&#8217;t Choose</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/08/14/java-exhaustiveness-guide/" target="_blank" rel="noopener"><strong>Preparing for Change: Safe Switching over Sealed APIs</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://blogs.oracle.com/java/jdk-21-approaches-end-of-permissive-license" target="_blank" rel="noopener"><strong>JDK 21 approaches end-of-permissive license</strong></a> [<span style="color: #800000;">oracle.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/08/exploring-compose-html-for-server-side-rendering/" target="_blank" rel="noopener"><strong>Exploring Compose HTML for Server Side Rendering</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://marcphilipp.de/blog/2026/08/16/renovate-teaching-an-old-dog-new-tricks/" target="_blank" rel="noopener"><strong>Renovate: teaching an old dog new tricks</strong></a> [<span style="color: #800000;">marcphilipp.de</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wimdeblauwe.com/blog/2026/08/17/writing-a-thymeleaf-component-library---part-4/" target="_blank" rel="noopener"><strong>Writing a Thymeleaf component library &#8211; part 4</strong></a> [<span style="color: #800000;">wimdeblauwe.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/build-secure-ai-chat-applications-with-boxlang-rag-ollama-and-amazon-bedrock-with-dan-card/" target="_blank" rel="noopener"><strong>Build Secure AI Chat Applications with BoxLang, RAG, Ollama, and Amazon Bedrock with Dan Card</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/08/15/podcast-066/" target="_blank" rel="noopener"><strong>Episode 66 “JSON API, Valhalla Progress, LTS <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2764.png" alt="❤" class="wp-smiley" style="height: 1em; max-height: 1em;" /> PQC” [IJN]</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://nipafx.dev/inside-java-newscast-114" target="_blank" rel="noopener"><strong>JSON API, Valhalla Progress, LTS <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2764.png" alt="❤" class="wp-smiley" style="height: 1em; max-height: 1em;" /> PQC &#8211; Inside Java Newscast #114</strong></a> [<span style="color: #800000;">nipafx.dev</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/08/17/spring-office-hours-podcast-S5E20" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E20 &#8211; The Developer&#8217;s Guide to AI with Danny Thompson</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/08/13/a-bootiful-podcast-andrey-santrosyan" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Redouble AI CTO and founder Andrey Santrosyan</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-38-3-released/" target="_blank" rel="noopener"><strong>Quarkus 3.38.3 &#8211; Maintenance release</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/a2a-java-sdk-1-2-0-final-released/" target="_blank" rel="noopener"><strong>A2A Java SDK 1.2.0.Final released</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-38-2-released/" target="_blank" rel="noopener"><strong>Quarkus 3.38.2 &#8211; Maintenance release</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://toomuchcoding.com/post/2026-08-20-stubborn-0-1-0-release/" target="_blank" rel="noopener"><strong>Stubborn Contract 0.1.0 released!</strong></a> [<span style="color: #800000;">toomuchcoding.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/hive/releases/tag/rel%2Frelease-4.2.1" target="_blank" rel="noopener"><strong>Apache Hive 4.2.1</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.11" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.11</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/langchain4j/langchain4j/releases/tag/1.19.0" target="_blank" rel="noopener"><strong>LangChain4j 1.19.0</strong></a> [<span style="color: #800000;">github.com/langchain4j</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/4.5.3" target="_blank" rel="noopener"><strong>Helidon 4.5.3</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/3.2.20" target="_blank" rel="noopener"><strong>3.2.20</strong></a> [<span style="color: #800000;">github.com/helidon-io</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/rachels-ramblings/citizens-agents-experts.html">&gt;&gt; Citizens Build, Agents Execute, Experts Govern</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>
<p>AI agents are making it dramatically easier to build working software, but enterprise delivery still depends on engineering judgment. Of course it does.</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://lucumr.pocoo.org/2026/8/19/what-is-reasoning/" target="_blank" rel="noopener"><strong>What Is Reasoning</strong></a> [<span style="color: #800000;">pocoo.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://nurkiewicz.com/2026/08/compiling-to-intermediate-representation-write-yourself-a-compiler.html" target="_blank" rel="noopener"><strong>Compiling to intermediate representation: Write yourself a compiler, Part III</strong></a> [<span style="color: #800000;">nurkiewicz.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/idempotent-write-paths-keeping-an-integration-pipeline-correct-when-messages-repeat/" target="_blank" rel="noopener"><strong>Idempotent Write Paths: Keeping an Integration Pipeline Correct When Messages Repeat</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://henrikwarne.com/2026/08/15/lessons-learned-from-fixing-flaky-tests-with-claude/" target="_blank" rel="noopener"><strong>Lessons Learned from Fixing Flaky Tests with Claude</strong></a> [<span style="color: #800000;">henrikwarne.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/practitioner-voice.html" target="_blank" rel="noopener"><strong>Practitioner Voice: The Writing Category Nobody has Named Yet</strong></a> [<span style="color: #800000;">martinfowler.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/08/17/incremental-steps-data-democratisation.html" target="_blank" rel="noopener"><strong>How to take incremental steps towards data democratisation by Andy Scotland</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/08/14/ai-success-depends-on-data-foundations.html" target="_blank" rel="noopener"><strong>AI success depends on data foundations by James Heward</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.medium.com/behind-the-scenes-evolving-netflixs-ads-event-pipeline-for-live-part-ii-826ebf9ad9fb" target="_blank" rel="noopener"><strong>Behind the Scenes: Evolving Netflix&#8217;s Ads Event Pipeline for Live &#8211; Part II</strong></a> [<span style="color: #800000;">medium.com</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://alex.draftist.io/blog/the-bedrock-of-software-design-ycqvcedsj">&gt;&gt; The Bedrock of Software Design</a></strong> [<span style="color: #993300;">alex.draftist.io</span>]</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-660">Java Weekly, Issue 660</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/968116088/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/968116088/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/968116088/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/968116088/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/968116088/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/968116088/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-660#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-660/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/~/968116088/0/baeldung~Java-Weekly-Issue/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-659</feedburner:origLink>
		<title>Java Weekly, Issue 659</title>
		<link>https://feeds.feedblitz.com/~/967864352/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/967864352/0/baeldung~Java-Weekly-Issue#comments</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sat, 15 Aug 2026 10:23:09 +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=204657</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>Equality in Java is evolving.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/967864352/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 659</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967864352/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/967864352/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/967864352/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/967864352/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/967864352/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-659#comments"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-659/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&nbsp;
<div style="clear:left;"><a rel="NOFOLLOW" href="https://www.baeldung.com/java-weekly-659#comments"><h3>Comments</h3></a><ul><li><a rel="NOFOLLOW" href="https://www.baeldung.com/java-weekly-659#comment-15701">In reply to Vincent.   Hey, Vincent.   Thanks for the feedback. ...</a> <i>by Ulisses Lima</i><li><a rel="NOFOLLOW" href="https://www.baeldung.com/java-weekly-659#comment-15700">You have encoding prolem on your URL :    with Helidonâ€ ...</a> <i>by Vincent</i></ul></div>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/08/12/java-mcp-migration/">&gt;&gt; Evolving a Java MCP Server During MCP Specification Upgrades</a></strong> [<span style="color: #993300">inside.java</span>]</p>
<p>A practical guide to evolving a Helidon-based Java MCP server to the stateless. Header-based routing and a temporary adapter keep older clients working while tests exercise both protocol paths. Good stuff.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/08/jep401-value-objects-preview/">&gt;&gt; Project Valhalla&#8217;s First Preview: JEP 401 Redefines == for Java Objects</a></strong> [<span style="color: #993300">infoq.com</span>]</p>
<p>JEP 401 brings identity-free value classes and records to preview, changing <code>==</code> for value objects while identity classes retain reference semantics. Good weekend read.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/security-baked-into-jvm/3/" target="_blank" rel="noopener"><strong>Security Baked Into the JVM: two Subjects, one call</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/duckdb-in-spring-batch-replace-in-memory-java-loops-with-one-sql-statement/" target="_blank" rel="noopener"><strong>DuckDB in Spring Batch: Replace In-Memory Java Loops with One SQL Statement</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/push-v3-one-message-from-your-server-to-every-surface/" target="_blank" rel="noopener"><strong>Push V3: One Message From Your Server to Every Surface</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/embedding-duckdb-in-a-maven-app-and-using-it-for-things-that-arent-databases/" target="_blank" rel="noopener"><strong>Embedding DuckDB in a Maven App (and Using It for Things That Aren&#8217;t Databases)</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/08/11/efficient-java-apps-profiling-troubleshooting/" target="_blank" rel="noopener"><strong>The Power of JDK Flight Recorder: Efficient Profiling and Troubleshooting for Java Applications</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/08/07/post-quantum-cryptography-in-long-term-support-jdk-releases/" target="_blank" rel="noopener"><strong>Post-Quantum Cryptography in Long-Term Support JDK Releases</strong></a> [<span style="color: #800000">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/08/how-to-use-ai-agents-in-intellij-idea-with-acp/" target="_blank" rel="noopener"><strong>How to Use AI Agents in IntelliJ IDEA With ACP</strong></a> [<span style="color: #800000">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/08/top-5-ai-features-in-intellij-idea/" target="_blank" rel="noopener"><strong>Top 5 AI Features in IntelliJ IDEA</strong></a> [<span style="color: #800000">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/08/agent-skills-in-intellij-idea/" target="_blank" rel="noopener"><strong>Agent Skills in IntelliJ IDEA</strong></a> [<span style="color: #800000">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wimdeblauwe.com/blog/2026/08/10/writing-a-thymeleaf-component-library---part-3/" target="_blank" rel="noopener"><strong>Writing a Thymeleaf component library &#8211; part 3</strong></a> [<span style="color: #800000">wimdeblauwe.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/08/06/podcast-065/" target="_blank" rel="noopener"><strong>Episode 65 &#8220;Embracing Virtual Threads with Helidon&#8221; [I/O]</strong></a> [<span style="color: #800000">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-insights-256-what-is-floci/" target="_blank" rel="noopener"><strong>Quarkus Insights #256: What is Floci?</strong></a> [<span style="color: #800000">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/08/06/a-bootiful-podcast-gregory-green/" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Data guru Gregory Green on RabbitMQ, Valkey, Gemfire, Data Flow, 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://blog.jetbrains.com/idea/2026/08/intellij-idea-2026-2-1/" target="_blank" rel="noopener"><strong>IntelliJ IDEA 2026.2.1 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://github.com/gradle/gradle/releases/tag/v9.7.0" target="_blank" rel="noopener"><strong>Gradle 9.7.0</strong></a> [<span style="color: #800000">github.com/gradle</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/4.5.32" target="_blank" rel="noopener"><strong>Eclipse Vert.x 4.5.32</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/v9.5.1" target="_blank" rel="noopener"><strong>Elasticsearch 9.5.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.4.5" target="_blank" rel="noopener"><strong>9.4.5</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v8.19.20" target="_blank" rel="noopener"><strong>8.19.20</strong></a> [<span style="color: #800000">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.4" target="_blank" rel="noopener"><strong>Netflix Zuul 4.1.4</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/camel/releases/tag/camel-4.18.4" target="_blank" rel="noopener"><strong>Apache Camel 4.18.4</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.14.9" target="_blank" rel="noopener"><strong>4.14.9</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.22.0" target="_blank" rel="noopener"><strong>4.22.0</strong></a> [<span style="color: #800000">github.com/apache</span>]</li>
</ul>
<h2 style="text-align: left" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mnot.net/blog/2026/linting_the_web.html">&gt;&gt; Informing HTTP Extension Design with Data</a></strong> [<span style="color: #993300">mnot.net</span>]</p>
<p>Mark Nottingham tests HTTP extensions against 120 million Common Crawl responses. The results show how invisible failures and weak migration incentives undermine adoption, yielding a practical lesson: design for repeatable deployment, make errors visible, and study real usage before extending a protocol.</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.jetbrains.com/research/2026/08/programming-language-migration/" target="_blank" rel="noopener"><strong>Programming Language Migration 2025: Why Developers Switch</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://marcphilipp.de/blog/2026/08/10/one-required-check-to-rule-them-all/" target="_blank" rel="noopener"><strong>One required check to rule them all</strong></a> [<span style="color: #800000">marcphilipp.de</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://event-driven.io/en/vertical-slices-and-dependencies/" target="_blank" rel="noopener"><strong>Vertical slices, their ownership and external dependencies</strong></a> [<span style="color: #800000">event-driven.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/exploring-gen-ai/tdd-in-the-agent-loop.html" target="_blank" rel="noopener"><strong>TDD inside the agent loop &#8211; theater or actual value?</strong></a> [<span style="color: #800000">martinfowler.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/08/12/unlocking-your-data-in-collaboration.html" target="_blank" rel="noopener"><strong>Unlocking your data: the value is in collaboration</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/08/10/agile-leadership.html" target="_blank" rel="noopener"><strong>Agile Leadership</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/08/06/how-to-accelerate-agentic-adoption.html" target="_blank" rel="noopener"><strong>How to accelerate agentic adoption</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-and-why-netflix-built-a-real-time-distributed-graph-part-3-querying-the-graph-with-grpc-0f3468349607" target="_blank" rel="noopener"><strong>How and Why Netflix Built a Real-Time Distributed Graph: Part 3 &#8211; Querying the graph with gRPC</strong></a> [<span style="color: #800000">netflixtechblog.com</span>]</li>
</ul>
<h2 style="text-align: left" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://jon.chrt.dev/2026/04/15/things-you-didnt-know-about-indexes.html">&gt;&gt; Things you didn&#8217;t know about indexes</a></strong> [<span style="color: #993300">jon.chrt.dev</span>]</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-659">Java Weekly, Issue 659</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/967864352/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967864352/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/967864352/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/967864352/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/967864352/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/967864352/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-659#comments"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-659/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&nbsp;
<div style="clear:left;"><a rel="NOFOLLOW" href="https://www.baeldung.com/java-weekly-659#comments"><h3>Comments</h3></a><ul><li><a rel="NOFOLLOW" href="https://www.baeldung.com/java-weekly-659#comment-15701">In reply to Vincent.   Hey, Vincent.   Thanks for the feedback. ...</a> <i>by Ulisses Lima</i><li><a rel="NOFOLLOW" href="https://www.baeldung.com/java-weekly-659#comment-15700">You have encoding prolem on your URL :    with Helidonâ€ ...</a> <i>by Vincent</i></ul></div>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/967864352/0/baeldung~Java-Weekly-Issue/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-658</feedburner:origLink>
		<title>Java Weekly, Issue 658</title>
		<link>https://feeds.feedblitz.com/~/967388204/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/967388204/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sat, 08 Aug 2026 10:07:32 +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=204617</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>A Java movie? Cool. And Spring Cloud Contract has a new home.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/967388204/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 658</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967388204/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/967388204/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/967388204/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/967388204/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/967388204/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-658#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-658/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.infoq.com/articles/virtual-threads-after-jdk24/">&gt;&gt; Virtual Threads after JDK 24: What Changed for Production Java</a></strong> [<span style="color: #993300;">infoq.com</span>]</p>
<p>If you haven&#8217;t looked at virtual threads in a while, this is the update you need. Benchmarks, code, and a repo you can run, plus concrete Spring Boot 3 adoption guidance.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/idempotent-spring-boot-starter/" target="_blank" rel="noopener"><strong>Idempotent Spring Boot: Safe REST with One Annotation</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.petrikainulainen.net/programming/testing/introduction-to-spring-mvc-test-framework/" target="_blank" rel="noopener"><strong>Introduction to Spring MockMvc</strong></a> [<span style="color: #800000;">petrikainulainen.net</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wimdeblauwe.com/blog/2026/08/03/writing-a-thymeleaf-component-library---part-2/" target="_blank" rel="noopener"><strong>Writing a Thymeleaf component library &#8211; part 2</strong></a> [<span style="color: #800000;">wimdeblauwe.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/how-to-create-a-spring-boot-fraud-scoring-service/" target="_blank" rel="noopener"><strong>How to Create a Spring Boot Fraud Scoring Service</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/jakarta-agentic-ai-hits-its-first-milestone/" target="_blank" rel="noopener"><strong>Jakarta Agentic AI Hits Its First Milestone</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/07/31/transitioning-java-to-more-frequent-security-updates/" target="_blank" rel="noopener"><strong>Transitioning Java to More Frequent Security Updates</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/08/intellij-idea-goes-lsp/" target="_blank" rel="noopener"><strong>IntelliJ IDEA Goes LSP: Java and Kotlin Intelligence Comes to VS Code, Cursor, and Agentic Flows</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/08/embabel-1/" target="_blank" rel="noopener"><strong>Embabel Agent Framework Reaches 1.0</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/the-codename-one-javascript-port-is-now-free-and-open-source/" target="_blank" rel="noopener"><strong>The Codename One JavaScript Port Is Now Free and Open Source</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://marcphilipp.de/blog/2026/08/02/reflecting-on-stf-support-and-looking-ahead/" target="_blank" rel="noopener"><strong>Reflecting on STF support and looking ahead</strong></a> [<span style="color: #800000;">marcphilipp.de</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-banner-extension/" target="_blank" rel="noopener"><strong>Custom ASCII-art startup banners for Quarkus</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/30/under-the-hat-empowering-gpu-acceleration-for-java/" target="_blank" rel="noopener"><strong>Under the HAT: Empowering GPU Acceleration for Java</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/30/podcast-064/" target="_blank" rel="noopener"><strong>Episode 64 &#8220;JIT Compiler From the Ground Up&#8221; [AtA]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/30/a-bootiful-podcast-phil-webb" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Spring Boot founder and lead Phil Webb</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/quarkus-insights-255-faster-leaner-predictable-quarkus/" target="_blank" rel="noopener"><strong>Quarkus Insights #255: Faster, Leaner, Predictable — Quarkus in the Real World</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://www.infoq.com/podcasts/feature-evolution-performance-transition-endive/" target="_blank" rel="noopener"><strong>WebAssembly on the JVM: Feature Evolution, Performance, and the Transition to Endive</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/presentations/yk-meta-tracing-jit-compiler/" target="_blank" rel="noopener"><strong>Automatically Retrofitting JIT Compilers</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/presentations/tech-hype-cycles-architectural-tradeoffs/" target="_blank" rel="noopener"><strong>The Free-Lunch Guide to Idea Circularity</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-38-1-released/" target="_blank" rel="noopener"><strong>Quarkus 3.38.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-33-3-1-released/" target="_blank" rel="noopener"><strong>3.33.3.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-27-5-1-released/" target="_blank" rel="noopener"><strong>3.27.5.1</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/jetty/jetty.project/releases/tag/jetty-12.1.12" target="_blank" rel="noopener"><strong>Jetty 12.1.12</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.38" target="_blank" rel="noopener"><strong>12.0.38</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/eclipse-vertx/vert.x/releases/tag/5.1.6" target="_blank" rel="noopener"><strong>Vert.x 5.1.6</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/helidon-io/helidon/releases/tag/4.5.2" target="_blank" rel="noopener"><strong>Helidon 4.5.2</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://github.com/elastic/elasticsearch/releases/tag/v9.5.0" target="_blank" rel="noopener"><strong>Elasticsearch 9.5.0</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://blog.jetbrains.com/qodana/2026/07/qodana-2026-2-more-security-better-coverage-less-configuration/" target="_blank" rel="noopener"><strong>Qodana 2026.2: More Security, Better Coverage, Less Configuration</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://inside.java/2026/08/05/java-vscode-extension-update/" target="_blank" rel="noopener"><strong>Oracle Java Platform Extension for Visual Studio Code &#8211; Version 26.0.1 Is Now Available</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/exploring-gen-ai/refactoring-economic-benefit.html">&gt;&gt; The Economic Benefit of Refactoring</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>
<p>We have always known that clean code pays for itself; here is someone putting a number on it in the agentic era. Giles Edwards-Alexander took a bloated 17,155-line data-access layer, refactored it systematically, and then measured what a representative feature change cost an AI agent before and after: input tokens dropped from 159,564 to 27,360, an 83% cut. Definitely a weekend read.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/rachels-ramblings/conductor-developer.html" target="_blank" rel="noopener"><strong>The Conductor Developer</strong></a> [<span style="color: #800000;">martinfowler.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://event-driven.io/en/fixing-bugs-in-event-sourcing-is-hard/" target="_blank" rel="noopener"><strong>Fixing bugs in Event Sourcing is hard, for real?</strong></a> [<span style="color: #800000;">event-driven.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.allthingsdistributed.com/2026/08/on-building-scalable-control-planes.html" target="_blank" rel="noopener"><strong>On building scalable control planes</strong></a> [<span style="color: #800000;">allthingsdistributed.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.medium.com/timeseries-tiered-storage-journey-kafka-flink-streams-to-native-cassandra-cold-reads-e59d597c9d60" target="_blank" rel="noopener"><strong>TimeSeries Tiered Storage Journey: Kafka/Flink Streams to Native Cassandra Cold Reads</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/genrec-towards-llm-native-recommendation-at-netflix-f20be6f643e3" target="_blank" rel="noopener"><strong>GenRec: Towards LLM-Native Recommendation at Netflix</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.christianposta.com/credential-brokering-patterns-for-ai-agent-egress-part2/" target="_blank" rel="noopener"><strong>Credential Brokering Patterns for AI Agents Part 2: Protecting the Token Vault with AWS KMS</strong></a> [<span style="color: #800000;">christianposta.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/08/04/ai-migration-techniques.html" target="_blank" rel="noopener"><strong>Confidence Techniques for Guiding AI Migration</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.frankel.ch/github-agentic-workflow-renovate/" target="_blank" rel="noopener"><strong>GitHub agentic workflows and Renovate</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://hamvocke.com/blog/task-runners/" target="_blank" rel="noopener"><strong>Use Task Runners for Common Coding Tasks</strong></a> [<span style="color: #800000;">hamvocke.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/modeling-device-capabilities-for-analytics-e7607acebde8" target="_blank" rel="noopener"><strong>Modeling Device Capabilities for Analytics</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/07/29/four-pillars-of-agentic-ai-success.html" target="_blank" rel="noopener"><strong>Four pillars of agentic AI success</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.paulgraham.com/vb.html"><strong>&gt;&gt; Life is Short</strong></a> [<span style="color: #993300;">paulgraham.com</span>]</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-658">Java Weekly, Issue 658</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/967388204/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967388204/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/967388204/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/967388204/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/967388204/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/967388204/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-658#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-658/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/~/967388204/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-google-genai-chat</feedburner:origLink>
		<title>Introduction to Google GenAI Chat and Spring AI</title>
		<link>https://feeds.feedblitz.com/~/967131092/0/baeldung~Introduction-to-Google-GenAI-Chat-and-Spring-AI</link>
					<comments>https://feeds.feedblitz.com/~/967131092/0/baeldung~Introduction-to-Google-GenAI-Chat-and-Spring-AI#respond</comments>
		
		<dc:creator><![CDATA[Sudarshan Hiray]]></dc:creator>
		<pubDate>Fri, 07 Aug 2026 03:22:20 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[popular]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204581</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to integrate Google’s Gemini models into a Spring Boot application using the Gemini Developer API.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/967131092/0/baeldung~Introduction-to-Google-GenAI-Chat-and-Spring-AI">Introduction to Google GenAI Chat and Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967131092/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/967131092/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/967131092/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/967131092/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/967131092/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-google-genai-chat#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-google-genai-chat/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 data-path-to-node="0" id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p data-path-to-node="1">The generative AI landscape is evolving rapidly, pushing framework maintainers to rethink how applications connect to diverse large language models (LLMs). With the release of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai">Spring AI</a>, Java developers gained a unified, portable interface to interact with cognitive services.</p>
<p data-path-to-node="2">However, as the ecosystem matured, integrating multiple foundational models within a single runtime classpath introduced configuration conflicts. To solve this, <strong>Spring AI introduced major architectural updates. These include the specialized <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-google-genai"><em>spring-ai-starter-model-google-genai</em> starter</a> module and an explicit model selection paradigm.</strong></p>
<p data-path-to-node="3">In this tutorial, we&#8217;ll explore how to integrate Google’s Gemini models into a Spring Boot application using the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.spring.io/spring-ai/reference/api/chat/google-genai-chat.html#_option_1_gemini_developer_api_api_key">Gemini Developer API</a> (via Google AI Studio). We&#8217;ll walk through a streamlined project setup, dive into text generation patterns using both low-level and high-level APIs, apply prompt templates, enable live web grounding, and stream tokens reactively with WebFlux.</p>
<h2 data-path-to-node="4" id="bd-project-setup" data-id="project-setup">2. Project Setup</h2>
<div class="bd-anchor" id="project-setup"></div>
<p data-path-to-node="5">To get started, we need a standard Spring Boot 3.x application. Because Spring AI modules are actively updated, it&#8217;s highly recommended to manage dependency versions using the Spring AI <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-maven-bom">Bill of Materials (BOM)</a>.</p>
<h3 data-path-to-node="6" id="bd-1-maven-dependencies" data-id="1-maven-dependencies">2.1. Maven Dependencies</h3>
<div class="bd-anchor" id="1-maven-dependencies"></div>
<p data-path-to-node="7">First, let&#8217;s configure the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-google-genai">Spring AI Starter Google GenAI</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-bom/2.0.0">Spring AI BOM</a> in our <em>pom.xml </em>file. <strong>Since Spring AI artifacts are hosted in the Spring Milestones repository during release cycles, we ensure both the repository and dependency management blocks are properly declared</strong>:</p>
<pre><code class="language-java">&lt;project&gt;
    &lt;dependencyManagement&gt;
        &lt;dependencies&gt;
            &lt;dependency&gt;
                &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
                &lt;artifactId&gt;spring-ai-bom&lt;/artifactId&gt;
                &lt;version&gt;2.0.0&lt;/version&gt;
                &lt;type&gt;pom&lt;/type&gt;
                &lt;scope&gt;import&lt;/scope&gt;
            &lt;/dependency&gt;
        &lt;/dependencies&gt;
    &lt;/dependencyManagement&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
            &lt;artifactId&gt;spring-ai-starter-model-google-genai&lt;/artifactId&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/project&gt;</code></pre>
<h3 data-path-to-node="5" id="bd-2-activating-the-chat-provider-via-springaimodelchat" data-id="2-activating-the-chat-provider-via-springaimodelchat">2.2. Activating the Chat Provider via <em>spring.ai.model.chat</em></h3>
<div class="bd-anchor" id="2-activating-the-chat-provider-via-springaimodelchat"></div>
<p data-path-to-node="6">In the latest versions of Spring AI, dropping a starter module onto the classpath no longer automatically activates it. This prevents runtime configuration conflicts when your application references multiple LLM providers.</p>
<p data-path-to-node="7"><strong>We must explicitly declare our active chat provider inside our <em>application.properties</em> file</strong>:</p>
<pre><code class="language-java">spring.ai.model.chat=google-genai
spring.ai.google.genai.chat.model=gemini-2.5-flash
spring.ai.google.genai.api-key=${SPRING_AI_GOOGLE_GENAI_API_KEY}</code></pre>
<p>Obtain an API key from the <a class="external" href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://aistudio.google.com/app/apikey" target="_blank" rel="noopener">Google AI Studio</a>. Using this placeholder syntax explicitly documents the application&#8217;s external runtime dependency while preventing hardcoded secrets from leaking into our source control repository.</p>
<h3 data-path-to-node="10" id="bd-3-environment-setup" data-id="3-environment-setup">2.3. Environment Setup</h3>
<div class="bd-anchor" id="3-environment-setup"></div>
<p data-path-to-node="11">We can satisfy the property placeholder defined above without writing any custom Java decryption or binding code. <strong>We&#8217;ll simply export the credentials directly into our environment</strong>. The auto-configuration engine will automatically resolve the placeholder value at startup:</p>
<pre><code class="language-java">export SPRING_AI_GOOGLE_GENAI_API_KEY="YourActualSecureGoogleAIStudioKeyHere"</code></pre>
<p>Once this environment variable is set, Spring Boot seamlessly bridges the variable to the properties file placeholder, making the<em> GoogleGenAiChatModel</em> ready for injection.</p>
<h2 data-path-to-node="22" id="bd-core-text-and-content-generation-strategies" data-id="core-text-and-content-generation-strategies">3. Core Text and Content Generation Strategies</h2>
<div class="bd-anchor" id="core-text-and-content-generation-strategies"></div>
<p data-path-to-node="23">Spring AI gives us two primary abstraction layers for communicating with Gemini models: <strong>the foundational <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-artificial-intelligence-structure-output#brief-introduction-to-the-chat-model"><em>ChatModel</em></a> bean and the highly configurable, fluent <em>ChatClient</em> API. </strong> We isolate our interaction logic into a dedicated service layer to follow enterprise design patterns and keep code clean. Then, we expose these capabilities through standard Spring REST controllers.</p>
<h3 data-path-to-node="24" id="bd-1-injecting-and-using-googlegenaichatmodel" data-id="1-injecting-and-using-googlegenaichatmodel">3.1. Injecting and Using <em>GoogleGenAiChatModel</em></h3>
<div class="bd-anchor" id="1-injecting-and-using-googlegenaichatmodel"></div>
<p data-path-to-node="24">The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/google/genai/GoogleGenAiChatModel.html"><em>GoogleGenAiChatModel </em></a>represents the foundational, low-level client abstraction.<strong> It handles request serialization, HTTP execution against Google&#8217;s RPC endpoints, and raw response parsing.</strong></p>
<p data-path-to-node="25">Let&#8217;s look at our <em>ChatService, </em>where we inject this bean along with a <em>ChatClient.Builder</em> to initialize our operational layers:</p>
<pre><code class="language-java">@Service
public class ChatService {
    private final GoogleGenAiChatModel chatModel;
    private final ChatClient defaultChatClient;
    private final ChatClient fluentChatClient;
    public ChatService(GoogleGenAiChatModel chatModel, ChatClient.Builder chatClientBuilder) {
        this.chatModel = chatModel;
        this.defaultChatClient = chatClientBuilder.build();
        this.fluentChatClient = chatClientBuilder
          .defaultSystem("You are a concise technical writer summarizing software concepts.")
          .build();
    }
    public String simplifiedPrompt(String message) {
        return chatModel.call(message);
    }
}</code></pre>
<p data-path-to-node="28">When we invoke <em>chatModel.call(message)</em>, the framework packages the raw string into a default prompt context. It then sends this to the configured model variant, extracts the text block from the response payload, and returns it. We expose this in our <em>ChatController</em>:</p>
<pre><code class="language-java">@RestController
public class ChatController {
    private final ChatService chatService;
    public ChatController(ChatService chatService) {
        this.chatService = chatService;
    }
    @GetMapping("/v1/chat/simple")
    public String simplifiedPrompt(@RequestParam(defaultValue = "Hello") String message) {
        return chatService.simplifiedPrompt(message);
    }
}</code></pre>
<p data-path-to-node="29">This establishes a simple string-in, string-out endpoint. It uses the lowest-level model client abstraction available to verify basic connectivity with the Gemini engine.</p>
<h3 data-path-to-node="29" id="bd-2-constructing-prompts-with-the-fluent-chatclient-api" data-id="2-constructing-prompts-with-the-fluent-chatclient-api">3.2. Constructing Prompts With the Fluent <em>ChatClient</em> API</h3>
<div class="bd-anchor" id="2-constructing-prompts-with-the-fluent-chatclient-api"></div>
<p data-path-to-node="30">While working directly with the model handles quick operations, production architectures favor the fluent <em>ChatClient </em>API. <strong>The <em>ChatClient </em>acts as a facade layer that simplifies prompt assembly and attaches default configuration baselines.</strong></p>
<p data-path-to-node="31">As shown in our service constructor, we pre-configured a specialized <span style="margin: 0px;padding: 0px">fluent<em>ChatClient </em>with</span> system instructions. Let&#8217;s add the corresponding execution method to <em>ChatService</em>:</p>
<pre><code class="language-java">public String fluentPrompt(String prompt) {
    return this.fluentChatClient.prompt()
      .user(prompt)
      .call()
      .content();
}</code></pre>
<p data-path-to-node="33">By routing requests through this pre-configured instance, every user query automatically appends the target system instructions before execution. We expose this endpoint in the controller:</p>
<pre><code class="language-java">@GetMapping("/v1/chat/fluent")
public String fluentPrompt(@RequestParam String prompt) {
    return chatService.fluentPrompt(prompt);
}</code></pre>
<p data-path-to-node="35">This allows us to encapsulate recurring personas or systemic behavior directly within the client wrapper instance. As a result, we don&#8217;t need to modify individual incoming text arguments manually.</p>
<h3 data-path-to-node="35" id="bd-3-handling-dynamic-inputs-via-prompt-templates" data-id="3-handling-dynamic-inputs-via-prompt-templates">3.3. Handling Dynamic Inputs via Prompt Templates</h3>
<div class="bd-anchor" id="3-handling-dynamic-inputs-via-prompt-templates"></div>
<p data-path-to-node="36">Hardcoding parameter logic directly inside strings leads to messy string concatenation and fragile codebases. <strong>Spring AI solves this with structured prompt templates, separating instructions from user variables.</strong></p>
<p data-path-to-node="37">Let&#8217;s implement a code review capability inside our <em>ChatService </em>using a multiline text block placeholder layout:</p>
<pre><code class="language-java">public String reviewCode(String language, String codeSnippet) {
    String template = """
            Analyze the following {language} code snippet for memory leaks or inefficiencies.
            Provide an optimized version.
            Code:
            {code}
            """;
    return this.defaultChatClient.prompt()
      .user(u -&gt; u.text(template).params(Map.of("language", language, "code", codeSnippet)))
      .call()
      .content();
}</code></pre>
<p>The client replaces the target tags (<em>{language}</em>,<em> {code}</em>) at runtime, formatting the text cleanly before submission. We expose this via a <em>POST </em>request wrapper in our controller:</p>
<pre><code class="language-java">@PostMapping("/v1/chat/review")
public String reviewCode(@RequestParam(defaultValue = "Java") String language, @RequestBody String codeSnippet) {
    return chatService.reviewCode(language, codeSnippet);
}</code></pre>
<p data-path-to-node="39">This pattern cleanly decouples structural prompt engineering boundaries from volatile business data, producing a highly reusable, parameter-driven invocation pattern.</p>
<h3 data-path-to-node="39" id="bd-4-grounding-responses-with-live-google-search-retrieval" data-id="4-grounding-responses-with-live-google-search-retrieval">3.4. Grounding Responses With Live Google Search Retrieval</h3>
<div class="bd-anchor" id="4-grounding-responses-with-live-google-search-retrieval"></div>
<p data-path-to-node="40">LLMs naturally suffer from training cut-off windows and knowledge gaps regarding live, real-world developments.<strong> The Google GenAI module exposes an explicit grounding property to seamlessly connect our model with live Google Search indexing.</strong></p>
<p data-path-to-node="41">To activate live search grounding across our entire application, we need to toggle the grounding flag to <em>true</em> inside our configuration properties:</p>
<pre><code class="language-java">spring.ai.google.genai.chat.google-search-retrieval=true</code></pre>
<p>When this property is enabled, queries regarding current events or breaking news are automatically grounded using fresh search results. Let&#8217;s write an endpoint to handle real-time informational requests:</p>
<pre><code class="language-java">public String searchGroundedPrompt(String currentEventQuery) {
    return this.defaultChatClient.prompt()
      .user(currentEventQuery)
      .call()
      .content();
}</code></pre>
<p data-path-to-node="45">We map this method to our HTTP layer inside our controller configuration:</p>
<pre><code class="language-java">@GetMapping("/v1/chat/grounded")
public String searchGroundedPrompt(@RequestParam String currentEventQuery) {
    return chatService.searchGroundedPrompt(currentEventQuery);
}</code></pre>
<p data-path-to-node="45">If we pass a request like <i data-path-to-node="33" data-index-in-node="26">&#8220;Who won the most recent football tournament match yesterday?&#8221;</i>, the model uses live Google Search indexing to anchor its response in verified facts, drastically reducing hallucinations. This configuration bridges the gap between static training cutoffs and live, real-world events.</p>
<h3 data-path-to-node="49" id="bd-5-processing-real-time-streaming-responses-with-flux" data-id="5-processing-real-time-streaming-responses-with-flux">3.5. Processing Real-Time Streaming Responses With <em>Flux</em></h3>
<div class="bd-anchor" id="5-processing-real-time-streaming-responses-with-flux"></div>
<p data-path-to-node="50">For user-facing interfaces, waiting for a long text block to fully generate on the server results in noticeable latency.<strong> Instead, we can stream chunks back to the user, token by token, using Spring Boot&#8217;s WebFlux integration.</strong></p>
<p data-path-to-node="51">Let&#8217;s add a streaming method to our <span style="margin: 0px;padding: 0px"><em>ChatService </em>that</span> returns a reactive <em>Flux&lt;String&gt;</em> structure:</p>
<pre><code class="language-java">public Flux&lt;String&gt; streamChatTokens(String prompt) {
    return this.defaultChatClient.prompt()
      .user(prompt)
      .stream()
      .content();
}</code></pre>
<p data-path-to-node="50">We&#8217;ll create a specialized <em>StreamingChatController </em>to deliver these tokens cleanly over an open connection pipeline. This controller explicitly produces a <em>text/event-stream</em> media response:</p>
<pre><code class="language-java">@RestController
public class StreamingChatController {
    private final ChatService chatService;
    public StreamingChatController(ChatService chatService) {
        this.chatService = chatService;
    }
    @GetMapping(value = "/v1/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux&lt;String&gt; streamChatTokens(@RequestParam String prompt) {
        return chatService.streamChatTokens(prompt);
    }
}</code></pre>
<p data-path-to-node="50">When hitting this endpoint, consumers read token data reactively.  This establishes a non-blocking execution pipeline that builds an interactive, low-latency UI flow chunk by chunk.</p>
<h2 data-path-to-node="55" id="bd-testing" data-id="testing">4. Testing</h2>
<div class="bd-anchor" id="testing"></div>
<p data-path-to-node="56">Testing applications that rely on external generative AI endpoints requires a clear separation of concerns. <strong>Running tests shouldn&#8217;t trigger external network calls, hit rate limits, or exhaust API quotas during local compilation</strong>. To prevent this, we isolate our HTTP layer using Spring Boot&#8217;s slice testing along with <em>@MockitoBean</em>.</p>
<h3 data-path-to-node="57" id="bd-1-unit-testing-via-weblayer-mockmvc-mocking" data-id="1-unit-testing-via-weblayer-mockmvc-mocking">4.1. Unit Testing via WebLayer MockMvc Mocking</h3>
<div class="bd-anchor" id="1-unit-testing-via-weblayer-mockmvc-mocking"></div>
<p data-path-to-node="58">Since our implementation encapsulates all LLM communications within <em>ChatService</em>, we can completely mock this business component. <strong>This permits us to cleanly assert controller routing, request parameters, response body expectations, and stream-handling patterns.</strong></p>
<p data-path-to-node="59">Let&#8217;s write an isolated test using <em>MockMvc </em>to verify all text-based, template-driven, and reactive stream endpoints without triggering an actual network handshake. First, we set up our simple prompt test to verify basic GET parameter routing and raw response mapping:</p>
<pre><code class="language-java">@SpringBootTest(properties = {
    "spring.ai.model.chat=google-genai",
    "spring.ai.google.genai.chat.model=gemini-2.5-flash",
    "spring.ai.google.genai.api-key=test-key"
})
@AutoConfigureMockMvc
class ChatControllerUnitTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private ChatService chatService;
    @Test
    void whenSimpleEndpointIsInvoked_thenReturnsMockedModelResponse() throws Exception {
        when(chatService.simplifiedPrompt(anyString())).thenReturn("Mocked Gemini Response");
        mockMvc.perform(get("/v1/chat/simple").param("message", "Hello"))
          .andExpect(status().isOk())
          .andExpect(content().string("Mocked Gemini Response"));
        verify(chatService).simplifiedPrompt("Hello");
    }
}
</code></pre>
<p>Next, we confirm that our fluent prompt endpoint forwards query parameters accurately to the underlying service. Then it returns the configured <em>system-prompt-backed</em> response:</p>
<pre><code class="language-java">@Test
void whenFluentEndpointIsInvoked_thenReturnsModelContent() throws Exception {
    when(chatService.fluentPrompt(anyString())).thenReturn("Fluent Response");
    mockMvc.perform(get("/v1/chat/fluent").param("prompt", "Explain DI"))
      .andExpect(status().isOk())
      .andExpect(content().string("Fluent Response"));
    verify(chatService).fluentPrompt("Explain DI");
}</code></pre>
<p>For our template-driven endpoint, we validate that the controller correctly processes a multipart payload consisting of query parameters and a <em>text/plain</em> request body:</p>
<pre><code class="language-java">@Test
void whenReviewEndpointIsInvoked_thenReturnsModelContent() throws Exception {
    when(chatService.reviewCode(anyString(), anyString())).thenReturn("Optimized Code");
    mockMvc.perform(post("/v1/chat/review")
        .param("language", "Java")
        .contentType(MediaType.TEXT_PLAIN)
        .content("class A { }"))
      .andExpect(status().isOk())
      .andExpect(content().string("Optimized Code"));
    verify(chatService).reviewCode("Java", "class A { }");
}</code></pre>
<pre></pre>
<p>We&#8217;ll now verify that search-grounded queries pass their parameters cleanly through the controller layer without parameter binding errors:</p>
<pre><code class="language-java">@Test
void whenGroundedEndpointIsInvoked_thenReturnsModelContent() throws Exception {
    when(chatService.searchGroundedPrompt(anyString())).thenReturn("Grounded Response");
    mockMvc.perform(get("/v1/chat/grounded").param("currentEventQuery", "latest match winner"))
      .andExpect(status().isOk())
      .andExpect(content().string("Grounded Response"));
    verify(chatService).searchGroundedPrompt("latest match winner");
}</code></pre>
<p>Finally, we test our reactive endpoint to ensure Spring MVC properly negotiates the <em>text/event-stream</em> media type. It will also format the reactive <em>Flux </em>elements into standard Server-Sent Event (<em>data:</em>) frames:</p>
<pre><code class="language-java">@Test
void whenStreamEndpointIsInvoked_thenReturnsEventStreamContent() throws Exception {
    when(chatService.streamChatTokens(anyString()))
      .thenReturn(reactor.core.publisher.Flux.just("token-1", "token-2"));
    mockMvc.perform(get("/v1/chat/stream").param("prompt", "Stream tokens"))
      .andExpect(status().isOk())
      .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM))
      .andExpect(content().string("data:token-1\n\ndata:token-2\n\n"));
    
    verify(chatService).streamChatTokens("Stream tokens");
}</code></pre>
<p>By leveraging the modern <em>@MockBean </em>annotation, we inject our mock definitions directly into the application context wrapper, replacing the real bean configuration. <strong>This allows us to stub out standard synchronous operations as well as complex reactive <em>Flux </em>stream responses safely and reliably without network overhead.</strong></p>
<h2 data-path-to-node="57" id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p data-path-to-node="64">In this tutorial, we configured a Spring Boot application using Spring AI&#8217;s updated <em>spring-ai-starter-model-google-genai</em> engine to interact directly with Google AI Studio.</p>
<p data-path-to-node="64">We saw how explicitly defining <em>spring.ai.model.chat=google-genai</em> resolves modern multi-model classpath dependencies. From there, we established a clean architecture using a dedicated <em>ChatService</em>. We built out flexible prompt workflows with the fluent <em>ChatClient </em>API and isolated dynamic parameters using templates. Finally, we enabled live web grounding and streamed responsive outputs via WebFlux <em>Flux.</em></p>
<p data-path-to-node="43">As always, the complete code samples used in this article are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-chat">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-google-genai-chat">Introduction to Google GenAI Chat and Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/967131092/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967131092/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/967131092/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/967131092/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/967131092/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/967131092/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-google-genai-chat#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-google-genai-chat/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/~/967131092/0/baeldung~Introduction-to-Google-GenAI-Chat-and-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/spring-ai-automemorytools</feedburner:origLink>
		<title>Spring AI AutoMemoryTools</title>
		<link>https://feeds.feedblitz.com/~/967131095/0/baeldung~Spring-AI-AutoMemoryTools</link>
					<comments>https://feeds.feedblitz.com/~/967131095/0/baeldung~Spring-AI-AutoMemoryTools#respond</comments>
		
		<dc:creator><![CDATA[Stelios Anastasakis]]></dc:creator>
		<pubDate>Fri, 07 Aug 2026 03:11:28 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[popular]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204568</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how an agent decides what to remember about a user or project with Spring AI AutoMemoryTools.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/967131095/0/baeldung~Spring-AI-AutoMemoryTools">Spring AI AutoMemoryTools</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967131095/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/967131095/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/967131095/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/967131095/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/967131095/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-automemorytools#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-automemorytools/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>As AI agents become more capable, long-term memory has become just as important as reasoning. While mechanisms such as <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-chat-memory">ChatMemory</a></em> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-redis-rag-app">Retrieval Augmented Generation</a> (RAG) handle conversations and external knowledge, they don&#8217;t answer a different question: What should an agent permanently remember about a user or project?</p>
<p>Spring AI AutoMemoryTools addresses this by enabling agents to persist and recall long-term memories, such as user preferences and project-specific knowledge.</p>
<p>In this article, we&#8217;ll explore AutoMemoryTools, its architecture and components, and build a simple Spring Boot chatbot that demonstrates how it works.</p>
<h2 id="bd-dependencies" data-id="dependencies">2. Dependencies</h2>
<div class="bd-anchor" id="dependencies"></div>
<p><strong>Let&#8217;s start by defining the minimum dependencies possible for demonstrating the Spring AI AutoMemoryTools</strong>. We&#8217;ll need the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-openai">spring-ai-starter-model-openai</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/spring-ai-agent-utils">spring-ai-agent-utils</a>. The first is the starter for integrating any OpenAI-compatible API with Spring AI, and the latter contains the tools we&#8217;re exploring in this article. We can use the (current) latest versions:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-starter-model-openai&lt;/artifactId&gt;
    &lt;version&gt;2.0.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-agent-utils&lt;/artifactId&gt;
    &lt;version&gt;0.10.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-spring-ai-automemorytools" data-id="spring-ai-automemorytools">3. Spring AI AutoMemoryTools</h2>
<div class="bd-anchor" id="spring-ai-automemorytools"></div>
<p><strong>Spring AI AutoMemoryTools is a Spring AI Community library that implements long-term memory for AI agents. It uses tool calling and file-based persistence</strong>. It&#8217;s part of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/spring-ai-community/spring-ai-agent-utils">Spring AI Agent Utils</a>, similar to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-agent-skills">Agent Skills</a>. It&#8217;s worth mentioning that it&#8217;s inspired heavily by Claude Code and saves developers from implementing common agent capabilities themselves.</p>
<p>Similar to <em>MessageChatMemoryAdvisor</em> (for conversation memory), Spring AI AutoMemoryTools also implements <em>BaseChatMemoryAdvisor</em>. This means it&#8217;s related to conversational memory, but it does not focus on the conversation part. Rather, aims to store information about concepts like user preferences, projects, feedback, and more.</p>
<h3 id="bd-1-types-of-memory" data-id="1-types-of-memory">3.1. Types of Memory</h3>
<div class="bd-anchor" id="1-types-of-memory"></div>
<p><strong>Let&#8217;s look at the types of memory the Spring AI AutoMemoryTools targets</strong>:</p>
<ul>
<li>User: Remembers who the user is and how to assist them best</li>
<li>Feedback: Remembers how the user prefers the AI to behave</li>
<li>Project: Remembers important project context and decisions</li>
<li>Reference: Remembers where to find information in external systems.</li>
</ul>
<p>As we&#8217;ll see later, these are best described in the <em>AUTO_MEMORY_TOOLS_SYSTEM_PROMPT.md</em>, which is part of the dependency resources. Another thing we&#8217;ll see is how we can override this <em>PromptSystemSpec</em> with a custom one. This would mean that these types of memory might no longer exist, unless we define them.</p>
<h3 id="bd-2-the-tools" data-id="2-the-tools">3.2. The Tools</h3>
<div class="bd-anchor" id="2-the-tools"></div>
<p><strong>Now, let&#8217;s go through the six distinct tools of AutoMemoryTools</strong>:</p>
<ul>
<li>MemoryView: Reads the contents of a memory file or lists the available memory files in a directory</li>
<li>MemoryCreate: Creates a new memory file to persist long-term information</li>
<li>MemoryStrReplace: Updates an existing memory by replacing an exact piece of text</li>
<li>MemoryInsert: Inserts new content at a specific location within an existing memory file</li>
<li>MemoryDelete: Removes a memory file or directory from persistent storage</li>
<li>MemoryRename: Renames or relocates a memory file while preserving its contents.</li>
</ul>
<h3 id="bd-3-memory-system-prompt" data-id="3-memory-system-prompt">3.3. Memory System Prompt</h3>
<div class="bd-anchor" id="3-memory-system-prompt"></div>
<p>As we saw before, the <em>AUTO_MEMORY_TOOLS_SYSTEM_PROMPT.md</em> is part of the library. <strong>The library offers two different levels of abstraction. The first is sandboxed, a restricted environment that limits the agent&#8217;s access, using the Spring AI AutoMemoryTools. The other is the unrestricted use of <em>FileSystemTools</em> or <em>ShellTools</em></strong>.</p>
<p>In practise, <em>AutoMemoryTools</em> is a specialized abstraction built on top of generic filesystem operations. The Agent can access and manage memory files within the designated memory directories like <em>memory/user</em>, <em>memory/feedback</em>, etc. But it should not have the privileges to do anything outside the memory directory. On the contrary, <em>FileSystemTools</em> and <em>ShellTools</em> are unrestricted unless the developer properly handles this matter.</p>
<h3 id="bd-4-how-it-works---bringing-all-together" data-id="4-how-it-works---bringing-all-together">3.4. How it Works &#8211; Bringing All Together</h3>
<div class="bd-anchor" id="4-how-it-works---bringing-all-together"></div>
<p>Now that we&#8217;ve covered the components, let&#8217;s see how a request flows through Spring AI AutoMemoryTools.</p>
<p><strong>When a user sends a request, the LLM combines the Memory System Prompt with the user&#8217;s message to determine whether it should read from or write to memory</strong>. If needed, it invokes the AutoMemoryTools to manage Markdown files in the configured memory directory, as described by the default <em>AUTO_MEMORY_TOOLS_SYSTEM_PROMPT.md</em>.</p>
<p>After generating the response, the LLM may perform additional memory operations, such as invoking more tools or consolidating existing memory entries. Finally, it returns the response to the user.</p>
<h2 id="bd-spring-ai-automemorytools-application" data-id="spring-ai-automemorytools-application">4. Spring AI AutoMemoryTools Application</h2>
<div class="bd-anchor" id="spring-ai-automemorytools-application"></div>
<p>Bringing all together, let&#8217;s see this in action. We&#8217;ll see three scenarios of configuring a <em>ChatClient</em> to use the Spring AI AutoMemoryTools:</p>
<ul>
<li>All-defaults <em>AutoMemoryToolsAdvisor</em> (sandbox operation)</li>
<li>Manual setup of <em>AutoMemoryToolsAdvisor</em> (sandbox operation)</li>
<li><em>FileSystemTools</em> and <em>ShellTools</em> (not sandboxed by default)</li>
</ul>
<h3 id="bd-1-default-automemorytoolsadvisor-configuration" data-id="1-default-automemorytoolsadvisor-configuration">4.1. Default AutoMemoryToolsAdvisor Configuration</h3>
<div class="bd-anchor" id="1-default-automemorytoolsadvisor-configuration"></div>
<p>For the first scenario, we create an AutoMemoryToolsAdvisor with the default, minimum configuration needed:</p>
<pre><code class="language-java">@Configuration
class ChatClientConfiguration {
    @Value("${agent.memory.dir}")
    String memoryDirectory;
    ChatClient chatClient(ChatModel chatModel) {
        return ChatClient
          .builder(chatModel)
          .defaultAdvisors(
            AutoMemoryToolsAdvisor.builder()
              .memoriesRootDirectory(memoryDirectory)
              .build(),
            MessageChatMemoryAdvisor.builder(
              MessageWindowChatMemory.builder()
                .maxMessages(100)
                .build())
            .build(),
            ToolCallingAdvisor.builder()
              .disableInternalConversationHistory()
              .build())
          .build();
    }
}</code></pre>
<p>This setup uses the default <em>AUTO_MEMORY_TOOLS_SYSTEM_PROMPT.md</em> and defines the memory types we explained earlier. We use the <em>Advisor</em> to intercept all message exchanges. The <em>memoriesRootDirectory()</em> method sets the memory directory, which is also the boundary of access for the Agent. Last, the <em>MessageChatMemoryAdvisor</em> is the known <em>Advisor</em> that handles the memory for the conversation aspect. We set a random value of 100 messages history.</p>
<h3 id="bd-2-custom-automemorytools-configuration" data-id="2-custom-automemorytools-configuration">4.2. Custom AutoMemoryTools Configuration</h3>
<div class="bd-anchor" id="2-custom-automemorytools-configuration"></div>
<p><strong>If we still want the sandboxed AutoMemoryTools but we have more system prompts to include</strong>, we can use a more custom approach:</p>
<pre><code class="language-java">@Configuration
class ChatClientConfiguration {
    // ...
    @Value("classpath:/prompts/AUTO_MEMORY_TOOLS_SYSTEM_PROMPT.md")
    Resource memorySystemPromptAutoMemoryTools;
    // ....
    @Bean
    ChatClient chatClientWithMoreSystemPrompt(ChatModel chatModel) {
        return ChatClient
          .builder(chatModel)
          .defaultSystem(p -&gt; p
            .text(memorySystemPromptAutoMemoryTools)
            .param("MEMORIES_ROOT_DIERCTORY", memoryDirectory))
          .defaultTools(
            AutoMemoryTools.builder()
              .memoriesDir(memoryDirectory)
              .build(),
            TodoWriteTool.builder()
              .build())
          .defaultAdvisors(ToolCallingAdvisor.builder()
              .build())
          .build();
    }
}</code></pre>
<p>Here, we do a similar setup as before, but we use the Tools instead. This way we can define the custom PromptSystemSpec, with the prompt in the <em>.md</em> file we provide. However, this prompt is the essence of AutoMemoryTools, where we should define memory types, how to store, and more. So, if we need to override it, better copy the default prompt and append the extra system prompts.</p>
<p>We should note that <em>ChatMemory</em> is not set here, but the <em>TodoWriteTool</em> is used. This has nothing to do with Spring AI AutoMemoryTools. It&#8217;s to show that we can have or not have more tools and advisors, depending on the Agent&#8217;s needs.</p>
<h3 id="bd-3-filesystemtools-and-shelltools" data-id="3-filesystemtools-and-shelltools">4.3. <em>FileSystemTools</em> and <em>ShellTools</em></h3>
<div class="bd-anchor" id="3-filesystemtools-and-shelltools"></div>
<p>Last, we can have an unrestricted scenario, using <em>FileSystemTools</em> and <em>ShellTools</em>. For example,<strong> if the agent already has FileSystemTools or ShellTools for other tasks, there&#8217;s no need for AutoMemoryTools</strong>:</p>
<pre><code class="language-java">@Configuration
class ChatClientConfiguration {
    // ...
    @Value("classpath:/prompts/AUTO_MEMORY_FILESYSTEM_TOOLS_SYSTEM_PROMPT.md")
    Resource memorySystemPromptFilesystemTools;
    // ....
    @Bean
    ChatClient chatClientWithoutAutoMemoryTools(ChatModel chatModel) {
      return ChatClient
        .builder(chatModel)
        .defaultSystem(p -&gt; p
          .text(memorySystemPromptFilesystemTools)
          .param("MEMORIES_ROOT_DIERCTORY", memoryDirectory))   // tells the agent where to write
          .defaultTools(
            ShellTools.builder()
              .build(),         // Bash — mkdir, ls, etc.
            FileSystemTools.builder()
              .build())    // Read, Write, Edit — memory file operations
          .defaultAdvisors(ToolCallingAdvisor.builder()
              .build())
        .build();
    }
}</code></pre>
<p>The file <em>AUTO_MEMORY_FILESYSTEM_TOOLS_SYSTEM_PROMPT.md</em> can also be found in the library. For the demonstration, we can copy it over. Using this one, the same memory conventions apply: <em>.md</em> files, two-step save, etc. But the agent conventionally has full filesystem access and stays in the configured directory only.</p>
<p>The two tools are the ones the Agent needs to do all the file creation, removal, editing, etc, to keep the memory in the file system.</p>
<h3 id="bd-4-other-project-files" data-id="4-other-project-files">4.4. Other Project Files</h3>
<div class="bd-anchor" id="4-other-project-files"></div>
<p>For the demonstration, we&#8217;ll use spring-web-mvc, with a <em>RestController</em>:</p>
<pre><code class="language-java">@PostMapping("/chat-with-memory")
ResponseEntity&lt;String&gt; chat(@RequestBody String question, @RequestHeader("X-Conversation-ID") String conversationId) {
    String answer = chatClient
      .prompt()
      .advisors(a -&gt; a.param(ChatMemory.CONVERSATION_ID, conversationId))
      .user(question)
      .call()
      .content();
    return ResponseEntity.ok(answer);
}</code></pre>
<p>As mentioned earlier, the <em>conversationId</em> is needed, since Spring AI AutoMemoryTools implements <em>BaseChatMemoryAdvisor</em>.</p>
<p>Let&#8217;s run the application using a local Ollama Qwen model:</p>
<p>&nbsp;</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/08/demo-automemorytools-chat-1024x440-1.png"><img loading="lazy" decoding="async" class="alignnone wp-image-204570" src="https://www.baeldung.com/wp-content/uploads/2026/08/demo-automemorytools-chat-1024x440-1-300x129.png" alt="" width="1021" height="439" srcset="https://www.baeldung.com/wp-content/uploads/2026/08/demo-automemorytools-chat-1024x440-1-300x129.png 300w, https://www.baeldung.com/wp-content/uploads/2026/08/demo-automemorytools-chat-1024x440-1-768x330.png 768w, https://www.baeldung.com/wp-content/uploads/2026/08/demo-automemorytools-chat-1024x440-1-100x43.png 100w, https://www.baeldung.com/wp-content/uploads/2026/08/demo-automemorytools-chat-1024x440-1-600x258.png 600w, https://www.baeldung.com/wp-content/uploads/2026/08/demo-automemorytools-chat-1024x440-1.png 1024w" sizes="auto, (max-width: 1021px) 100vw, 1021px" /></a></p>
<p>First, we ask the Agent to save some info. Then we request this info, but notice the different <em>conversationId</em>! The Agent doesn&#8217;t know anything about <em>session-1</em>. Asking as <em>session-2</em> again, the Agent remembers it.</p>
<p>Depending on our machine, the Agent interaction with the file system might be quite slow. We can also see the directories that the Agent created, along with the file content:</p>
<p>&nbsp;</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/08/automemorytools-folders-created-1024x332-1.png"><img loading="lazy" decoding="async" class="alignnone wp-image-204569" src="https://www.baeldung.com/wp-content/uploads/2026/08/automemorytools-folders-created-1024x332-1-300x97.png" alt="" width="1021" height="330" srcset="https://www.baeldung.com/wp-content/uploads/2026/08/automemorytools-folders-created-1024x332-1-300x97.png 300w, https://www.baeldung.com/wp-content/uploads/2026/08/automemorytools-folders-created-1024x332-1-768x249.png 768w, https://www.baeldung.com/wp-content/uploads/2026/08/automemorytools-folders-created-1024x332-1-100x32.png 100w, https://www.baeldung.com/wp-content/uploads/2026/08/automemorytools-folders-created-1024x332-1-600x195.png 600w, https://www.baeldung.com/wp-content/uploads/2026/08/automemorytools-folders-created-1024x332-1.png 1024w" sizes="auto, (max-width: 1021px) 100vw, 1021px" /></a></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 Spring AI AutoMemoryTools. We covered the use cases and the basic definitions. Then we walked through the six operations and the three options to use them. Finally, we used Spring AI to demonstrate it in practise.</p>
<p>As always, the source code of the examples can be found <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-agent-skills">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-automemorytools">Spring AI AutoMemoryTools</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/967131095/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967131095/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/967131095/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/967131095/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/967131095/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/967131095/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-automemorytools#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-automemorytools/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/~/967131095/0/baeldung~Spring-AI-AutoMemoryTools/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-agent2agent-protocol</feedburner:origLink>
		<title>Exploring Agent2Agent Protocol (A2A) With Spring AI</title>
		<link>https://feeds.feedblitz.com/~/967127630/0/baeldung~Exploring-AgentAgent-Protocol-AA-With-Spring-AI</link>
					<comments>https://feeds.feedblitz.com/~/967127630/0/baeldung~Exploring-AgentAgent-Protocol-AA-With-Spring-AI#respond</comments>
		
		<dc:creator><![CDATA[Hardik Singh Behl]]></dc:creator>
		<pubDate>Fri, 07 Aug 2026 02:59:48 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[popular]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204564</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 make your AI agents to discover each other and communicate efficiently using A2A and SpringAI.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/967127630/0/baeldung~Exploring-AgentAgent-Protocol-AA-With-Spring-AI">Exploring Agent2Agent Protocol (A2A) With Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967127630/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/967127630/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/967127630/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/967127630/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/967127630/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-agent2agent-protocol#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-agent2agent-protocol/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>We&#8217;re increasingly building AI agents that can handle a user&#8217;s entire request on their own. Instead of simply answering questions from what a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/large-language-models">Large Language Model (LLM)</a> already knows, these agents reason through a problem, break it down into steps, call external tools, and even execute local scripts.</p>
<p>As these requests grow in complexity, packing every capability into a single agent becomes unmanageable. The natural fix is to create smaller agents, each specialized in one job. However, getting these agents to talk to each other is a challenge in itself, as they run as independent services built using different languages, frameworks, and LLMs.</p>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://a2a-protocol.org/">Agent2Agent (A2A) Protocol</a> addresses this issue by defining a standard for agents to discover each other and communicate.</p>
<p>In this tutorial, we&#8217;ll practically explore the A2A protocol by implementing its client and server architecture using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai">Spring AI</a>.</p>
<h2 id="bd-agent2agent-a2a-protocol-101" data-id="agent2agent-a2a-protocol-101">2. Agent2Agent (A2A) Protocol 101</h2>
<div class="bd-anchor" id="agent2agent-a2a-protocol-101"></div>
<p>Before we dive into the implementation, let&#8217;s take a closer look at the protocol and the way two agents interact:</p>
<p>&nbsp;</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/08/a2a-protocol-client-server-flow-diagram-1.png"><img loading="lazy" decoding="async" class="alignnone wp-image-204573" src="https://www.baeldung.com/wp-content/uploads/2026/08/a2a-protocol-client-server-flow-diagram-1-300x66.png" alt="" width="1032" height="227" srcset="https://www.baeldung.com/wp-content/uploads/2026/08/a2a-protocol-client-server-flow-diagram-1-300x66.png 300w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-protocol-client-server-flow-diagram-1-768x170.png 768w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-protocol-client-server-flow-diagram-1-1536x340.png 1536w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-protocol-client-server-flow-diagram-1-2048x453.png 2048w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-protocol-client-server-flow-diagram-1-100x22.png 100w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-protocol-client-server-flow-diagram-1-600x133.png 600w" sizes="auto, (max-width: 1032px) 100vw, 1032px" /></a></p>
<p>An agent acting as a server exposes its capabilities to the outside world, while an agent acting as a client consumes them.</p>
<p><strong>Discovery happens through an Agent Card, which is a JSON document that a server publishes at a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://en.wikipedia.org/wiki/Well-known_URI#:~:text=2019%2D03%2D01-,agent%2Dcard.json,-Details%20for%20an">well-known URL</a></strong>. This card exposes the agent&#8217;s details, including the list of skills it offers. A client agent fetches this card first to learn what a remote agent can do and where to reach it.</p>
<p><strong>The client sends a Message that describes the work to be done in plain natural language. The remote agent turns that message into a Task, processes it, and returns one or more Artifacts that carry the actual response content</strong>.</p>
<p>A2A is a complex and vast topic. We can refer to the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://a2a-protocol.org/latest/specification/">official specification</a> to learn more.</p>
<h2 id="bd-the-project-were-building" data-id="the-project-were-building">3. The Project We&#8217;re Building</h2>
<div class="bd-anchor" id="the-project-were-building"></div>
<p>To see the protocol in action, we&#8217;ll build a job screening system for recruiters:</p>
<p>&nbsp;</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/08/a2a-job-screening-architecture-diagram-1-scaled.png"><img loading="lazy" decoding="async" class="alignnone wp-image-204572" src="https://www.baeldung.com/wp-content/uploads/2026/08/a2a-job-screening-architecture-diagram-1-300x188.png" alt="" width="1146" height="718" srcset="https://www.baeldung.com/wp-content/uploads/2026/08/a2a-job-screening-architecture-diagram-1-300x188.png 300w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-job-screening-architecture-diagram-1-1024x642.png 1024w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-job-screening-architecture-diagram-1-768x481.png 768w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-job-screening-architecture-diagram-1-1536x963.png 1536w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-job-screening-architecture-diagram-1-2048x1283.png 2048w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-job-screening-architecture-diagram-1-100x63.png 100w, https://www.baeldung.com/wp-content/uploads/2026/08/a2a-job-screening-architecture-diagram-1-600x376.png 600w" sizes="auto, (max-width: 1146px) 100vw, 1146px" /></a></p>
<p>As we can see, <strong>our system is made up of one orchestrator agent that acts as the A2A client and communicates with three specialized remote agents that act as A2A servers</strong>.</p>
<p>A recruiter submits a candidate&#8217;s details to a single REST endpoint. Behind the scenes, the orchestrator agent will break the request down and delegate each task to a specialized agent. Once every agent responds, the orchestrator will merge the individual verdicts into one short screening summary.</p>
<h2 id="bd-creating-an-a2a-server" data-id="creating-an-a2a-server">4. Creating an A2A Server</h2>
<div class="bd-anchor" id="creating-an-a2a-server"></div>
<p>Since creating A2A servers follows the exact same structure, we&#8217;ll only walk through implementing the skills matcher agent.</p>
<p>The remaining two remote servers differ solely in their tools and prompts. To view the complete implementation of the project, we can refer to the repository backing this tutorial.</p>
<h3 id="bd-1-dependencies-and-llm-configuration" data-id="1-dependencies-and-llm-configuration">4.1. Dependencies and LLM Configuration</h3>
<div class="bd-anchor" id="1-dependencies-and-llm-configuration"></div>
<p>Let&#8217;s start by adding the necessary dependencies to our project&#8217;s <em>pom.xml</em> file:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-starter-model-openai&lt;/artifactId&gt;
    &lt;version&gt;2.0.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-a2a-server-autoconfigure&lt;/artifactId&gt;
    &lt;version&gt;0.3.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Here, we first import <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-openai">Spring AI’s OpenAI starter dependency</a>, which we’ll use to interact with an LLM. Additionally, <strong>we import the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/spring-ai-a2a-server-autoconfigure">A2A server autoconfigure</a> dependency from the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/spring-ai-community">Spring AI community</a>, which takes care of serving our agent card at startup and handling A2A requests</strong>.</p>
<p>Next, let’s configure our <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://platform.openai.com/api-keys">OpenAI API key</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://developers.openai.com/api/docs/models">chat model</a> in the <em>application.yaml</em> file:</p>
<pre><code class="language-yaml">spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        model: gpt-5.5</code></pre>
<p>Here, we specify OpenAI&#8217;s <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openai.com/index/introducing-gpt-5-5/">GPT 5.5</a> model using the <em>gpt-5.5</em> model ID. Alternatively, we can use a different chat model, as the specific AI model or provider is irrelevant for this demonstration.</p>
<p><strong>With these two properties set, Spring AI automatically creates a bean of type <em>ChatClient.Builder</em></strong>, which we&#8217;ll use in the upcoming section.</p>
<h3 id="bd-2-creating-a-tool-and-registering-it-with-chatclient" data-id="2-creating-a-tool-and-registering-it-with-chatclient">4.2. Creating a Tool and Registering It With <em>ChatClient</em></h3>
<div class="bd-anchor" id="2-creating-a-tool-and-registering-it-with-chatclient"></div>
<p>Next, let&#8217;s configure the actual capability that our A2A server offers. We&#8217;ll create a <em>SkillsMatcherTools</em> class and define a tool that evaluates a candidate&#8217;s skills:</p>
<pre class="add-horizontal-scrollbar"><code class="language-java">@Tool(
  name = "match-skills",
  description = "Compares a candidate's skills against a job's required skills and returns a fit score"
)
SkillsMatchResult matchSkills(
  @ToolParam(description = "Candidate skills, comma-separated") String candidateSkills,
  @ToolParam(description = "Required job skills, comma-separated") String requiredSkills
) {
    // ... rudimentary implementation
}
record SkillsMatchResult(
    int score,
    Verdict verdict,
    Set&lt;String&gt; matchedSkills,
    Set&lt;String&gt; missingSkills
) {}
enum Verdict {
    STRONG_MATCH,
    PARTIAL_MATCH,
    WEAK_MATCH
}</code></pre>
<p><strong>We annotate our method with the <em>@Tool</em> annotation and give it an explicit <em>name</em> along with a short <em>description</em>. Both of these values help the AI model decide if and when to invoke this tool</strong>. Similarly, we describe both method parameters with the <em>@ToolParam</em> annotation so that the LLM knows the format we expect the inputs to be in.</p>
<p>We&#8217;ve deliberately omitted the method implementation here, since it&#8217;s not important to our A2A understanding.</p>
<p>Next, let&#8217;s create a <em>ChatClient</em> bean and register our tool with it:</p>
<pre><code class="language-java">@Bean
ChatClient chatClient(
  ChatClient.Builder chatClientBuilder,
  SkillsMatcherTools skillsMatcherTools
) {
    return chatClientBuilder
      .defaultSystem("""
        You are a skills-matching assistant for recruiters.
        Use the match-skills tool to compare a candidate's skills
        against a job's required skills, then summarize the result.
        """)
      .defaultTools(skillsMatcherTools)
      .build();
}</code></pre>
<p>Here, we use the <em>ChatClient.Builder</em> bean that Spring AI configures for us, along with the <em>SkillsMatcherTools</em> bean we defined above, to create a <em>ChatClient</em> bean. <strong>This class acts as our main entry point for interacting with the configured LLM</strong>.</p>
<p>Additionally, we define a system prompt defining the agent&#8217;s role, and then register our tool class using the <em>defaultTools()</em> method. This enables the model to call our tool method when it receives a matching request.</p>
<h3 id="bd-3-defining-an-agentexecutor-to-handle-a2a-requests" data-id="3-defining-an-agentexecutor-to-handle-a2a-requests">4.3. Defining an <em>AgentExecutor</em> to Handle A2A Requests</h3>
<div class="bd-anchor" id="3-defining-an-agentexecutor-to-handle-a2a-requests"></div>
<p>The <em>ChatClient</em> bean we&#8217;ve defined can only be used by components within our own application. To let other agents send us tasks, we need to define an <em>AgentExecutor</em> bean that handles incoming A2A requests:</p>
<pre class="add-horizontal-scrollbar"><code class="language-java">@Bean
AgentExecutor agentExecutor(ChatClient chatClient) {
    return new DefaultAgentExecutor(chatClient, (client, requestContext) -&gt; {
        String userMessage = DefaultAgentExecutor.extractTextFromMessage(requestContext.getMessage());
        return client
          .prompt()
          .user(userMessage)
          .call()
          .content();
    });
}</code></pre>
<p>Here, we use the <em>DefaultAgentExecutor</em> class, passing it our <em>ChatClient</em> bean along with a handler function that defines how we want to respond. In the handler function, we extract the plain text of the incoming message and pass it to our LLM as a user prompt. <strong>The response from our <em>chatClient</em> bean is automatically wrapped into an artifact and sent back to the calling agent</strong>.</p>
<h3 id="bd-4-describing-our-agent-using-an-agentcard" data-id="4-describing-our-agent-using-an-agentcard">4.4. Describing Our Agent Using an <em>AgentCard</em></h3>
<div class="bd-anchor" id="4-describing-our-agent-using-an-agentcard"></div>
<p>Finally, we need to expose an Agent Card that accurately describes our agent:</p>
<pre><code class="language-java">@Bean
AgentCard agentCard(
  @Value("${server.host}") String host,
  @Value("${server.port}") int port
) {
    return new AgentCard.Builder()
      .name("Skills Matcher Agent")
      .description("Evaluates how well a candidate's skills match a job's required skills")
      .url(String.format("http://%s:%d/", host, port))
      .version("1.0.0")
      .capabilities(new AgentCapabilities
        .Builder()
        .streaming(false)
        .build())
      .defaultInputModes(List.of("text"))
      .defaultOutputModes(List.of("text"))
      .skills(List.of(new AgentSkill.Builder()
        .id("skills_matching")
        .name("Skills Matching")
        .description("Compares candidate skills to job requirements and scores the fit")
        .tags(List.of("hiring", "recruiting"))
        .build()))
      .protocolVersion("1.0.1")
      .build();
}</code></pre>
<p>Here, we create an <em>AgentCard</em> bean and define the important properties of <em>name</em>, <em>description</em>, and <em>skills</em>. <strong>Client agents rely on these properties to decide whether a remote agent and the capabilities it offers are a good fit for a given task</strong>.</p>
<p>Similarly, we define the <em>url</em> property from the <em>host</em> and <em>port</em> of our running application, telling clients where to reach us. Additionally, we declare that our agent doesn&#8217;t support streaming and it exchanges plain text in both directions.</p>
<p><strong>It&#8217;s worth noting that every property we set above is mandatory</strong>, and the builder rejects a card with any of them missing. With this bean defined, <strong>the auto-configuration serves our agent card at the <em>.well-known/agent-card.json</em> path</strong>.</p>
<h2 id="bd-creating-an-a2a-client" data-id="creating-an-a2a-client">5. Creating an A2A Client</h2>
<div class="bd-anchor" id="creating-an-a2a-client"></div>
<p>With our specialized agents ready, we&#8217;ll build our A2A client, i.e., the job screening orchestrator that delegates the actual evaluation to them.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">5.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p><strong>Our orchestrator is a separate application that also talks with an LLM. For this reason, we&#8217;ll need to import a chat model dependency and configure the API key and model properties, just like we did in our A2A server</strong>.</p>
<p>In addition to that, we need to add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/io.github.a2asdk/a2a-java-sdk-client">A2A Java SDK</a> to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;io.github.a2asdk&lt;/groupId&gt;
    &lt;artifactId&gt;a2a-java-sdk-client&lt;/artifactId&gt;
    &lt;version&gt;0.3.3.Final&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>This SDK provides us the classes we need to fetch agent cards, open connections to remote agents, and send them messages.</p>
<p>We should note that we didn&#8217;t explicitly declare this dependency while building our A2A server, as the <em>server autoconfigure</em> dependency brings it in transitively. However, for an agent purely acting as a client, we&#8217;ll need to add this ourselves.</p>
<h3 id="bd-2-discovering-remote-agents-at-startup" data-id="2-discovering-remote-agents-at-startup">5.2. Discovering Remote Agents at Startup</h3>
<div class="bd-anchor" id="2-discovering-remote-agents-at-startup"></div>
<p>Our orchestrator can only delegate work to agents it knows about, so let&#8217;s list their addresses in the <em>application.yaml</em>:</p>
<pre><code class="language-yaml">remote:
  agents:
    urls:
      - http://localhost:8081
      - http://localhost:8082
      - http://localhost:8083</code></pre>
<p>Here, <strong>we configure the base URLs of our remote agents using a custom property</strong>. We need to make sure to update these values if the agents run on a different host or port.</p>
<p>Next, let&#8217;s create an <em>AgentRegistry</em> component that fetches agent cards from these URLs when the application starts:</p>
<pre><code class="language-java">private final Map&lt;String, AgentCard&gt; agentCards = new HashMap&lt;&gt;();
AgentRegistry(@Value("${remote.agents.urls}") List&lt;String&gt; agentUrls) {
    for (String url : agentUrls) {
        String path = new URI(url).getPath();
        AgentCard card = A2A.getAgentCard(url, path + ".well-known/agent-card.json", null);
        agentCards.put(card.name(), card);
    }
}</code></pre>
<p>Here, we iterate over each configured URL inside the constructor and retrieve its agent card from the <em>.well-known/agent-card.json</em> path. <strong>Then, we store the resulting <em>AgentCard</em> instances in an in-memory map keyed by the agent&#8217;s name</strong>.</p>
<p>To expose the fetched agent cards to other components, let&#8217;s add a couple of helper methods to this class:</p>
<pre><code class="language-java">AgentCard get(String agentName) {
    return agentCards.get(agentName);
}
String describeAgents() {
    return agentCards
      .values()
      .stream()
      .map(card -&gt; "- " + card.name() + ": " + card.description())
      .collect(Collectors.joining("\n"));
}</code></pre>
<p>The <em>get()</em> method returns the card of a specific agent by its name. Meanwhile, the <em>describeAgents()</em> method renders the formatted summary of all available remote agents with their descriptions. We&#8217;ll use these helper methods in the upcoming sections to define additional components.</p>
<h3 id="bd-3-communicating-with-remote-agents" data-id="3-communicating-with-remote-agents">5.3. Communicating With Remote Agents</h3>
<div class="bd-anchor" id="3-communicating-with-remote-agents"></div>
<p>Our A2A client is now capable of discovering remote agents at startup. Next, let&#8217;s create a <em>RemoteAgentClient</em> component that actually communicates with these agents:</p>
<pre><code class="language-java">String sendMessage(String agentName, String task) {
    AgentCard agentCard = agentRegistry.get(agentName);
    CompletableFuture&lt;String&gt; response = new CompletableFuture&lt;&gt;();
    BiConsumer&lt;ClientEvent, AgentCard&gt; responseConsumer = (event, card) -&gt; {
        TaskEvent taskEvent = (TaskEvent) event;
        response.complete(taskEvent.getTask()
          .getArtifacts()
          .stream()
          .map(Artifact::parts)
          .map(this::extractText)
          .collect(Collectors.joining("\n")));
    };
    Client client = Client.builder(agentCard)
      .clientConfig(new ClientConfig.Builder()
        .setAcceptedOutputModes(List.of("text"))
        .build())
      .withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig())
      .addConsumers(List.of(responseConsumer))
      .streamingErrorHandler(response::completeExceptionally)
      .build();
    Message message = A2A.toUserMessage(task);
    client.sendMessage(message);
    return response.get(60, TimeUnit.SECONDS);
}
private String extractText(List&lt;Part&lt;?&gt;&gt; parts) {
    return parts
      .stream()
      .filter(TextPart.class::isInstance)
      .map(TextPart.class::cast)
      .map(TextPart::getText)
      .collect(Collectors.joining("\n"));
}</code></pre>
<p>Here, we start by fetching the target agent&#8217;s card from our registry, as the SDK uses it to build a client.</p>
<p><strong>Since the SDK returns responses asynchronously, we register a consumer that receives the completed task. Then, it pulls the text out of its artifacts, and passes it to our <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-completablefuture">CompletableFuture</a></em> <em>response</em></strong>. Then, we convert the given <em>task</em> into an A2A message, send it to the remote agent, and wait for the <em>response</em> to complete.</p>
<p>Next, let&#8217;s expose this capability as a tool that our orchestrator&#8217;s LLM can call:</p>
<pre><code class="language-java">@Tool(
  name = "send-message-to-agent",
  description = "Sends a task to a remote agent and returns its response."
)
String sendMessageToAgent(
  @ToolParam(description = "Name of the remote agent") String agentName,
  @ToolParam(description = "The task to perform") String task
) {
    return remoteAgentClient.sendMessage(agentName, task);
}</code></pre>
<p>This single tool is all the orchestrator needs to reach all our configured agents. The LLM will decide which agent to contact and what to ask it, simply by filling in the <em>agentName</em> and <em>task</em> parameters.</p>
<h3 id="bd-4-building-the-orchestrator-chatclient" data-id="4-building-the-orchestrator-chatclient">5.4. Building the Orchestrator <em>ChatClient</em></h3>
<div class="bd-anchor" id="4-building-the-orchestrator-chatclient"></div>
<p>With our discovery and communication logic in place, let&#8217;s create the <em>ChatClient</em> bean for our orchestrator agent:</p>
<pre><code class="language-java">@Bean
ChatClient chatClient(
  ChatClient.Builder chatClientBuilder,
  AgentRegistry agentRegistry,
  RemoteAgentTools remoteAgentTools
) {
    return chatClientBuilder
      .defaultSystem("""
        You are a job-screening orchestrator for recruiters.
        You do not evaluate candidates yourself. Instead, you delegate
        to the following remote agents:
        %s
        Once all agents have responded, combine their responses into a short screening summary.
        """.formatted(agentRegistry.describeAgents()))
      .defaultTools(remoteAgentTools)
      .build();
}</code></pre>
<p>In our system prompt, we explicitly instruct the model not to evaluate candidates on its own. Instead, <strong>we inject the list of discovered agents using the <em>describeAgents()</em> method and ask it to delegate the individual evaluations to them</strong>.</p>
<p>Then, we register our tool, which gives the model the capability to actually reach these agents.</p>
<h3 id="bd-5-exposing-a-rest-api" data-id="5-exposing-a-rest-api">5.5. Exposing a REST API</h3>
<div class="bd-anchor" id="5-exposing-a-rest-api"></div>
<p>Finally, let&#8217;s use the orchestrator <em>ChatClient</em> we&#8217;ve defined and expose a REST API endpoint to accept candidate screening requests:</p>
<pre><code class="language-java">@PostMapping("/screenings")
ScreeningResponse screenCandidate(@RequestBody ScreeningRequest screeningRequest) {
    String verdict = chatClient
      .prompt()
      .user(screeningRequest.toString())
      .call()
      .content();
    return new ScreeningResponse(verdict);
}
record ScreeningRequest(
  String name,
  String email,
  String jobTitle,
  String requiredSkills,
  String candidateSkills,
  int expectedSalary
) {}
record ScreeningResponse(
  String verdict
) {}</code></pre>
<p>Our endpoint accepts a <em>ScreeningRequest</em> record holding everything our agents need, namely the candidate&#8217;s identity, the job details, and the expected salary.</p>
<p>We simply pass the record&#8217;s string representation to the model as a user prompt and return the resulting summary as a <em>ScreeningResponse</em>. <strong>This way, our orchestrator client receives the full candidate details and distributes the relevant data to each of our specialized agents</strong>.</p>
<h2 id="bd-testing-our-implementation" data-id="testing-our-implementation">6. Testing Our Implementation</h2>
<div class="bd-anchor" id="testing-our-implementation"></div>
<p>With our architecture implemented, let&#8217;s start all our agents and test the job screening flow.</p>
<p>We&#8217;ll use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/httpie-http-client-command-line">HTTPie</a> CLI to invoke our screening endpoint:</p>
<pre><code class="language-bash">http POST :8080/screenings \
  name="John Doe" \
  email="john.doe@baeldung.com" \
  jobTitle="Backend Developer" \
  requiredSkills="Java, Spring Boot, AWS, Kafka" \
  candidateSkills="Java, Spring Boot, Azure, Kafka" \
  expectedSalary:=110000 \
  | jq -r '.verdict'</code></pre>
<p>Here, we submit sample data for a candidate and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/linux/anonymous-named-pipes#what-is-a-pipe">pipe</a> the response through <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/linux/jq-command-json"><em>jq</em></a> to print the <em>verdict</em> as readable text.</p>
<p>Let’s see what we get as a response:</p>
<pre class="add-horizontal-scrollbar"><code class="language-bash">Screening summary for John Doe (Backend Developer):
- Salary: Expected salary of $110,000 is within budget.
- Background check: Clear; no relevant flags found.
- Skills match: Strong match, 75% fit. Only missing AWS experience, though Azure experience may be transferable.
Overall: John Doe appears to be a good candidate to proceed with, with follow-up recommended on AWS/cloud experience.</code></pre>
<p>As we can see, <strong>the orchestrator delegated the request to all three remote agents and consolidated their individual verdicts into a single summary</strong>.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we&#8217;ve understood what the Agent2Agent (A2A) protocol is and practically implemented it using Spring AI.</p>
<p>We started by building an A2A server, exposing its capability through an agent card. Next, we built an A2A client that acts as an orchestrator, dynamically discovering remote agents and delegating tasks to them. Finally, we tested the complete flow of our implementation and confirmed that our orchestrator combines the responses of all the specialized agents into one screening summary.</p>
<p>As always, all the code examples used in this article are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-a2a">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-agent2agent-protocol">Exploring Agent2Agent Protocol (A2A) With Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/967127630/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/967127630/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/967127630/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/967127630/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/967127630/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/967127630/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-agent2agent-protocol#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-agent2agent-protocol/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/~/967127630/0/baeldung~Exploring-AgentAgent-Protocol-AA-With-Spring-AI/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-657</feedburner:origLink>
		<title>Java Weekly, Issue 657</title>
		<link>https://feeds.feedblitz.com/~/965486156/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/965486156/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sat, 01 Aug 2026 15:56:58 +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=204552</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>A Java movie? Cool. And Spring Cloud Contract has a new home.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/965486156/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 657</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/965486156/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/965486156/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/965486156/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/965486156/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/965486156/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-657#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-657/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.infoq.com/articles/self-building-agent-langchain4j/">&gt;&gt; The Self-Building Agent: a LangChain4j Experiment</a></strong> [<span style="color: #993300;">infoq.com</span>]</p>
<p>A interesting reveral of the typical agent tutorial by pointing an LLM at the LangChain4j documentation and having it <strong>design an agent</strong>. Meta yes, and worth a quick 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://quarkus.io/blog/to-cache-or-not-to-cache-virtual-threads/" target="_blank" rel="noopener"><strong>To Cache or Not to Cache Virtual Threads</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://inside.java/2026/07/25/design-java-mcp-tool/" target="_blank" rel="noopener"><strong>Pairing In-Process and Hosted Embeddings for Java MCP Tool Development</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/new-between-quarters-security-updates-for-java-what-cspus-mean-for-your-release-pipeline/" target="_blank" rel="noopener"><strong>New Between-Quarters Security Updates for Java: What CSPUs Mean for Your Release Pipeline</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/07/reverse-engineering-with-hibernate-7-4-and-intellij-idea/" target="_blank" rel="noopener"><strong>Reverse Engineering with Hibernate 7.4 and IntelliJ IDEA</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wimdeblauwe.com/blog/2026/07/27/writing-a-thymeleaf-component-library/" target="_blank" rel="noopener"><strong>Writing a Thymeleaf component library</strong></a> [<span style="color: #800000;">wimdeblauwe.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/28/quality-heads-up/" target="_blank" rel="noopener"><strong>Quality Outreach Heads-up &#8211; JDK 28: jlink cacerts plugin</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/research/2026/07/kotlinllm-open-source/" target="_blank" rel="noopener"><strong>KotlinLLM is Going Open Source</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.frankel.ch/rfc-9880-iot-validation-compiler/" target="_blank" rel="noopener"><strong>RFC 9880 and the IoT Validation Problem</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/ai-found-the-bugs-whos-patching-your-eol-java-code/" target="_blank" rel="noopener"><strong>AI Found the Bugs. Who&#8217;s Patching Your EOL Java Code?</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/07/secure-your-apis-oauth2-and-jwt-for-beginners/" target="_blank" rel="noopener"><strong>Secure Your APIs: OAuth2 and JWT for Beginners</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/23/podcast-063/" target="_blank" rel="noopener"><strong>Episode 63 &#8220;AI Solutions with Spring AI 2.0&#8221; [I/O]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/23/a-bootiful-podcast-billy-korando" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Java Developer Advocate Billy Korando on Java 27 and Beyond</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/27/spring-office-hours-podcast-S5E19" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E19 &#8211; Docker, Compose, Testcontainers, Oh My!</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/presentations/kotlin-serverless/" target="_blank" rel="noopener"><strong>Clean Architecture for Serverless: Business Logic You Can Take Anywhere</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-37-4-released/" target="_blank" rel="noopener"><strong>Quarkus 3.37.4</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-33-3-released/" target="_blank" rel="noopener"><strong>3.33.3</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.2.4" target="_blank" rel="noopener"><strong>GraalVM Community 25 Innovation 2</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/Netflix/zuul/releases/tag/v4.1.3" target="_blank" rel="noopener"><strong>Zuul 4.1.3</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.2" target="_blank" rel="noopener"><strong>4.1.2</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.0.7" target="_blank" rel="noopener"><strong>Micronaut Core 5.0.7</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/orchestrator-tax.html">&gt;&gt; The Orchestrator&#8217;s Tax</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>
<p>Insight into <strong>multi-agent coding setups</strong>. Exploratory rather than prescriptive, and better for it.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.christianposta.com/credential-brokering-patterns-for-ai-agent-egress/" target="_blank" rel="noopener"><strong>Credential Brokering Patterns for AI Agents Part 1: Don&#8217;t Give the Agent the Keys</strong></a> [<span style="color: #800000;">christianposta.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/07/27/rise-of-open-weights.html" target="_blank" rel="noopener"><strong>The Rise of Open Weights; And the fall of commercial AI?</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://event-driven.io/en/throw-result-or-neither/" target="_blank" rel="noopener"><strong>Throw, Result, or neither?</strong></a> [<span style="color: #800000;">event-driven.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://lucumr.pocoo.org/2026/7/24/codeberg-divides/" target="_blank" rel="noopener"><strong>Codeberg Divides</strong></a> [<span style="color: #800000;">lucumr.pocoo.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/07/27/shifting-readiness-left-what-ai-can-and-cant-do-for-organisational-readiness.html" target="_blank" rel="noopener"><strong>Shifting readiness left: What AI can (and can&#8217;t) do for organisational readiness</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://seths.blog/2009/04/first-ten/">&gt;&gt; First, ten</a></strong> [<strong><span style="color: #993300;">seths.blog</span></strong>]</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-657">Java Weekly, Issue 657</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/965486156/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/965486156/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/965486156/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/965486156/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/965486156/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/965486156/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-657#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-657/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/~/965486156/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/spark-dataframe-empty</feedburner:origLink>
		<title>Check if Spark Dataframe Is Empty</title>
		<link>https://feeds.feedblitz.com/~/965089607/0/baeldung~Check-if-Spark-Dataframe-Is-Empty</link>
					<comments>https://feeds.feedblitz.com/~/965089607/0/baeldung~Check-if-Spark-Dataframe-Is-Empty#respond</comments>
		
		<dc:creator><![CDATA[Yadu Krishnan]]></dc:creator>
		<pubDate>Fri, 31 Jul 2026 18:24:51 +0000</pubDate>
				<category><![CDATA[Data]]></category>
		<category><![CDATA[Spark]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204544</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 check whether a Spark DataFrame is empty to improve performance, especially when working with large datasets.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/965089607/0/baeldung~Check-if-Spark-Dataframe-Is-Empty">Check if Spark Dataframe Is Empty</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/965089607/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/965089607/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/965089607/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/965089607/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/965089607/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/spark-dataframe-empty#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/spark-dataframe-empty/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>When working with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-spark">Apache Spark</a>, we often encounter situations where a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spark-dataframes">DataFrame</a> becomes empty after applying transformations, filters, or joins.</p>
<p><strong>Before triggering expensive actions or saving data to external storage, it&#8217;s a best practice to verify if the DataFrame contains any rows</strong>. While this may seem like a trivial check, different approaches trigger different Spark actions and can have a noticeable impact on performance, especially for large datasets.</p>
<p>In this tutorial, we&#8217;ll explore different ways to check whether a Spark DataFrame is empty.</p>
<h2 id="bd-setup" data-id="setup">2. Setup</h2>
<div class="bd-anchor" id="setup"></div>
<p>First, let&#8217;s create the following sample DataFrame:</p>
<pre><code class="language-java">public static Dataset getDataFrame(SparkSession spark) {
    List&lt;Row&gt; players = List.of(
      RowFactory.create(1, "Messi", "Argentina"),
      RowFactory.create(2, "Ronaldo", "Portugal"),
      RowFactory.create(3, "Mbappe", "France"));
    return spark.createDataFrame(players, PLAYER_SCHEMA);
}</code></pre>
<p>We&#8217;ll use this DataFrame throughout the examples.</p>
<h2 id="bd-using-the-isempty-method" data-id="using-the-isempty-method">3. Using the <em>isEmpty()</em> method</h2>
<div class="bd-anchor" id="using-the-isempty-method"></div>
<p><strong>Since Spark 2.4.0, the easiest way to check whether a Dataset is empty is by using the <em>isEmpty()</em> method</strong>.</p>
<p>Let&#8217;s create an empty DataFrame by filtering for a country that doesn&#8217;t exist in the dataset.</p>
<pre><code class="language-java">Dataset&lt;Row&gt; englandPlayers = allPlayers.filter(col("country").equalTo("England"));
Assertions.assertTrue(englandPlayers.isEmpty());
</code></pre>
<p>Since there are no England players in the dataframe, the filter returns an empty dataframe and <em>isEmpty()</em> returns true.</p>
<h2 id="bd-using-the-count-method" data-id="using-the-count-method">4. Using the <em>count()</em> method</h2>
<div class="bd-anchor" id="using-the-count-method"></div>
<p>Another way to check for emptiness is by counting the number of rows in a DataFrame and checking if it&#8217;s zero:</p>
<pre><code class="language-java">Dataset&lt;Row&gt; englandPlayers = allPlayers.filter(col("country").equalTo("England"));
Assertions.assertEquals(0, englandPlayers.count());</code></pre>
<p>Here, we invoked <em>count()</em> and verified that the number of rows is 0.</p>
<h2 id="bd-using-the-takeaslist-method" data-id="using-the-takeaslist-method">5. Using the <em>takeAsList()</em> Method</h2>
<div class="bd-anchor" id="using-the-takeaslist-method"></div>
<p>Another approach is to retrieve at most one row using <em>takeAsList():</em></p>
<pre><code class="language-">Dataset&lt;Row&gt; englandPlayers = allPlayers.filter(col("country").equalTo("England"));
Assertions.assertEquals(0, englandPlayers.takeAsList(1).size());</code></pre>
<p>Here we called the <em>takeAsList(1)</em>, which returns at most one row. We can then verify that the returned list is empty by checking whether its size is 0. <strong>Since only one row is requested, Spark can stop processing once it finds the first matching row</strong>, making this approach more efficient than counting every row.</p>
<p>Alternatively, we can also check if the list is empty instead of <em>size(</em>):</p>
<pre><code class="language-">Assertions.assertTrue(englandPlayers.takeAsList(1).isEmpty());</code></pre>
<h2 id="bd-comparing-methods-for-performance" data-id="comparing-methods-for-performance">6. Comparing Methods for Performance</h2>
<div class="bd-anchor" id="comparing-methods-for-performance"></div>
<p>Although all of these approaches can determine whether a DataFrame is empty, they don&#8217;t have the same performance characteristics.The <em>isEmpty()</em> method is the recommended approach because it&#8217;s specifically designed for this purpose. Internally, <strong>Spark only needs to determine whether at least one row exists, allowing it to stop processing as soon as a row is found</strong>.</p>
<p>Similarly, <em>takeAsList(1)</em> requests only a single row. Once Spark retrieves the first row, it can terminate the scan, making this approach much more efficient than counting every row. In contrast, <em>count()</em> computes the total number of rows in the DataFrame. Since Spark must process the entire dataset to produce an exact count, this approach is considerably more expensive for large DataFrames.</p>
<p>In general, we should prefer <em>isEmpty()</em> whenever it&#8217;s available. If we&#8217;re working with an older Spark version that doesn&#8217;t support it, <em>takeAsList(1).isEmpty()</em> is a good alternative. We should reserve <em>count()</em> for situations where we also need the total number of rows.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored several ways to determine whether a Spark DataFrame is empty. We started with the <em>isEmpty()</em> method, which is the simplest and most expressive solution, and then looked at alternatives based on <em>count()</em> and <em>takeAsList()</em>.</p>
<p><strong>In most situations, <em>isEmpty()</em> is the preferred choice because it&#8217;s both expressive and efficient</strong>. For older Spark versions, <em>takeAsList(1).isEmpty()</em> provides a practical alternative, while <em>count()</em> is best reserved for cases where the total number of rows is also needed.</p>
<p>As always, the sample code used in this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/apache-spark-2">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spark-dataframe-empty">Check if Spark Dataframe Is Empty</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/965089607/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/965089607/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/965089607/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/965089607/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/965089607/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/965089607/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/spark-dataframe-empty#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/spark-dataframe-empty/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/~/965089607/0/baeldung~Check-if-Spark-Dataframe-Is-Empty/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
</channel></rss>

