<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://feeds.feedblitz.com/feedblitz_rss.xslt"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:webfeeds="http://webfeeds.org/rss/1.0"  xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
<channel>
	<title>Baeldung</title>
	<atom:link href="https://www.baeldung.com/feed" rel="self" type="application/rss+xml" />
	<link>https://www.baeldung.com</link>
	<description>Java, Spring and Web Development tutorials</description>
	<lastBuildDate>Mon, 29 Jun 2026 05:46:14 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
<meta xmlns="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-dynamic-tool-discovery</feedburner:origLink>
		<title>Spring AI&#8217;s Dynamic Tool Discovery</title>
		<link>https://feeds.feedblitz.com/~/958677014/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958677014/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Kostiantyn Ivanov]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 02:50:03 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[LLM]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204066</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" fetchpriority="high" /><p>Learn how to use Tool Search Tool in Spring AI to find available tools without wasting extra tokens.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958677014/0/baeldung">Spring AI’s Dynamic Tool Discovery</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958677014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" 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="(max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>When we build AI-integrated systems, we often provide our AI clients with a large number of tools. On every request, we send the definitions of all available tools to the LLM so it can decide which ones to use. As a result, we waste a significant number of tokens before the model even processes the user query.  <strong>In this article, we explore how we solve this issue using the Tool Search Tool. </strong></p>
<h2 id="bd-how-the-tool-search-tool-works" data-id="how-the-tool-search-tool-works">2. How the Tool Search Tool Works</h2>
<div class="bd-anchor" id="how-the-tool-search-tool-works"></div>
<p>Using the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/spring-ai-community/spring-ai-tool-search-tool">Tool Search Tool</a><em>,</em> we don&#8217;t send all the tool definitions with the context. We only expose tools when the model actually needs them. First, we index all registered tools at startup. We store them inside the <em>ToolSearcher</em>, but we do NOT send them to the LLM. Next, we send only the Tool Search Tool in the initial request. This keeps the prompt small and focused. When the model needs a capability, it calls the Tool Search Tool using a natural-language query.</p>
<p>We treat this as a discovery signal and trigger a search over the indexed tools using the configured strategy. Next, we return only the most relevant matches from the ToolSearcher and inject their definitions into the next LLM request, so the model sees a focused set of tools instead of the full registry.</p>
<p data-start="305" data-end="488" data-is-last-node="" data-is-only-node=""><strong>Once the relevant tools are available, the model selects and calls the actual tool. We execute it and send the result back to the LLM, which then uses it to generate the final answer.</strong></p>
<h2 id="bd-building-a-travel-assistant-example" data-id="building-a-travel-assistant-example">3. Building a Travel Assistant Example</h2>
<div class="bd-anchor" id="building-a-travel-assistant-example"></div>
<p data-start="54" data-end="187">Let&#8217;s build a travel assistant that helps users plan trips. We connect multiple tools such as flights, hotels, weather, and attractions. <strong>We use the Tool Search Tool approach to avoid sending all tools to the LLM upfront. Instead, we discover tools dynamically at runtime.</strong></p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">3.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p>We start by adding <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/tool-search-tool">tool search</a> dependency support:</p>
<pre><code class="language-">&lt;dependency&gt;
    &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
    &lt;artifactId&gt;tool-search-tool&lt;/artifactId&gt;
    &lt;version&gt;${tool-search-tool.version}&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>Also, let&#8217;s add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/tool-searcher-regex/2.1.0">regex searcher</a> dependency:</p>
<pre><code class="language-">&lt;dependency&gt;
    &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
    &lt;artifactId&gt;tool-searcher-regex&lt;/artifactId&gt;
    &lt;version&gt;${tool-search-tool.version}&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Using it, we&#8217;ll have a <em>regex</em> tool search strategy. The other available strategies can be found in the project repository.</p>
<h3 id="bd-2-flight-tools" data-id="2-flight-tools">3.2. Flight Tools</h3>
<div class="bd-anchor" id="2-flight-tools"></div>
<p>Let’s create a simple <em>FlightTools</em>. We’ll use this tool to retrieve available flight options. In addition, we’ll create a bunch of artificial tools to simulate context overloading<em>:</em></p>
<pre><code class="language-java">public class FlightTools {
    @Tool(description = "Searches available flights between two cities")
    public List&lt;FlightOption&gt; searchFlights(String from, String to, String departureDate) {
        return List.of(
          new FlightOption(
            "Romania Airlines",
            from,
            to,
            departureDate,
            249.99
          )
        );
    }
}</code></pre>
<p>Here we return a single flight option.</p>
<h3 id="bd-3-tokencounteradvisor" data-id="3-tokencounteradvisor">3.3. <em>TokenCounterAdvisor</em></h3>
<div class="bd-anchor" id="3-tokencounteradvisor"></div>
<p>Now let’s create a simple <em>TokenCounterAdvisor</em> that counts the number of tokens used to produce the final result. We’ll use it to compare token usage between different setups, with and without tool search enabled:</p>
<pre><code class="language-java">public class TokenCounterAdvisor implements BaseAdvisor {
    private static final Logger log = LoggerFactory.getLogger(TokenCounterAdvisor.class);
    private final AtomicInteger totalTokenCounter = new AtomicInteger(0);
    @Override
    public String getName() {
        return "TokenCounterAdvisor";
    }
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 1;
    }
    @Override
    public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
        return chatClientRequest;
    }
    @Override
    public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
        var usage = chatClientResponse.chatResponse().getMetadata().getUsage();
        totalTokenCounter.addAndGet(usage.getTotalTokens());
        log.info("Total tokens spent: {}", totalTokenCounter.get());
        return chatClientResponse;
    }
}</code></pre>
<p>Here we store the number of tokens in an <em>AtomicInteger</em> field and log this information during execution. <strong>We attach this advisor to the maximum order, so it runs at the end of the processing pipeline. As a result, it captures the total token usage after all other advisors complete.</strong></p>
<h3 id="bd-4-configuration" data-id="4-configuration">3.4. Configuration</h3>
<div class="bd-anchor" id="4-configuration"></div>
<p>Next, we add the <em>TravelAssistantConfig</em> implementation:</p>
<pre><code class="language-java">@Configuration
public class TravelAssistantConfig {
    @Bean
    ToolSearcher toolSearcher() {
        return new RegexToolSearcher();
    }
    @Bean
    ToolSearchToolCallAdvisor toolSearchToolCallAdvisor(ToolSearcher toolSearcher) {
        return ToolSearchToolCallAdvisor.builder()
          .toolSearcher(toolSearcher)
          .maxResults(5)
          .build();
    }
    @Bean
    ChatClient chatClient(ToolSearchToolCallAdvisor toolSearchToolCallAdvisor, OpenAiChatModel model) {
        return ChatClient.builder(model)
          .defaultTools(
            new FlightTools(),
            new RandomTools()
          )
          .defaultAdvisors(toolSearchToolCallAdvisor, new TokenCounterAdvisor())
          .build();
    }
    @Bean
    ChatClient chatClientWithoutToolsSearch(OpenAiChatModel model) {
        return ChatClient.builder(model)
          .defaultTools(
            new FlightTools(),
            new RandomTools()
          )
          .defaultAdvisors(new TokenCounterAdvisor())
          .build();
    }
}</code></pre>
<p>We configure a travel assistant that uses dynamic tool discovery instead of loading all tools into the LLM. Next, we set up a <em>ToolSearcher</em> with a <em>RegexToolSearcher</em> implementation. This allows us to match tools based on naming patterns and fast keyword-like queries. Then, we create a <em>ToolSearchToolCallAdvisor</em> and connect it to the searcher. After that, we build the <em>ChatClient</em> with the flight tools registered.</p>
<p><span style="margin: 0px;padding: 0px">By design, we&#8217;ve added <em>RandomTools</em>, which includes many unrelated tool definitions</span>.  However, we do not send these tool definitions to the LLM initially. Instead, we only index them in the system. <strong>Finally, we expose only the Tool Search Tool to the model at the start. The model then uses it to discover which tools it actually needs for a given request.</strong> Additionally, we&#8217;ve configured a separate <em>ChatClient</em> bean that doesn&#8217;t use the <em>ToolSearchToolCallAdvisor</em>.</p>
<h3 id="bd-5-call-the-travelassistant" data-id="5-call-the-travelassistant">3.5. Call the TravelAssistant</h3>
<div class="bd-anchor" id="5-call-the-travelassistant"></div>
<p>Finally, let&#8217;s create a <em>ToolsSearchToolLiveTest</em> with similar test cases for both clients:</p>
<pre><code class="language-java">@SpringBootTest
@ActiveProfiles("toolsearchtool")
class ToolsSearchToolLiveTest {
    @Autowired
    private ChatClient chatClient;
    @Autowired
    private ChatClient chatClientWithoutToolsSearch;
    @Test
    void shouldFindFlightsBetweenRomaniaAndCroatiaUsingToolsSearch() {
        String response = getClientResponseString(chatClient);
        assetClientResponse(response);
    }
    @Test
    void shouldFindFlightsBetweenRomaniaAndCroatiaWithoutToolsSearch() {
        String response = getClientResponseString(chatClientWithoutToolsSearch);
        assetClientResponse(response);
    }
    
    private static void assetClientResponse(String response) {
        assertThat(response).isNotBlank();
        assertThat(response).containsIgnoringCase("Croatia");
        assertThat(response).containsIgnoringCase("flight");
    }
    private String getClientResponseString(ChatClient chatClientWithoutToolsSearch) {
        return chatClientWithoutToolsSearch.prompt()
          .user("""
                  Find available flights from Romania to Croatia next week.
                  """)
          .call()
          .content();
    }
}</code></pre>
<p>We&#8217;ve called our travel advisor clients with the same prompt and obtained the same verified results. Now, let&#8217;s compare the token usage in both of them:</p>
<pre><code class="language-">[2026-05-24 11:39:07] [INFO] [c.b.s.t.TokenCounterAdvisor] - Total tokens spent: 974 //With tools search tool
[2026-05-24 11:39:10] [INFO] [c.b.s.t.TokenCounterAdvisor] - Total tokens spent: 3685 //Without tools search tool</code></pre>
<p>As we can see, the difference in token usage is crucial. <strong>The more tools we have in our system, the greater the token savings the Tool Search Tool will provide.</strong></p>
<h2 id="bd-conclusion" data-id="conclusion">4. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we reviewed the Tool Search Tool and demonstrated how it helps reduce token usage in real scenarios. Using it, we can build large AI-integrated systems with hundreds of attached tools and use them efficiently, without wasting tokens. Additionally, we can explore other tool search strategies, such as vector search, or even build our own custom strategy to make tool discovery even more efficient.</p>
<p>As always, the code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-4">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-dynamic-tool-discovery">Spring AI’s Dynamic Tool Discovery</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958677014/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958677014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958677014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-dynamic-tool-discovery/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958677014/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-26-new-features</feedburner:origLink>
		<title>New Features in Java 26</title>
		<link>https://feeds.feedblitz.com/~/958677017/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958677017/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Karthikeya Tatavarthi]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 02:46:15 +0000</pubDate>
				<category><![CDATA[Core Java]]></category>
		<category><![CDATA[>= Java 26]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-26-new-features</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" /><p>Explore all key features introduced in Java 26.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958677017/0/baeldung">New Features in Java 26</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958677017/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-26-new-features#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-26-new-features/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>Oracle released Java 26 in March, which offered meaningful features that boost performance, new APIs, and preview features that may become permanent features in future releases. <strong>This release primarily focused on improvements in areas like garbage collection, networking, startup performance, cryptography, and other language features.</strong></p>
<p>In this tutorial, we’ll briefly talk about all key features introduced in Java 26.</p>
<h2 id="bd-restricts-reflective-modification-of-final-fields" data-id="restricts-reflective-modification-of-final-fields"><strong>2. Restricts Reflective Modification of <em>final</em> Fields</strong></h2>
<div class="bd-anchor" id="restricts-reflective-modification-of-final-fields"></div>
<p>We create <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-final"><em>final</em> variables</a> to ensure developers that their values can&#8217;t be changed after initialization. This will help us create immutable objects and help the JVM perform certain optimizations. However, this can be breached using reflections.</p>
<p><strong>There are libraries and frameworks that use reflections to modify final fields after an object has been created.</strong> This will invalidate the assumptions made by developers, and JVM optimizations may no longer hold true.</p>
<p>Starting from Java 26, the platform takes another step towards stronger encapsulation and integrity. When code tries to modify a final field using deep reflection, the JVM shows warnings indicating to the developer that the operation relies on a behavior that is being phased out. Even though this is currently not enforced, the direction is clear; future Java releases are expected to impose stronger restrictions.</p>
<p>This will impact certain serialization libraries, dependency injection frameworks, mocking tools, and legacy code that bypasses constructors to populate object state.</p>
<p><strong>Developers should now start to audit and identify reflective writes to final fields and migrate toward supported alternatives such as constructors, builders, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/factory-method-vs-factory-vs-abstract-factory">factory methods</a>, or records or use framework-provided APIs designed for immutable objects.</strong></p>
<h2 id="bd-removal-of-applet-api" data-id="removal-of-applet-api"><strong>3. Removal of Applet API</strong></h2>
<div class="bd-anchor" id="removal-of-applet-api"></div>
<p>Applets were historically used to run Java applications in browsers. However, modern browsers don&#8217;t support applets due to performance, compatibility, and security concerns. This is considered an outdated technology, and this API has been marked deprecated since Java 9. Developers nowadays use much better alternatives like Swing/<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/javafx">JavaFX</a> for UI.</p>
<p>With Java 26, the applet API is no longer existent.<strong> This update helps Oracle keep Java streamlined and secured while also reducing the maintenance burden. Its removal will affect only very old legacy applications that still depend on it.</strong></p>
<h2 id="bd-ahead-of-time-object-caching-with-any-gc" data-id="ahead-of-time-object-caching-with-any-gc"><strong>4. Ahead of Time Object Caching with Any GC</strong></h2>
<div class="bd-anchor" id="ahead-of-time-object-caching-with-any-gc"></div>
<p>Java is known for building distributed cloud-native applications that are frequently deployed, scaled, started, and stopped. Oracle is working towards bringing enhancements that reduce the start-up overhead, which allows applications to reach their peak performance more quickly. In Java 26, Oracle introduced one such enhancement.</p>
<p>Java has Ahead of Time (AOT) object caching, which identifies the objects that are expensive to create during startup and stores them in a cache. <strong>When the application starts again, these objects are loaded directly from the cache instead of recreating them, thereby reducing the start-up overhead.</strong></p>
<p>The benefits of Ahead of Time (AOT) are limited to specific GC configurations. <strong>However, in Java 26, this feature is available to any garbage collector, including low-latency collectors such as Z Garbage Collector (ZGC).</strong></p>
<p>This enhancement allows developers to take advantage of AOT object caching without giving up their preferred garbage collector.</p>
<h2 id="bd-http3-for-the-http-client-api" data-id="http3-for-the-http-client-api"><strong>5. HTTP/3 for the HTTP Client API</strong></h2>
<div class="bd-anchor" id="http3-for-the-http-client-api"></div>
<p>Network resilience and latency are key factors for applications that communicate heavily over the network, like microservices, real-time applications, and cloud-native services. <strong>With Java 26, the HTTP Client API now supports HTTP/3, which is the latest version of the HTTP protocol.</strong></p>
<p>While HTTP/1.1 and HTTP/2 were built on top of TCP, HTTP/3 uses the QUIC transport protocol, which runs on UDP and addresses some of the limitations of the TCP protocol. With TCP, a packet loss can delay all streams that are sharing the same connection, while QUIC allows streams to progress independently. QUIC achieves this by combining both transport and security handshakes to reduce the number of network round trips required before data starts flowing.</p>
<p><strong>Another practical advantage is automatic protocol negotiation. If the target server supports HTTP/3, the client can use it. If not, it can seamlessly fall back to HTTP/2 or HTTP/1.1, ensuring compatibility with existing infrastructure.</strong></p>
<p>With the introduction of HTTP/3, applications can now experience reduced latency and improved responsiveness. This is especially useful for real-time and cloud-native applications.</p>
<p>Let&#8217;s now take a look at a quick example:</p>
<pre><code class="language-java">@Test
void givenHttp3Client_whenSendingARequest_thenShouldReceiveSuccessfulResponse()
    throws IOException, InterruptedException {
    HttpClient client = HttpClient.newBuilder()
      .version(HttpClient.Version.HTTP_3)
      .build();
    HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://example.com"))
      .GET()
      .build();
    HttpResponse&lt;String&gt; response =
      client.send(request, HttpResponse.BodyHandlers.ofString());
    assertEquals(200, response.statusCode());
}</code></pre>
<p>To use HTTP/3, we configure the <em>HttpClient</em> by specifying <em>HttpClient.Version.HTTP_3</em> while building the client. We then create a simple <em>GET</em> request to <em>https://example.com</em> and send it synchronously using the <em>send()</em> method. Finally, we verify that the request completes successfully by asserting that the response status code is <em>200</em>.</p>
<p>It&#8217;s worth noting that configuring the client for HTTP/3 doesn&#8217;t guarantee that the connection will use HTTP/3. If the target server doesn&#8217;t support HTTP/3, the client automatically falls back to an earlier HTTP version (HTTP/2 or HTTP/1.1), while still completing the request successfully.</p>
<h2 id="bd-g1-garbage-collector-throughput-improvements" data-id="g1-garbage-collector-throughput-improvements"><strong>6. G1 Garbage Collector Throughput Improvements</strong></h2>
<div class="bd-anchor" id="g1-garbage-collector-throughput-improvements"></div>
<p>For quite some time, the Garbage First (G1) garbage collector has been Java&#8217;s default <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jvm-garbage-collectors">garbage collector</a>. It offers an impressive blend of throughput, memory, and predictability of pause times.</p>
<p>A typical modern Java application will constantly create or update object references. G1 is responsible for tracking object reference changes to perform garbage collection. Ultimately, there is some overhead in this tracking, particularly in applications with high allocation rates or frequent object updates.</p>
<p>With Java 26, G1 now has a set of internal changes that improve the throughput of an application. <strong>One such change is reducing the need for mutator application threads and garbage collection (GC) threads to synchronize when tracking the changes of which object references have been changed. </strong> Because of these improvements, application threads will synchronously wait less time related to GC and more time performing application logic.</p>
<h2 id="bd-pem-encodings-of-cryptographic-objects-second-preview" data-id="pem-encodings-of-cryptographic-objects-second-preview"><strong>7. PEM Encodings of Cryptographic Objects (Second Preview)</strong></h2>
<div class="bd-anchor" id="pem-encodings-of-cryptographic-objects-second-preview"></div>
<p>Applications that deal with security often need to handle certificates, public and private keys, and certificate chains. <strong>Cryptographic objects are usually stored and transmitted using PEM (Privacy Enhanced Mail)</strong>. PEM is a text-based format that allows binary security data to be formatted in a compact manner.</p>
<p>Java developers have historically been required to deploy third-party libraries or write code of their own to read and write PEM files. This has made security-related applications both complex and cumbersome due to additional dependencies and code that serves little or no purpose.</p>
<p>Java 26 introduces the second preview of a newly proposed API to PEM format cryptographic objects that adds the capability to Java SE.<strong> Using the proposed API brings the capability to Java developers to read and write certificates and keys using the standard Java SDK instead of using libraries.</strong></p>
<p>The proposed API enhances Java&#8217;s capability to integrate with standard security protocols and infrastructure. It also simplifies the management of certificates and keys. In addition, the API reduces the time and effort to write custom code for applications. Even if it is still a preview, it provides a more consistent and pragmatic approach to Java security.</p>
<h2 id="bd-structured-concurrency-sixth-preview" data-id="structured-concurrency-sixth-preview"><strong>8. Structured Concurrency (Sixth Preview)</strong></h2>
<div class="bd-anchor" id="structured-concurrency-sixth-preview"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-structured-concurrency">Structured concurrency</a> adds to Project Loom&#8217;s collection of tools to simplify concurrent programming in Java by proposing that multiple related tasks be treated as a unit of work, rather than requiring developers to manage multiple threads separately.</p>
<p>In traditional concurrent code, coordinating threads, handling failures, and cleaning up resources can become complex. <strong>Structured concurrency allows related tasks to run within the defined scope, and if one task fails, other related tasks will be cancelled automatically</strong>. This helps prevent wasted work.</p>
<p>Java 26 includes the sixth preview of Structured Concurrency, bringing further refinements based on real-world usage and community feedback as the feature moves closer to becoming a permanent part of the platform in the future.</p>
<h2 id="bd-lazy-constants-second-preview" data-id="lazy-constants-second-preview"><strong>9. Lazy Constants (Second Preview)</strong></h2>
<div class="bd-anchor" id="lazy-constants-second-preview"></div>
<p>In a normal scenario, class constants are created when the class gets loaded, even though the application never uses them. This contributes to increased startup time and creation of unnecessary objects.</p>
<p>To address this, developers used to introduce their own code to create constants only when they were actually needed. Often this code is complex, less readable, and harder to maintain.</p>
<p><strong>With lazy constants, this mechanism is now built in. Developers can now reap the benefits of lazy loading without having to introduce complex code.</strong> By creating objects only when they are first used, applications can now start faster and use fewer resources. Java 26 includes the second preview of this feature.</p>
<h2 id="bd-10-vector-api-eleventh-incubator" data-id="10-vector-api-eleventh-incubator"><strong>10. Vector API (Eleventh Incubator)</strong></h2>
<div class="bd-anchor" id="10-vector-api-eleventh-incubator"></div>
<p>Vector API in Java 26 is an incubator feature, which allows developers to take advantage of SIMD (Single Instruction, Multiple Data).</p>
<p>Normally, a processor performs an operation on one value at a time. With SIMD, the same operation can be performed on multiple values at once. This helps applications achieve performance closer to native code while remaining portable across different CPU architectures.</p>
<p><strong>This will significantly improve the performance for computation-heavy workloads such as scientific computing, data analytics, image processing, machine learning, and financial calculations.</strong></p>
<h2 id="bd-11-primitive-types-in-patterns-instanceof-and-switch-fourth-preview" data-id="11-primitive-types-in-patterns-instanceof-and-switch-fourth-preview"><strong>11. Primitive Types in Patterns, instanceof, and switch (Fourth Preview)</strong></h2>
<div class="bd-anchor" id="11-primitive-types-in-patterns-instanceof-and-switch-fourth-preview"></div>
<p>Pattern matching makes code easier to read and reduces the need for manual type checks and casting. In previous releases, this is only supported for reference types.</p>
<p><strong>However, with Java 26, this is also included for primitive types like int, long, float, and double.</strong> This allows developers to work with primitive values more naturally in pattern matching constructs, including switch statements and other pattern-based control flow.</p>
<h2 id="bd-12-conclusion" data-id="12-conclusion"><strong>12. Conclusion</strong></h2>
<div class="bd-anchor" id="12-conclusion"></div>
<p>We’ve seen various improvements and new features introduced in Java 26. While they are a mix of permanent and preview features, it’s evident that the Java community is moving towards making Java ideal for modern-day enterprise application development.</p>
<p><span style="font-weight: 400">The source code for the examples can be found </span><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-26-new-features"><span style="font-weight: 400">over on GitHub</span></a><span style="font-weight: 400">.</span></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-26-new-features">New Features in Java 26</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958677017/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958677017/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958677017/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-26-new-features#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-26-new-features/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958677017/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-apache-paimon-guide</feedburner:origLink>
		<title>A Guide to Apache Paimon Java API</title>
		<link>https://feeds.feedblitz.com/~/958675751/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958675751/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Parthiv Pradhan]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 02:37:29 +0000</pubDate>
				<category><![CDATA[Persistence]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-apache-paimon-guide</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Explore the Java API for Apache Paimon and learn how to perform CRUD operations on a Paimon database.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958675751/0/baeldung">A Guide to Apache Paimon Java API</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958675751/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-apache-paimon-guide#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-apache-paimon-guide/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>In this tutorial, we&#8217;ll explore the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://paimon.apache.org/docs/1.4/program-api/java-api/">Java API </a><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://paimon.apache.org/docs/1.4/program-api/java-api/" target="_blank" rel="noopener">for Apache Paimon,</a> which is essential for managing the Paimon database from custom applications. We&#8217;ll use it to create a data lake with a table in HDFS storage and perform <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/crud-operations">CRUD</a> operations on it.</p>
<p><strong>This open-source data lakehouse format provides a unified storage layer for real-time streaming, batch processing, and OLAP</strong>. It can serve as a message queue for a streaming application and as a HIVE table for a batch processing application. It supports building data lakes on a variety of storage systems, including object storage, HDFS, and local file systems. Furthermore, it integrates seamlessly with popular distributed data processing engines such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-spark">Apache Spark</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-flink">Apache Flink</a>, and Trino to build scalable data pipelines.</p>
<h2 id="bd-prerequisites" data-id="prerequisites">2. Prerequisites</h2>
<div class="bd-anchor" id="prerequisites"></div>
<p>Let&#8217;s start with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.paimon/paimon-bundle">Maven dependencies</a> required in a Java application to integrate with Apache Paimon:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.paimon&lt;/groupId&gt;
    &lt;artifactId&gt;paimon-bundle&lt;/artifactId&gt;
    &lt;version&gt;1.4.1&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>For our example, we&#8217;ll use HDFS as the storage, and hence, let&#8217;s include the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client-runtime">runtime library</a> for interacting with it:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.hadoop&lt;/groupId&gt;
    &lt;artifactId&gt;hadoop-client-runtime&lt;/artifactId&gt;
    &lt;version&gt;3.4.3&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-create-data-lake" data-id="create-data-lake">3. Create Data Lake</h2>
<div class="bd-anchor" id="create-data-lake"></div>
<p>Let&#8217;s consider a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/data-lake-introduction">data lake</a> to store IT infrastructure performance metrics collected by monitoring tools that poll devices. These metrics can quickly accumulate in volume, and data lakes such as Paimon can reliably store them for long periods.</p>
<p>Let&#8217;s define the data model:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/metrics-datamodel.png"><img decoding="async" class="alignnone size-full wp-image-247667" src="https://www.baeldung.com/wp-content/uploads/2026/06/metrics-datamodel.png" alt="Metrics data model" /></a>
<p data-start="52" data-end="325">Before ingesting monitoring data into Apache Paimon, we need to set up the storage layer by creating a catalog and defining the table schema. <strong>In Paimon, the catalog acts as the entry point to the data lake, while tables define how data is organized, typed, and versioned</strong>.</p>
<p data-start="327" data-end="467">Moving on, let’s look at a simple utility class responsible for initializing the data lake and creating a metrics table:</p>
<pre><code class="language-java">public class PaimonDatabaseManager {
    public static Catalog createCatalog(String warehousePath) {
        CatalogContext context = CatalogContext.create(new Path(warehousePath));
        return CatalogFactory.createCatalog(context);
    }
    public static Identifier createTable(Catalog catalog) throws Exception {
        Schema schema = Schema.newBuilder()
          .column("device_id", DataTypes.STRING())
          .column("metrics_name", DataTypes.STRING())
          .column("metrics_value", DataTypes.DOUBLE())
          .column("source", DataTypes.STRING())
          .column("create_time", DataTypes.TIMESTAMP(3))
          .column("state", DataTypes.STRING())
          .primaryKey("device_id", "metrics_name", "create_time")  
          .build();
        Identifier tableId = Identifier.create("metric_db", "metrics");
        catalog.createDatabase("metric_db", true);
        catalog.createTable(tableId, schema, false);
        return tableId;
    }
}</code></pre>
<p><strong>In the class, <em>PaimonDatabaseManager.createCatalog()</em> creates a <em>Catalog</em> instance backed by the local filesystem, using a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-common/1.4.1/org/apache/paimon/catalog/CatalogContext.html"><em>CatalogContext</em></a> to configure the warehouse location.</strong> Finally, <em>CatalogFactory.createCatalog()</em> uses the context to create the Paimon database.</p>
<p>Furthermore, the <em>PaimonDatabaseManager.createTable()</em> method builds a <em>Schema</em> for the metrics table and uses an <em>Identifier</em> to register it under the <em>metric_db</em> database. The table schema includes typed columns and a composite primary key.</p>
<p>Now, we&#8217;ll use the <em>PaimonDatabaseManager</em> class in a program to create a Paimon database table:</p>
<pre><code class="language-java">void whenCallCreateTable_thenTableCreated() throws Exception {
    catalog = PaimonDatabaseManager.createCatalog(WAREHOUSE_PATH);
    assertNotNull(catalog);
    
    tableId = PaimonDatabaseManager.createTable(catalog);
    assertNotNull(tableId);
    assertTrue(catalog.listTables("metric_db").contains("metrics"));
}</code></pre>
<p>In the program, we first call <em>PaimonDatabaseManager.createCatalog(WAREHOUSE_PATH)</em> to create a Paimon database catalog. The argument <em>WAREHOUSE_PATH</em> contains the path to a temporary directory created for the database. Next, we call <em>PaimonDatabaseManager.createTable()</em> to create a <em>metrics</em> table in the catalog. Finally, we invoke <em>Catalog.listTables()</em> to list all the tables in the <em>metric_db</em> database and confirm that it contains the <em>metrics</em> table<em>. </em></p>
<h2 id="bd-insert-records-in-table" data-id="insert-records-in-table">4. Insert Records in Table</h2>
<div class="bd-anchor" id="insert-records-in-table"></div>
<p>In this section, we&#8217;ll learn about the API to insert metric records into the Paimon <em>metric</em> table:</p>
<pre><code class="language-java">public class PaimonTableDataManager {
    public static void insert(Catalog catalog, Identifier tableId, List metrics) throws Exception {
        Table table = catalog.getTable(tableId);
        BatchWriteBuilder builder = table.newBatchWriteBuilder();
        BatchTableWrite write = builder.newWrite();
        metrics.forEach(metric -&gt; {
          try { 
              GenericRow row = createGenericRow(metric);
              write.write(row, 0);
          } catch (Exception e) {
              logger.error("Error writing metric", e);
          }
        });
        List messages = write.prepareCommit();
        BatchTableCommit commit = builder.newCommit();
        commit.commit(messages);
    }
}</code></pre>
<p>First, we retrieve the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/Table.html"><em>Table</em></a> object associated with the <em>tableId</em> from the <em>Catalog</em> object. Next, we get an instance of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/sink/BatchWriteBuilder.html"><em>BatchWriteBuilder</em></a> class by invoking <em>Table#newBatchWriteBuilder().</em> <strong>The builder object helps create a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/sink/BatchTableWrite.html"><em>BatchTableWrite</em></a> object that executes write operations on tables</strong>.</p>
<p>Further, we transform each metric record into a <em>GenericRow</em> object in the <em>createGenericRow()</em> method:</p>
<pre><code class="language-java">private static GenericRow createGenericRow(Metric metric) {
    GenericRow row = GenericRow.of(
      BinaryString.fromString(metric.getDeviceId()),
      BinaryString.fromString(metric.getMetricsName()),
      metric.getMetricsValue(),
      BinaryString.fromString(metric.getSource()),
      convertToTimestamp(metric.getCreateTime()),
      BinaryString.fromString(metric.getCreatedBy()),
      BinaryString.fromString(metric.getState())
    );
    return row;
}</code></pre>
<p>During this process, each column value is converted to its corresponding Paimon-compatible data type, such as <em>BinaryString</em> and <em>Timestamp</em>.</p>
<p>After creating the <em>GenericRow</em> object in the insert method, <em>BatchTableWrite.write()</em> writes each row into the <em>metrics</em> table. Finally, the data is committed in a single batch outside the loop.</p>
<p>Let&#8217;s run the <em>PaimonTableDataManager.insert()</em> to insert some records into the <em>metrics</em> table:</p>
<pre><code class="language-java">void whenCallInsertRecords_thenRecordsInserted() throws Exception {
    assertTrue(catalog.listTables("metric_db").contains("metrics"));
    PaimonTableDataManager.insert(catalog, tableId, getMetrics());
}</code></pre>
<p>First, with the assert method, we ensure that the <em>metric</em> table exists in the catalog. The arguments, <em>catalog</em>, and <em>tableId</em> hold the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/catalog/Catalog.html"><em>Catalog</em></a> and table <em>Identifier</em> objects that we retrieved and stored while creating the <em>metrics</em> table. The <em>getMetrics()</em> method could be a service fetching the metric data from a monitoring tool. Moreover, for our example, we retrieve the synthetic data from a CSV file <em>metrics.out</em>:</p>
<pre><code class="language-bash">device_id,metric_name,create_time,metric_value,source
dev_101,cpu_usage,2026-04-21 18:57:01,72.5,agent
dev_102,cpu_usage,2026-04-21 18:57:02,65.1,agent
dev_103,memory_usage,2026-04-21 18:57:03,58.3,agent
...</code></pre>
<p>However, real-world applications usually poll monitoring tools at regular intervals to fetch the latest metrics. Finally, we invoke the insert method to create new records into the metrics table.</p>
<h2 id="bd-query-records-from-table" data-id="query-records-from-table">5. Query Records From Table</h2>
<div class="bd-anchor" id="query-records-from-table"></div>
<p>After the raw metrics are collected, they must be fetched from the database and then processed to derive actionable analytics. The API supports filter pushdown and column projections, helping enhance performance. Additionally, the API supports both batch reads and streaming data for analytics and real-time use cases.</p>
<p>Let&#8217;s use the Paimon Java API to implement a batch read operation:</p>
<pre><code class="language-java">public static List&lt;Metric&gt; fetchMetricsBySourceAndDateRange(Catalog catalog, Identifier tableId,
      String source, String startDate, String endDate) throws Exception {
    Table table = catalog.getTable(tableId);
    RowType rowType = table.rowType();
    PredicateBuilder predicateBuilder = new PredicateBuilder(rowType);
    int[] projection = new int[] {0, 1, 2, 3, 4};
    Predicate sourcePredicate = predicateBuilder.equal(3, BinaryString.fromString(source));
    Predicate dateRangePredicate = predicateBuilder.between(
      4, convertToTimestamp(startDate), convertToTimestamp(endDate)
    );
    Predicate predicate = PredicateBuilder.and(sourcePredicate, dateRangePredicate);
    ReadBuilder readBuilder = table.newReadBuilder().withFilter(predicate).withProjection(projection);
    List&lt;Split&gt; splits = readBuilder.newScan().plan().splits();
    TableRead read = readBuilder.newRead().executeFilter();
    RecordReader&lt;InternalRow&gt; reader = read.createReader(splits);
    List&lt;Metric&gt; results = new ArrayList&lt;&gt;();
    reader.forEachRemaining(internalRow -&gt; {
        String deviceId = internalRow.getString(0).toString();
        String metricsName = internalRow.getString(1).toString();
        double metricsValue = internalRow.getDouble(2);
        String sourceValue = internalRow.getString(3).toString();
        Timestamp timestamp = internalRow.getTimestamp(4, 3);
        String createTime = ConvertTimestampToStr(timestamp); 
        Metric metric = new Metric(deviceId, metricsName, metricsValue, sourceValue, createTime, null);
        logger.info("Fetched Metric: {}", metric);
        results.add(metric);
    });
    return results;
}</code></pre>
<p><strong>The <em>fetchMetricsBySourceAndDateRange()</em> method queries the <em>metrics</em> tables to fetch metric data originating from a specific source within a given date range</strong>. In the method, the <em>PredicateBuilder</em> helps create <em>Predicate</em> objects with filter conditions on the <em>source</em> and <em>create_time</em> columns. The <em>Table#newReadBuilder()</em> creates a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/source/ReadBuilder.html"><em>ReadBuilder</em></a> object, and subsequently, <em>ReadBuilder#withFilter()</em> sets the necessary predicates. Additionally, we define a <em>projection</em> array to fetch only the required columns.</p>
<p>Moving on, the builder creates data splits that can be distributed across multiple threads or processes, enabling faster execution. The splits are comparable to partitions or data files corresponding to a table. Next, <em>ReadBuilder#newRead()</em> creates a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/static/org.apache.paimon/paimon-core/1.4.1/org/apache/paimon/table/source/TableRead.html"><em>TableRead</em></a> object on which we invoke the <em>executeFilter()</em> method. After this, <em>TableRead#createReader()</em> creates a <em>RecordReader</em> for the generated splits. Multiple readers can process different splits concurrently, enabling scalable and efficient parallel reads. Finally, we call <em>RecordReader#forEachRemaining()</em> to iterate through the rows and fetch the metric attributes.</p>
<p>Now, let&#8217;s run the program to fetch a metric from a certain source and within a date range:</p>
<pre><code class="language-java">void whenCallReadRecords_thenRecordsRead() throws Exception {
    List&lt;Metric&gt; metrics = PaimonTableDataManager.fetchMetricsBySourceAndDateRange(
      catalog, tableId, "collector", "2026-04-21 18:58:19", "2026-04-21 18:58:30");
    assertFalse(metrics.isEmpty());
    assertTrue(metrics.size() == 4);
}</code></pre>
<p>The program fetched four metric rows with the source <em>collector</em> and created between <em>2026-04-21 18:58:19</em> and <em>2026-04-21 18:58:30</em>.</p>
<h2 id="bd-update-table-records" data-id="update-table-records">6. Update Table Records</h2>
<div class="bd-anchor" id="update-table-records"></div>
<p>There&#8217;s no particular Java API for updating records in a Paimon Table. However, <strong>when we perform a write operation, just like in the case of performing an insert on an existing record with the primary key, instead of a new row, the existing row gets updated</strong>.</p>
<p>To understand the update operation, we&#8217;ve defined the method in the <em>PaimonTableDataManager </em>class:</p>
<pre><code class="language-java">public static void updateMetricStateByDeviceIdMetricNameAndCreatedDate(Catalog catalog,
      Identifier tableId, String deviceId, String metricName, String newState, String createdDate)
    throws Exception {
    Metric metric = fetchMetricByDeviceIdMetricNameAndCreatedDate(
      catalog, tableId, deviceId, metricName, createdDate
    );
    Table table = catalog.getTable(tableId);
    BatchWriteBuilder builder = table.newBatchWriteBuilder();
    BatchTableWrite write = builder.newWrite();
    metric.setState(newState);
    write.write(createGenericRow(metric), 0);
    List&lt;CommitMessage&gt; messages = write.prepareCommit();
    BatchTableCommit commit = builder.newCommit();
    commit.commit(messages);
}</code></pre>
<p>First, we fetch a metric record filtered by the primary key, composed of metric attributes such as device ID, metric name, and created date, by invoking the <em>fetchMetricByDeviceIdMetricNameAndCreatedDate()</em> method<em>.</em> The method is in the same line as the method described in the previous section, which queries records from the metric table.</p>
<p>Then, we update the <em>Metric#state</em> attribute with a new state. The remaining steps are largely similar to the insert method described earlier. However, instead of creating a new record, the existing metric&#8217;s state is updated with a new state.</p>
<p>Let&#8217;s execute <em>updateMetricStateByDeviceIdMetricNameAndCreatedDate()</em>:</p>
<pre><code class="language-java">void whenCallUpdateRecord_thenRecordUpdated() throws Exception {
    Metric metric = PaimonTableDataManager
      .fetchMetricByDeviceIdMetricNameAndCreatedDate(
        catalog, tableId, 
        "dev_137", "cpu_usage",
        "2026-04-21 18:58:27"
    );
    assertNotNull(metric);
    assertEquals("active", metric.getState());
    PaimonTableDataManager.updateMetricStateByDeviceIdMetricNameAndCreatedDate(
        catalog, tableId, "dev_137",
            "cpu_usage", "inactive", 
            "2026-04-21 18:58:27"
    );
    metric = PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate(
        catalog, tableId, "dev_137", 
        "cpu_usage", "2026-04-21 18:58:27"
    );
    assertNotNull(metric);
    assertEquals("inactive", metric.getState());
}</code></pre>
<p>We fetched a metric record by its composite primary key and updated its state to <em>inactive</em>. Later, we again fetched the record and confirmed that the state has been updated to <em>inactive</em>.</p>
<h2 id="bd-delete-table-records" data-id="delete-table-records">7. Delete Table Records</h2>
<div class="bd-anchor" id="delete-table-records"></div>
<p>Moving on, Paimon Java API&#8217;s also supports the delete operation:</p>
<pre><code class="language-java">public static void deleteRecordsByDeviceIdMetricNameAndCreatedDate(Catalog catalog, 
    Identifier tableId, String deviceId, 
    String metricsName, String createdDate) throws Exception {
    Metric metric = PaimonTableDataManager
     .fetchMetricByDeviceIdMetricNameAndCreatedDate(
       catalog, tableId, deviceId, metricsName, createdDate
    );
    Table table = catalog.getTable(tableId);
    BatchWriteBuilder builder = table.newBatchWriteBuilder();
    BatchTableWrite write = builder.newWrite();
    GenericRow deleteRow = createGenericRow(metric);
    deleteRow.setRowKind(RowKind.DELETE);
    write.write(deleteRow, 0);
    List&lt;CommitMessage&gt; messages = write.prepareCommit();
    builder.newCommit().commit(messages);
}</code></pre>
<p>First, we fetched a metric record by calling <em>PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate(). </em>The next set of steps is almost similar to the update method, except that we don&#8217;t modify any field in the metric record. Later, <strong>before invoking <em>BatchTableWrite#write()</em>, we flag the operation as delete by calling the method <em>BatchTableWrite#setRowKind(RowKind.DELETE)</em></strong>.</p>
<p>Let&#8217;s verify the <em>deleteRecordsByDeviceIdMetricNameAndCreatedDate()</em> method by executing it:</p>
<pre><code class="language-java">void whenCallDeleteRecord_thenRecordDeleted() throws Exception {
    Metric metric = PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate(
      catalog, tableId, 
      "dev_136", "disk_io", "2026-04-21 18:58:26"
    );
    assertNotNull(metric);
    PaimonTableDataManager.deleteRecordsByDeviceIdMetricNameAndCreatedDate(
      catalog, tableId, 
      "dev_136", "disk_io", "2026-04-21 18:58:26"
    );
    metric = PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate(
      catalog, tableId, "dev_136", "disk_io", "2026-04-21 18:58:26"
    );
    assertTrue(metric == null);
}</code></pre>
<p>First, we fetch a metric record corresponding to a device by invoking <em>PaimonTableDataManager.fetchMetricByDeviceIdMetricNameAndCreatedDate()</em>. After ensuring the record isn&#8217;t <em>null</em>, we invoke the delete method. Finally, we query the metric table again and confirm that no such record is present in the Paimon DB.</p>
<h2 id="bd-conclusion" data-id="conclusion">8. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored the core Java APIs for managing a Paimon database, including creating tables, performing batch insertions, updates, deletes, and executing filtered data queries. <strong>We used the local file system to create the Paimon DB, but the APIs are standard and effective irrespective of the chosen storage layer</strong>.</p>
<p>As usual, the source code can be found <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/persistence-modules/apache-paimon">over on GitHub.</a></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-apache-paimon-guide">A Guide to Apache Paimon Java API</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958675751/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958675751/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958675751/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-apache-paimon-guide#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-apache-paimon-guide/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958675751/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-kafka-commitfailedexception</feedburner:origLink>
		<title>Understanding and Avoiding CommitFailedException in Kafka</title>
		<link>https://feeds.feedblitz.com/~/958534139/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958534139/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Saikat Chakraborty]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:53:49 +0000</pubDate>
				<category><![CDATA[Data]]></category>
		<category><![CDATA[Kafka]]></category>
		<category><![CDATA[Messaging]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-kafka-commitfailedexception</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="A robot with a vacuum hand that is pulling in various bits of data and outputing graphs and charts with its other hand. On the far left are the words &quot;Data on Baeldung&quot;." style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn about what causes the CommitFailedException when working with Kafka in Java and some approaches to help avoid it occurring.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958534139/0/baeldung">Understanding and Avoiding CommitFailedException in Kafka</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958534139/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-05-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-kafka-commitfailedexception#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-kafka-commitfailedexception/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="A robot with a vacuum hand that is pulling in various bits of data and outputing graphs and charts with its other hand. On the far left are the words &quot;Data on Baeldung&quot;." style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>Kafka provides a high degree of flexibility in committing offsets for processed messages in the consumer. It includes synchronous, asynchronous, and auto-commit options.</p>
<p>While this reduces the complexity of the commit process, unexpected errors can still occur.</p>
<p>In this tutorial, we’ll learn about a commit offset-related failure in a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-kafka">Kafka</a> consumer application. We&#8217;ll debug the root cause of the resulting <em>CommitFailedException</em> and implement a few solutions to avoid it where possible.</p>
<h2 id="bd-implementing-a-consumer-service-to-demonstrate-the-issue" data-id="implementing-a-consumer-service-to-demonstrate-the-issue">2. Implementing a Consumer Service to Demonstrate the Issue</h2>
<div class="bd-anchor" id="implementing-a-consumer-service-to-demonstrate-the-issue"></div>
<p>Let&#8217;s build a simple <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/kafka-create-listener-consumer-api">consumer</a> application that processes records sequentially.</p>
<p>We&#8217;ll implement the <em>consume</em> method in the <em>KafkaConsumerService </em>class:</p>
<pre><code class="language-java">public class KafkaConsumerService {
    private final KafkaConsumer&lt;String, String&gt; consumer;
    private final AtomicBoolean running = new AtomicBoolean(true);
    public void consume() {
        try {
            while (running.get()) {
                ConsumerRecords&lt;String, String&gt; records = consumer.poll(Duration.ofMillis(100));
                if (records.isEmpty()) {
                    continue;
                }
                records.forEach(this::simulateDBCall);
                consumer.commitSync();
            }
        } catch (WakeupException ex) {
            if (running.get()) {
                log.error("Error in the Kafka Consumer with exception", ex);
                throw ex;
            }
        } finally {
            consumer.close();
        }
    }
}</code></pre>
<p>In the code above, we process records sequentially and then commit the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/kafka-consumer-offset">offset</a>.</p>
<p>To simulate a database call, we&#8217;ll add a delay in the above <em>simulateDBCall </em>method:</p>
<pre><code class="language-java">private void simulateDBCall(ConsumerRecord&lt;String, String&gt; record) {
    try {
        log.info("Simulating a DB call - record key {} value {}", record.key(), record.value());
        Thread.sleep(150L);
    } catch (InterruptedException ex) {
        Thread.currentThread()
          .interrupt();
        throw new RuntimeException(ex);
    }
}</code></pre>
<p>The delay will cause the consumer to wait around 150 ms before the next record.</p>
<h2 id="bd-testing-the-service" data-id="testing-the-service">3. Testing the Service</h2>
<div class="bd-anchor" id="testing-the-service"></div>
<p>Next, let&#8217;s test the consumer service. Note that we use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/docker-test-containers">Testcontainers</a> in our test harness to provide a local Kafka broker for testing; full details are available in the supporting GitHub repository linked at the end of this article.</p>
<p>First, we&#8217;ll include the consumer-related configs in the test class:</p>
<pre><code class="language-java">private static Properties getConsumerConfig() {
    Properties consumerProperties = new Properties();
    consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_CONTAINER.getBootstrapServers());
    consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "consumer-app");
    consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
    consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
    consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
    consumerProperties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
    consumerProperties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1);
    consumerProperties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 50);
    
    return consumerProperties;
}</code></pre>
<p>In the config above, we&#8217;re polling for one record with a maximum poll interval of 50 ms and manually committing the offset.</p>
<p>We&#8217;ll implement a test case by producing, consuming a message, and verifying the offset:</p>
<pre><code class="language-java">@Test
void givenProducerMessageIsSent_whenConsumerIsRunning_thenConsumerOffsetIsCommitted() {
    KafkaConsumerService kafkaConsumerService = new KafkaConsumerService(getConsumerConfig(), "test-topic");
    Thread th = new Thread(kafkaConsumerService::consume);
    th.start();
    try (KafkaProducer&lt;String, String&gt; producer = new KafkaProducer&lt;&gt;(getProducerConfig())) {
        producer.send(new ProducerRecord&lt;&gt;("test-topic", "x1", "test"));
        producer.flush();
    }
    Awaitility.await()
      .atMost(30, TimeUnit.SECONDS)
      .pollInterval(2, TimeUnit.SECONDS)
      .untilAsserted(() -&gt; {
          TopicPartition topicPartition = new TopicPartition("test-topic", 0);
          Map&lt;TopicPartition, OffsetAndMetadata&gt; committedOffsets;
          try (AdminClient adminClient = AdminClient.create(getAdminProps())) {
              ListConsumerGroupOffsetsResult result = adminClient.listConsumerGroupOffsets("consumer-app");
              committedOffsets = result.partitionsToOffsetAndMetadata()
                .get();
          }
          assertNotNull(committedOffsets);
          assertNotNull(committedOffsets.get(topicPartition));
          assertEquals(1L, committedOffsets.get(topicPartition)
            .offset());
      });
    kafkaConsumerService.shutdown();
}</code></pre>
<p>Instead of offset committed, we&#8217;ll see the following error log with the test case also failing:</p>
<pre><code class="language-java">org.apache.kafka.clients.consumer.CommitFailedException: Offset commit cannot be completed since the consumer is not part of an active group for auto partition assignment; it is likely that the consumer was kicked out of the group.
	at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.sendOffsetCommitRequest(ConsumerCoordinator.java:1280)
	...
	at org.apache.kafka.clients.consumer.KafkaConsumer.commitSync(KafkaConsumer.java:918)
	at com.baeldung.kafka.commitfailure.sequential.KafkaConsumerService.consume(KafkaConsumerService.java:34)
</code></pre>
<p>From the log above, it&#8217;s obvious that the <em>CommitFailedException</em> was thrown while committing the offset in the <em>consume</em> method. Also, it says that the consumer is no longer part of the group. Kafka gives some hints in the class <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://kafka.apache.org/39/javadoc/org/apache/kafka/clients/consumer/CommitFailedException.html">documentation</a> on the possible reason.</p>
<h2 id="bd-root-cause-of-the-commitfailedexception" data-id="root-cause-of-the-commitfailedexception">4. Root Cause of the <em>CommitFailedException</em></h2>
<div class="bd-anchor" id="root-cause-of-the-commitfailedexception"></div>
<p>We can monitor and debug the root cause with multiple options, including container logs, CLI, and Kafka UI tools.</p>
<p>First, we&#8217;ll check the Kafka container logs for the error:</p>
<pre><code class="language-bash">[2026-06-08 16:53:05,349] INFO [GroupCoordinator 1]: Preparing to rebalance group consumer-app in state PreparingRebalance with old generation 1 (__consumer_offsets-0) (reason: Removing member consumer-consumer-app-1-3792090b-be55-4e19-bdbd-a3c66b968168 on LeaveGroup; client reason: consumer poll timeout has expired.)
...
[2026-06-08 16:53:05,354] INFO [GroupCoordinator 1]: Member MemberMetadata(memberId=consumer-consumer-app-1-3792090b-be55-4e19-bdbd-a3c66b968168, groupInstanceId=None, clientId=consumer-consumer-app-1, clientHost=/172.17.0.1, sessionTimeoutMs=45000, rebalanceTimeoutMs=50, supportedProtocols=List(range, cooperative-sticky)) has left group consumer-app through explicit `LeaveGroup`; client reason: consumer poll timeout has expired...</code></pre>
<p>We&#8217;ll also confirm the consumer group&#8217;s state using the <em>kafka-consumer-groups</em> command:</p>
<pre><code class="language-bash">sh-4.4$ /usr/bin/kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group consumer-app --state
Consumer group 'consumer-app' has no active members.
GROUP                     COORDINATOR (ID)          ASSIGNMENT-STRATEGY  STATE           #MEMBERS
consumer-app              8db993586dab:9092  (1)                         Empty           0</code></pre>
<p>Based on the above logs and status, we can see that <strong>the consumer was removed due to a client-specific error: <em>consumer poll timeout has expired</em>.</strong></p>
<p>Kafka has multiple ways to check if the consumer is healthy and running. This includes a background heartbeat check, which should run before the session timeout.</p>
<p>Additionally,<strong> Kafka expects the consumer to poll once within the specified <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://kafka.apache.org/39/configuration/consumer-configs/#consumerconfigs_max.poll.interval.ms">max poll interval time period</a>.</strong> If the consumer does not poll for any reason, <strong>it will be considered dead, and the partition will be reassigned to another consumer</strong>.</p>
<p>By further debugging and re-verifying the application logs, we observe that the record processing was clearly taking more time, and <strong>the consumer could not call the poll method within the specified <em>MAX_POLL_INTERVAL_MS_CONFIG</em> time period</strong>. Consequently, the consumer was removed by the Kafka group coordinator.</p>
<p>So, any further attempt to commit an offset failed with the error. It makes sense as the consumer is no longer the partition owner, and a commit can corrupt the offset state.</p>
<p>We&#8217;ll explore a few practical ways to prevent the error.</p>
<h2 id="bd-approaches-to-avoid-the-problem" data-id="approaches-to-avoid-the-problem">5. Approaches to Avoid the Problem</h2>
<div class="bd-anchor" id="approaches-to-avoid-the-problem"></div>
<p>With the consumer polling understanding, we&#8217;ll try to solve it by analyzing the consumer code and configuration.</p>
<p><strong>We may consider enabling auto-commit config</strong>, but that brings a possibility of data loss and irregular commit timing. Moreover, this approach does not ensure at-least-once processing.</p>
<p>Instead, we&#8217;ll explore some relevant manual commit solutions.</p>
<h3 id="bd-1-tuning-the-poll-duration-and-batch-size" data-id="1-tuning-the-poll-duration-and-batch-size">5.1. Tuning the Poll Duration and Batch Size</h3>
<div class="bd-anchor" id="1-tuning-the-poll-duration-and-batch-size"></div>
<p>By debugging the code, we found that the actual processing time was significantly longer than the specified maximum poll interval.</p>
<p>We&#8217;ll increase the maximum poll interval config:</p>
<pre><code class="language-java">consumerProperties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1);
consumerProperties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 300);</code></pre>
<p>We can also fine-tune the batch size according to our batch processing timing and throughput requirements.</p>
<p>Let&#8217;s test the updated consumer and confirm that no <em>CommitFailedException</em> is thrown.</p>
<p>We should note that it’s <strong>good to set the poll interval to about 3 times the batch processing time</strong> to ensure a safe buffer.</p>
<p>While changing the poll interval works, we may still face the issue in more complex setups, such as processing involving database transactions or network calls.</p>
<h3 id="bd-2-async-processing" data-id="2-async-processing">5.2. Async Processing</h3>
<div class="bd-anchor" id="2-async-processing"></div>
<p>If we&#8217;re processing records in the same thread that performs I/O operations, the above solution reduces throughput and increases latency.</p>
<p>In a production environment, <strong>we can&#8217;t guarantee a predictable database execution time, so configuring just the polling duration might be insufficient</strong>.</p>
<p>We can consider asynchronous processing of the records, so the consumer thread blocks for much shorter periods.</p>
<p>In Kafka, this can be done by processing the records asynchronously with virtual threads.</p>
<p>First, we&#8217;ll include a worker thread pool and a per-partition offset tracking map in the consumer service:</p>
<pre><code class="language-java">public class KafkaConsumerService {
    private final KafkaConsumer&lt;String, String&gt; consumer;
    private final AtomicBoolean running = new AtomicBoolean(true);
    private final ExecutorService workers;
    private final Map&lt;TopicPartition, AtomicLong&gt; committableOffsets = new ConcurrentHashMap&lt;&gt;();
    public KafkaConsumerService(Properties consumerProps, String topic) {
        this.consumer = new KafkaConsumer&lt;&gt;(consumerProps);
        consumer.subscribe(List.of(topic));
        workers = Executors.newVirtualThreadPerTaskExecutor();
    }
}</code></pre>
<p>Next, we&#8217;ll update the <em>consume</em> method with async processing using the worker threads:</p>
<pre><code class="language-java">public void consume() {
    try {
        while (running.get()) {
            ConsumerRecords&lt;String, String&gt; records = consumer.poll(Duration.ofMillis(100));
            if (records.isEmpty()) {
                continue;
            }
            List&lt;CompletableFuture&lt;Void&gt;&gt; futures = processAsync(records);
            try {
                CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new))
                  .orTimeout(700L, TimeUnit.MILLISECONDS)
                  .join();
            } catch (CompletionException ex) {
                log.error("Batch processing timed out or failed", ex);
            }
            commitOffsets();
        }
    } catch (WakeupException ex) {
        if (running.get()) {
            log.error("Error in the Kafka Consumer with exception", ex);
            throw ex;
        }
    } finally {
        commitOffsets();
        consumer.close();
    }
}</code></pre>
<p>We&#8217;ll implement the <em>processAsync</em> method to asynchronously process the records:</p>
<pre><code class="language-java">private List&lt;CompletableFuture&lt;Void&gt;&gt; processAsync(ConsumerRecords&lt;String, String&gt; records) {
    return StreamSupport.stream(records.spliterator(), false)
      .map(record -&gt; CompletableFuture.runAsync(() -&gt; simulateDBUpdate(record), workers)
        .whenComplete((ignored, ex) -&gt; {
            if (ex == null) {
                markComplete(record);
            } else {
                log.error("Failed offset and send to DLQ {} {} {}", 
                  record.offset(), record.key(), ex.getMessage());
            }
        })
        .exceptionally(ex -&gt; null))
      .toList();
}</code></pre>
<p>In the code above, the consumer waits for the workers to complete processing, then commits the processed offset. If any record fails during processing, we log the failure message and can send it to a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/kafka-spring-dead-letter-queue">Dead Letter Queue (DLQ)</a> topic.</p>
<p>Next, let&#8217;s implement the <em>markComplete</em> method to update the processed record offset into the<i> committableOffsets</i> map:</p>
<pre><code class="language-java">private void markComplete(ConsumerRecord&lt;String, String&gt; record) {
    TopicPartition tp = new TopicPartition(record.topic(), record.partition());
    committableOffsets.computeIfAbsent(tp, k -&gt; new AtomicLong(-1L))
      .accumulateAndGet(record.offset() + 1L, Math::max);
}</code></pre>
<p>Finally, we&#8217;ll implement the <em>commitOffsets</em> method to commit the processed offset:</p>
<pre><code class="language-java">private void commitOffsets() {
    Map&lt;TopicPartition, OffsetAndMetadata&gt; toCommit = new HashMap&lt;&gt;();
    committableOffsets.forEach((tp, atomicOffset) -&gt; {
        long val = atomicOffset.get();
        if (val != -1L) {
            toCommit.put(tp, new OffsetAndMetadata(val));
        }
    });
    if (toCommit.isEmpty()) {
        return;
    }
    consumer.commitSync(toCommit);
    toCommit.forEach((tp, meta) -&gt; {
        AtomicLong ref = committableOffsets.get(tp);
        if (ref != null) {
            ref.compareAndSet(meta.offset(), -1L);
        }
    });
}</code></pre>
<p>In the method above, we build the committed offset snapshot map for all the processed records and commit it. We also atomically mark the committed offset as completed.</p>
<p>Now, let&#8217;s configure the consumer with an increased batch size and max poll interval in the test class:</p>
<pre><code class="language-java">consumerProperties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 30);
consumerProperties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 1000);</code></pre>
<p>Finally, we&#8217;ll implement a test case by producing and consuming multiple messages and verifying the offset:</p>
<pre><code class="language-java">@Test
void givenProducerMessagesAreSent_whenConsumerIsRunning_thenConsumerOffsetIsCommitted() {
    KafkaConsumerService kafkaConsumerService = new KafkaConsumerService(getConsumerConfig(), "test-topic");
    Thread th = new Thread(kafkaConsumerService::consume);
    th.start();
    try (KafkaProducer&lt;String, String&gt; producer = new KafkaProducer&lt;&gt;(getProducerConfig())) {
        for (int num = 0; num &lt; 100; num++) {
            producer.send(new ProducerRecord&lt;&gt;("test-topic", "x1" + num, "test" + num));
        }
        producer.flush();
    }
    Awaitility.await()
      .atMost(30, TimeUnit.SECONDS)
      .pollInterval(2, TimeUnit.SECONDS)
      .untilAsserted(() -&gt; {
          TopicPartition topicPartition = new TopicPartition("test-topic", 0);
          Map&lt;TopicPartition, OffsetAndMetadata&gt; committedOffsets;
          try (AdminClient adminClient = AdminClient.create(getAdminProps())) {
              ListConsumerGroupOffsetsResult result = adminClient.listConsumerGroupOffsets("consumer-app");
              committedOffsets = result.partitionsToOffsetAndMetadata()
                .get();
          }
          assertNotNull(committedOffsets);
          assertNotNull(committedOffsets.get(topicPartition));
          assertEquals(100L, committedOffsets.get(topicPartition)
            .offset());
      });
    kafkaConsumerService.shutdown();
}</code></pre>
<p>By running the test above, we have successfully verified that the committed offset matches the total number of consumed messages.</p>
<h3 id="bd-3-implementing-a-consumerrebalancelistener" data-id="3-implementing-a-consumerrebalancelistener">5.3. Implementing a <em>ConsumerRebalanceListener</em></h3>
<div class="bd-anchor" id="3-implementing-a-consumerrebalancelistener"></div>
<p>We should note that <strong>it&#8217;s always good practice to implement <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-kafka-reset-consumer-offset#1-implement-the-consumer-rebalance-listener"><em>ConsumerRebalanceListener</em></a> in the consumer to commit any in-flight offset and clean any data</strong>.</p>
<p>Let&#8217;s implement the <em>ConsumerRebalanceListener</em> in the <em>KafkaConsumer</em> object in the <em>KafkaConsumerService</em> class:</p>
<pre><code class="language-java">consumer.subscribe(List.of(topic), new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection&lt;TopicPartition&gt; partitions) {
        try {
            commitOffsets(partitions);
        } catch (Exception ex) {
            log.error("Commit failed during rebalance", ex);
        } finally {
            partitions.forEach(committableOffsets::remove);
        }
    }
    @Override
    public void onPartitionsAssigned(Collection&lt;TopicPartition&gt; partitions) {
        partitions.forEach(committableOffsets::remove);
    }
});</code></pre>
<p>We&#8217;ll also need to implement the <em>commitOffsets</em> method to commit offsets for the specific partitions:</p>
<pre><code class="language-java">private void commitOffsets(Collection&lt;TopicPartition&gt; partitions) {
    Map&lt;TopicPartition, OffsetAndMetadata&gt; toCommit = new HashMap&lt;&gt;();
    partitions.forEach(tp -&gt; {
        AtomicLong ref = committableOffsets.get(tp);
        if (ref != null) {
            long val = ref.get();
            if (val != -1L) {
                toCommit.put(tp, new OffsetAndMetadata(val));
            }
        }
    });
    if (toCommit.isEmpty()) {
        return;
    }
    consumer.commitSync(toCommit);
}</code></pre>
<p>In the code above, we&#8217;re committing the offset by creating a snapshot map from the specific partition&#8217;s offset.</p>
<p>Now, let&#8217;s run the previous section&#8217;s test method to verify the offset for the updated consumer implementation:</p>
<pre><code class="language-bash">13:01:21.571 [virtual-158] INFO  c.b.k.c.f.async.KafkaConsumerService - Simulating a db call - record key x199 value test99
</code></pre>
<p>From the test above, we confirm that all messages were processed and committed.</p>
<h3 id="bd-4-best-practices" data-id="4-best-practices">5.4. Best Practices</h3>
<div class="bd-anchor" id="4-best-practices"></div>
<p>While we cannot prevent exceptions entirely, we can protect the system from any reprocessing duplication, data loss, or silent message drops.</p>
<p>We should implement idempotency on both the producer and the consumer, validate database constraints, and ensure side-effect-free reprocessing. Additionally, we should send the failed messages to a retry or DLQ topic.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we learned about one of the common offset commit-related exceptions in the Consumer application. We explored why the resulting <em>CommitFailedException</em> happens and how to prevent it. We also implemented a preventive solution by tuning the maximum poll interval and switching to asynchronous processing logic.</p>
<p>As always, the example code can be found <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/apache-kafka-4">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-kafka-commitfailedexception">Understanding and Avoiding CommitFailedException in Kafka</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958534139/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958534139/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-05-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958534139/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-kafka-commitfailedexception#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-kafka-commitfailedexception/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958534139/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-05-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/hibernate-query-regexp</feedburner:origLink>
		<title>Regular Expression Support in HQL</title>
		<link>https://feeds.feedblitz.com/~/958534142/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958534142/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Kai Yuan]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:48:47 +0000</pubDate>
				<category><![CDATA[JPA]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[HQL]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/hibernate-query-regexp</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use HQL to query a string column against a regular expression.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958534142/0/baeldung">Regular Expression Support in HQL</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958534142/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-query-regexp#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-query-regexp/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>Hibernate 7.2 introduces first-class regular expression support in HQL via the <em>like regexp</em> operator. Before this addition, matching a string column against a non-trivial pattern meant writing a native query. This new operator delegates regex matching to the database engine, avoiding native queries and post-filtering in Java.</p>
<p>In this tutorial, we&#8217;ll walk through a small <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-start">Spring Boot</a> example that demonstrates <em>like regexp</em>. We&#8217;ll look under the hood to understand how Hibernate translates it to SQL and what happens when the underlying database doesn&#8217;t support regex.</p>
<h2 id="bd-preparing-a-simple-spring-boot-example" data-id="preparing-a-simple-spring-boot-example">2. Preparing a Simple Spring Boot Example</h2>
<div class="bd-anchor" id="preparing-a-simple-spring-boot-example"></div>
<p>Before we can demonstrate the <em>like regexp</em> operator, we need a tiny project to run queries against. We&#8217;ll set up the dependencies, an entity, some seed data, the application properties, and a Spring Data repository. For simplicity, we&#8217;ll not show all the code here.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">2.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p>Our project uses Spring Boot 3.3 as its parent. Spring Boot 3.3&#8217;s BOM pins Hibernate to 6.5, so we override the version and a couple of its transitive dependencies that Hibernate 7 requires newer versions of:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.hibernate.orm&lt;/groupId&gt;
    &lt;artifactId&gt;hibernate-core&lt;/artifactId&gt;
    &lt;version&gt;7.4.2.Final&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.jboss.logging&lt;/groupId&gt;
    &lt;artifactId&gt;jboss-logging&lt;/artifactId&gt;
    &lt;version&gt;3.6.1.Final&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;jakarta.persistence&lt;/groupId&gt;
    &lt;artifactId&gt;jakarta.persistence-api&lt;/artifactId&gt;
    &lt;version&gt;3.2.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>Beyond that, we pull in the usual <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/the-persistence-layer-with-spring-data-jpa"><em>spring-boot-starter-data-jpa</em></a> and the H2 driver, as we&#8217;ll use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-h2-database">H2 in-memory database</a> to keep the demo self-contained.</p>
<h3 id="bd-2-the-entity" data-id="2-the-entity">2.2. The Entity</h3>
<div class="bd-anchor" id="2-the-entity"></div>
<p>We&#8217;ll keep things minimal with a single-column entity:</p>
<pre><code class="language-java">@Entity
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String content;
    // constructors, getters ...
}
</code></pre>
<h3 id="bd-3-seed-data" data-id="3-seed-data">2.3. Seed Data</h3>
<div class="bd-anchor" id="3-seed-data"></div>
<p>We load test data through Spring&#8217;s <em>data.sql</em> mechanism. The values are short phrases designed to exercise regex patterns:</p>
<pre><code class="language-sql">INSERT INTO message (content) VALUES ('quick brown fox jumps over');
INSERT INTO message (content) VALUES ('hello world from spring boot');
INSERT INTO message (content) VALUES ('Order 1234 shipped on Monday');
INSERT INTO message (content) VALUES ('Please call 555 today');
INSERT INTO message (content) VALUES ('ERROR connection refused remotely');
INSERT INTO message (content) VALUES ('warning low disk space detected');
</code></pre>
<h3 id="bd-4-application-properties" data-id="4-application-properties">2.4. Application Properties</h3>
<div class="bd-anchor" id="4-application-properties"></div>
<p>The test profile (<em>application-test.yaml</em>) wires up H2 and asks Spring to run <em>data.sql</em> after Hibernate creates the schema:</p>
<pre><code>spring:
  datasource:
    url: jdbc:h2:mem:testdb
    ...
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: create-drop
    defer-datasource-initialization: true
    show-sql: true
    ...
  sql:
    init:
      mode: always
</code></pre>
<p><strong>The <em>defer-datasource-initialization: true</em> setting ensures that <em>data.sql</em> runs after the schema is created. </strong>It&#8217;s also worth mentioning that we enabled <em>show-sql</em> option to log translated SQL. This helps us understand how the <em>like regexp</em> operator works. We&#8217;ll discuss it later.</p>
<h3 id="bd-5-the-repository" data-id="5-the-repository">2.5. The Repository</h3>
<div class="bd-anchor" id="5-the-repository"></div>
<p>The repository is the part we&#8217;ll actually be exercising. It declares a pair of HQL queries using the new <em>like regexp</em>/<em>not like regexp</em> operators:</p>
<pre><code class="language-java">public interface MessageRepository extends JpaRepository&lt;Message, Long&gt; {
    @Query("select m from Message m where m.content like regexp :pattern")
    List&lt;Message&gt; findByContentMatchingRegex(@Param("pattern") String pattern);
    @Query("select m from Message m where m.content not like regexp :pattern")
    List&lt;Message&gt; findByContentNotMatchingRegex(@Param("pattern") String pattern);
}
</code></pre>
<p>As always, we&#8217;ll use unit tests to demonstrate how each operator behaves.</p>
<h2 id="bd-demonstration" data-id="demonstration">3. Demonstration</h2>
<div class="bd-anchor" id="demonstration"></div>
<p>In our defined repository methods, the <em>pattern</em> parameter accepts a full regex pattern. To find every message that contains at least one digit, we use .*<em>\d+.*</em>:</p>
<pre><code class="language-java">@Test
void givenContainsDigitRegex_whenLikeRegexp_thenReturnsMatching() {
    List&lt;Message&gt; results = repository.findByContentMatchingRegex(".*\\d+.*");
    assertThat(results)
      .extracting(Message::getContent)
      .containsExactlyInAnyOrder(
        "Order 1234 shipped on Monday",
        "Please call 555 today");
}
</code></pre>
<p>Similarly, if we pass the same regex to the <em>findByContentNotMatchingRegex()</em> method, we&#8217;ll get all rows that don&#8217;t contain any digits.</p>
<pre><code class="language-java">@Test
void givenContainsDigitRegex_whenNotLikeRegexp_thenReturnsNonMatching() {
    List&lt;Message&gt; results = repository.findByContentNotMatchingRegex(".*\\d+.*");
    assertThat(results)
      .extracting(Message::getContent)
      .containsExactlyInAnyOrder(
        "quick brown fox jumps over",
        "hello world from spring boot",
        "ERROR connection refused remotely",
        "warning low disk space detected");
}
</code></pre>
<p>If we run these two tests, they will pass. The same pattern parameter drives both variants, so we don&#8217;t need a separate regex for each side of the predicate. Next, let&#8217;s understand how Hibernate&#8217;s like regexp operator works under the hood.</p>
<h2 id="bd-under-the-hood" data-id="under-the-hood">5. Under The Hood</h2>
<div class="bd-anchor" id="under-the-hood"></div>
<p>Having seen the operator in action, let&#8217;s look at how Hibernate actually implements it and where the portability boundaries lie.</p>
<h3 id="bd-1-how-like-regexp-is-translated" data-id="1-how-like-regexp-is-translated">5.1. How <em>like regexp</em> Is Translated</h3>
<div class="bd-anchor" id="1-how-like-regexp-is-translated"></div>
<p>When Hibernate parses <em>m.content like regexp :pattern</em>, it doesn&#8217;t translate to a <em>LIKE</em> statement at all. Instead, <strong>it routes the predicate through a Semantic Query Model (SQM) function that each dialect registers under the name <em>regexp_like</em></strong>. The default <em>H2Dialect</em> maps <em>regexp_like(x, y)</em> to H2&#8217;s <em>regexp_like(x, y)</em> SQL function, so if we check the log output of the example test above, we can see the translated SQL:</p>
<pre><code>select m1_0.id, m1_0.content
from message m1_0
where regexp_like(m1_0.content, ?)
</code></pre>
<p>In other words, <em>like regexp</em> is syntactic sugar at the HQL level, while the heavy lifting happens in the database. Other dialects bind the same SQM function to whatever native construct they support, such as <em>REGEXP_LIKE</em> in Oracle, the <em>~</em> operator in PostgreSQL, <em>REGEXP</em> in MySQL, and so on.</p>
<p>Some dialects, for example, SQL Server prior to 2025, have no native regular expression function at all. In that case, the dialect simply doesn&#8217;t register <em>regexp_like</em>, and<strong> any HQL using <em>like regexp</em> fails at SQL execution time because Hibernate emits a call to a function the database doesn&#8217;t know</strong>.</p>
<h3 id="bd-2-the-portability-caveat" data-id="2-the-portability-caveat">5.2. The Portability Caveat</h3>
<div class="bd-anchor" id="2-the-portability-caveat"></div>
<p>Because each dialect delegates to the database&#8217;s native regex engine, the <em>flavor</em> of regex we can use is whatever the underlying database supports. Let&#8217;s see a few examples:</p>
<ul>
<li>H2 uses <em>java.util.regex</em> (Perl-compatible, <em>Matcher.find()</em> semantics — no implicit anchoring).</li>
<li>PostgreSQL uses POSIX regular expressions, which differ in subtle ways. For example, it has no <em>\d</em> shorthand without the embedded <em>(?x)</em> flag.</li>
<li>Oracle uses POSIX with a few extensions and does not support <em>\d</em> either. We must write <em>[[:digit:]]</em> or <em>[0-9]</em> instead.</li>
<li>MySQL (8.x) uses ICU regex, again with its own quirks.</li>
</ul>
<p><strong>So while the HQL stays portable, the patterns we feed into <em>like regexp</em> generally don&#8217;t</strong>. For example, a regex tested against H2 may fail or behave differently when the application is pointed at Oracle. If our application targets multiple databases, we should stick to a conservative regex subset.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>Hibernate 7.2&#8217;s <em>like regexp</em> operator lets us express regex predicates directly in HQL and have them pushed down to the database without resorting to native queries. The HQL surface is portable across dialects, but the actual regex flavor depends on the underlying database engine.</p>
<p>As always, the full source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/blob/master/persistence-modules/hibernate7">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-query-regexp">Regular Expression Support in HQL</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958534142/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958534142/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958534142/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-query-regexp#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-query-regexp/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958534142/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-02-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-bean-background-init</feedburner:origLink>
		<title>Bean Background Initialization in Spring Framework</title>
		<link>https://feeds.feedblitz.com/~/958532825/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958532825/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Umara Mushtaq]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:42:09 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[Spring 7]]></category>
		<category><![CDATA[Spring Boot 4]]></category>
		<category><![CDATA[Spring Core Basics]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-bean-background-init</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to initialize Spring beans in the background using <em>@Bean(bootstrap = BACKGROUND)</em> to reduce startup latency while preserving dependency safety and lifecycle consistency.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958532825/0/baeldung">Bean Background Initialization in Spring Framework</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958532825/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-bean-background-init#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-bean-background-init/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-bean">Bean</a> background initialization in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-tutorial">Spring Framework</a> provides a way to initialize selected beans asynchronously during application context startup. <strong>Instead of blocking the startup process on every bean initialization, Spring initializes selected beans in the background and continues processing the remaining application context</strong>. This improves application startup time and reduces perceived latency in applications with expensive initialization logic.</p>
<p>In this tutorial, we&#8217;ll explore how Bean background initialization works using the Spring native <em>bootstrap</em> attribute. We&#8217;ll demonstrate how to configure it correctly, how Spring manages lifecycle consistency during asynchronous initialization, and how this approach improves startup performance in real-world applications that rely on heavy resources such as caches, external services, or computation-heavy components.</p>
<h2 id="bd-why-background-initialization-matters-in-spring" data-id="why-background-initialization-matters-in-spring">2. Why Background Initialization Matters in Spring</h2>
<div class="bd-anchor" id="why-background-initialization-matters-in-spring"></div>
<p>Spring initializes <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-bean-scopes">singleton beans</a> during the <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-application-context">ApplicationContext</a></em> refresh phase in a strictly synchronous manner. During startup, the container manages the complete bean lifecycle and processes each bean in sequence:</p>
<ol>
<li>bean instantiation</li>
<li>dependency injection</li>
<li>initialization callback execution</li>
<li>marking the bean as ready for use</li>
</ol>
<p><strong>This approach provides predictable and deterministic startup behavior by ensuring that each bean reaches a fully initialized state before the context refresh proceeds</strong>. However, it introduces performance constraints when beans execute expensive operations during initialization.</p>
<h3 id="bd-1-synchronous-bean-initialization-model" data-id="1-synchronous-bean-initialization-model">2.1. Synchronous Bean Initialization Model</h3>
<div class="bd-anchor" id="1-synchronous-bean-initialization-model"></div>
<p><strong>Expensive initialization increases the duration of the <em>ApplicationContext</em> refresh phase by forcing the container to complete the lifecycle of each bean before moving forward</strong>. It reduces throughput in the bean creation pipeline and increases overall startup latency. As a result, even non-critical initialization logic delays the application from becoming available.</p>
<h3 id="bd-2-single-threaded-initialization-bottleneck" data-id="2-single-threaded-initialization-bottleneck">2.2. Single-Threaded Initialization Bottleneck</h3>
<div class="bd-anchor" id="2-single-threaded-initialization-bottleneck"></div>
<p><strong>The default startup model executes all bean initialization on the same thread that drives the context refresh process</strong>. Consequently, Spring executes independent initialization tasks sequentially and prevents them from running concurrently. This limits startup throughput and prevents Spring from taking advantage of available parallelism during application bootstrap.</p>
<h2 id="bd-background-initialization-mechanism" data-id="background-initialization-mechanism">3. Background Initialization Mechanism</h2>
<div class="bd-anchor" id="background-initialization-mechanism"></div>
<p>Spring introduces background initialization using the <em>bootstrap</em> attribute to reduce startup blocking caused by expensive singleton bean creation.</p>
<p>During application context refresh, Spring applies a modified execution strategy to selected beans:</p>
<ul>
<li><strong>Async delegation</strong>: Spring delegates the initialization phase of selected beans to a container-managed executor (the <em>bootstrap executor</em>), enabling execution to occur on a separate thread without blocking the main refresh process.</li>
<li><strong>Dependency coordination:</strong> When another bean depends on a background-initialized bean through a non-lazy dependency, Spring suspends dependency resolution for that bean and waits for initialization to complete before injecting the bean.</li>
<li><strong>Lifecycle preservation</strong>: Spring preserves the standard lifecycle order.</li>
</ul>
<p>This design ensures safe background execution while decoupling heavy initialization from the main startup thread, enabling earlier application readiness without affecting bean consistency.</p>
<h2 id="bd-example-setup" data-id="example-setup">4. Example Setup</h2>
<div class="bd-anchor" id="example-setup"></div>
<p>This section introduces a product preload class that simulates an expensive startup workload in Spring applications. This can be cache warming, dataset loading, or external resource preparation, which typically increases application startup time.</p>
<p>Let&#8217;s define the class to demonstrate this behavior:</p>
<pre><code class="language-java">public class ProductCatalogInitializer {
    public ProductCatalogInitializer() {
        loadProducts();
    }
    private void loadProducts() {
        System.out.println("Starting product preload");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Product preload completed");
    }
    public String getStatus() {
        return "Catalog ready";
    }
}</code></pre>
<p>The <em>loadProducts()</em> method represents a long-running initialization task executed during bean creation. Spring runs this logic on the main startup thread, which blocks the <em>ApplicationContext</em> refresh until completion.</p>
<p>This component acts as the baseline for demonstrating how Spring shifts such initialization work to background execution using the bootstrap mechanism in the next section.</p>
<h2 id="bd-background-initialization-using-bootstrap-mode" data-id="background-initialization-using-bootstrap-mode">5. Background Initialization Using Bootstrap Mode</h2>
<div class="bd-anchor" id="background-initialization-using-bootstrap-mode"></div>
<p>Spring applies background initialization at the bean definition level using the <em>bootstrap</em> attribute. Selected beans then get initialized concurrently without blocking the main startup thread.</p>
<h3 id="bd-1-enabling-background-initialization" data-id="1-enabling-background-initialization">5.1. Enabling Background Initialization</h3>
<div class="bd-anchor" id="1-enabling-background-initialization"></div>
<p>At this point, let&#8217;s create a configuration that marks <em>ProductCatalogInitializer</em> as a Spring-managed bean and enables background initialization using <em>BACKGROUND</em> bootstrap mode:</p>
<pre><code class="language-java">@Configuration
public class AppConfig {
    @Bean(bootstrap = Bean.Bootstrap.BACKGROUND)
    public ProductCatalogInitializer productCatalogInitializer() {
        return new ProductCatalogInitializer();
    }
}</code></pre>
<p>Spring detects the <em>BACKGROUND</em> bootstrap mode and offloads bean initialization to a container-managed <em>bootstrapExecutor</em>, allowing it to run on a separate thread instead of blocking the main application context refresh. Hence, this mechanism relies on a configured bootstrap executor. Without it, Spring can&#8217;t execute background initialization, and the affected beans fall back to normal synchronous initialization during startup.</p>
<h3 id="bd-2-bean-injection" data-id="2-bean-injection">5.2. Bean Injection</h3>
<div class="bd-anchor" id="2-bean-injection"></div>
<p>A background-initialized bean can be injected into other Spring-managed components in the same way as any standard singleton bean, since background initialization doesn&#8217;t change the core dependency injection model.</p>
<p>In most cases, direct injection remains sufficient because Spring coordinates the lifecycle and ensures that the bean becomes fully available within the application context startup phase.<strong> However, in scenarios where access may occur before the initialization process has completed, Spring provides <em>ObjectProvider</em> as an optional mechanism for deferred retrieval of the bean at runtime.</strong>
<br>
To that end, let&#8217;s create a service that demonstrates the injection of a background-initialized bean:</p>
<pre><code class="language-java">@Service
public class ProductService {
    private final ObjectProvider&lt;ProductCatalogInitializer&gt;
            initializerProvider;
    public ProductService(
            ObjectProvider&lt;ProductCatalogInitializer&gt; initializerProvider) {
                this.initializerProvider = initializerProvider;
    }
    public void printStatus() {
        ProductCatalogInitializer initializer = initializerProvider.getObject();
        System.out.println("Bean status: " + initializer.getStatus());
    }
}</code></pre>
<p>Now, the dependency isn&#8217;t resolved during bean creation. Instead, Spring retains the reference and defers resolution until <em>getObject()</em> is invoked. At that point, the actual bean instance is retrieved, so access occurs only after background initialization has completed, while the injection phase remains independent of startup timing.</p>
<h3 id="bd-3-application-startup-behaviorwith-background-initialization" data-id="3-application-startup-behaviorwith-background-initialization">5.3. Application Startup Behavior with Background Initialization</h3>
<div class="bd-anchor" id="3-application-startup-behaviorwith-background-initialization"></div>
<p>With <em>BACKGROUND</em> bootstrap mode enabled, Spring offloads selected bean initialization to a background thread, allowing the application context refresh to proceed without waiting for these beans. As a result, startup becomes non-blocking for the selected initialization workload, while execution continues in parallel.</p>
<p>Despite this, non-lazy background-initialized beans remain part of the Spring container lifecycle, and their completion is coordinated within the overall startup process.</p>
<h3 id="bd-4-performance-impact" data-id="4-performance-impact">5.4. Performance Impact</h3>
<div class="bd-anchor" id="4-performance-impact"></div>
<p>This behavior directly reduces startup latency. Spring moves heavy bean initialization off the main refresh thread. As a result, the application becomes operational earlier while background tasks continue executing in parallel. Beans that load large caches, establish connection pools, or run schema validation benefit the most.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored background initialization in the Spring Framework using the <em>BACKGROUND</em> bootstrap mode, which enables selected beans to initialize asynchronously during application context refresh.</p>
<p><strong>The mechanism decouples expensive bean initialization from the main startup thread while preserving the Spring lifecycle semantics.</strong> Rather than launching uncontrolled threads, Spring keeps full awareness of bean dependencies. If a non-lazy bean depends on one that is still initializing in the background, Spring ensures it waits, preventing access to incomplete or half-built objects. In addition, using <em>ObjectProvider</em> helps delay bean retrieval until it is actually needed, adding an extra layer of safety for early-access scenarios.</p>
<p><strong>This approach improves application startup responsiveness in systems with heavy initialization workloads by enabling parallel execution during context refresh.</strong> It maintains dependency safety and lifecycle consistency while reducing blocking on the main thread. Background initialization is best suited for non-critical beans such as cache warmers, external integrations, and data preloading components.</p>
<p>The code backing this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-core-5">over on Github.</a></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-bean-background-init">Bean Background Initialization in Spring Framework</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958532825/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958532825/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958532825/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-bean-background-init#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-bean-background-init/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958532825/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-jcasbin-authorization</feedburner:origLink>
		<title>Authorization using jCasbin</title>
		<link>https://feeds.feedblitz.com/~/958532828/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958532828/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Graham Cox]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:36:54 +0000</pubDate>
				<category><![CDATA[Security]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-jcasbin-authorization</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use jCasbin to define an access control model for Java applications, making use of standard approaches such as ACLs and RBAC.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958532828/0/baeldung">Authorization using jCasbin</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958532828/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSecurity-Featured-Image-06-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-jcasbin-authorization#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-jcasbin-authorization/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction"><strong>1. Introduction</strong></h2>
<div class="bd-anchor" id="introduction"></div>
<p>In this tutorial, we’ll take a look at <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/casbin-jcasbin">jCasbin</a> &#8211; the Java official port of the popular Apache <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://casbin.apache.org/">Casbin</a> authorization library. We’ll see what it is, how to use it and what we can do with it.</p>
<h2 id="bd-what-is-jcasbin" data-id="what-is-jcasbin"><strong>2. What is jCasbin</strong></h2>
<div class="bd-anchor" id="what-is-jcasbin"></div>
<p><strong>jCasbin is the Java version of the powerful Casbin access control library. This allows us to easily answer questions about access to resources within our applications.</strong></p>
<p>jCasbin, and the entire suite of Casbin libraries, work using a configuration model that allows us to describe the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-access-control-models">access control model</a> we want to use &#8211; such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://en.wikipedia.org/wiki/Access-control_list">ACLs</a> or <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/role-vs-permission-based-access-control">RBAC</a> &#8211; and a separate set of policy data applied to that configuration model.</p>
<p>Typically, this works by defining:</p>
<ul>
<li>Objects that we want to control access to.</li>
<li>Subjects who want to access these objects.</li>
<li>Actions that the objects want to perform.</li>
</ul>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/06/jcasbin-access-control.png"><img loading="lazy" decoding="async" class="alignnone wp-image-252658" src="https://www.baeldung.com/wp-content/uploads/2026/06/jcasbin-access-control-1024x382.png" alt="" width="536" height="200" /></a>
<p>However, jCasbin is flexible enough that we can use any structure we want, as long as we can model it correctly in our configuration and policy data.</p>
<h2 id="bd-dependencies" data-id="dependencies"><strong>3. Dependencies</strong></h2>
<div class="bd-anchor" id="dependencies"></div>
<p><strong>Before using jCasbin, we need to include the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.casbin/jcasbin">latest version</a> in our build, which is <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.casbin/jcasbin/1.99.0">1.99.0</a> at the time of writing.</strong></p>
<p>If we’re using Maven, we can include this dependency in our pom.xml file:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.casbin&lt;/groupId&gt;
    &lt;artifactId&gt;jcasbin&lt;/artifactId&gt;
    &lt;version&gt;1.99.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>At this point, we’re ready to start using it in our application.</p>
<h2 id="bd-creating-an-enforcer" data-id="creating-an-enforcer"><strong>4. Creating an Enforcer</strong></h2>
<div class="bd-anchor" id="creating-an-enforcer"></div>
<p>Once we have jCasbin in our application, we&#8217;re ready to use it. <strong>The central class for this is the <em>Enforcer</em>. Constructing this requires two data sources: our configuration model and our policy data.</strong> The easiest way to manage this is through passing in filenames:</p>
<pre><code class="language-java">Enforcer enforcer = new Enforcer("path/to/model.conf", "path/to/policy.csv");</code></pre>
<p>However, having our policy data be a local file can be difficult. <strong>As such, jCasbin allows the use of <em>Adapter</em>s here instead</strong>. These provide a means to load and manipulate the policy data from any data source we like. We also have a set of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://casbin.apache.org/docs/adapters/">standard adapters</a> that can be used for our application. These include familiar technologies such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/casbin-jcasbin-jdbc-adapter">JDBC</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jcasbin/hibernate-adapter">Hibernate</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jcasbin/jcasbin-mongo-adapter">MongoDB</a> and many others:</p>
<pre><code class="language-java">Adapter jdbcAdapter = new JDBCAdapter(dataSource);
Enforcer enforcer = new Enforcer("path/to/model.conf", jdbcAdapter);</code></pre>
<p>Exactly how these adapters work depends on the adapter in question and is out of scope of this article.</p>
<p>When specifying policy data as a filename, this internally uses the <em>FileAdapter </em>instead. We can also use this to load files available from other sources by providing an<em> InputStream</em>:</p>
<pre><code class="language-">FileAdapter fileAdapter = new FileAdapter(getClass().getResourceAsStream("/com/baeldung/jcasbin/policy.csv"));
Enforcer enforcer = new Enforcer("path/to/model.conf", fileAdapter);
</code></pre>
<p><strong>In exactly the same way, we can provide our model data using a <em>Model </em>instance instead.</strong> This allows us to load our model data from alternative sources:</p>
<pre><code class="language-java">String content = new String(getClass().getClassLoader().getResourceAsStream("com/baeldung/jcasbin/model.conf").readAllBytes());
Model model = new Model();
model.loadModelFromText(content);
Enforcer enforcer = new Enforcer(model, "path/to/policy.csv");
</code></pre>
<p>Once we&#8217;ve created our <em>Enforcer</em>, we&#8217;re ready to start using it to check permissions.</p>
<h2 id="bd-enforcing-permissions" data-id="enforcing-permissions"><strong>5. Enforcing Permissions</strong></h2>
<div class="bd-anchor" id="enforcing-permissions"></div>
<p><strong>Now that we&#8217;ve got our <em>Enforcer</em>, we can start checking permissions. We do this using the <em>enforce()</em> method.</strong> This typically involves passing in our subject, object, and action to check:</p>
<pre><code class="language-java">if (enforcer.enforce("alice", "data1", "read")) {
    // permit alice to read data1
} else {
    // deny the request
}</code></pre>
<p>Note that the <em>enforce()</em> method actually takes an arbitrary set of objects, depending on how our configuration model is defined.</p>
<p>The exact way permission checking works then depends on both the structure of our configuration model and our policy data.</p>
<h3 id="bd-1-access-control-lists" data-id="1-access-control-lists"><strong>5.1. Access Control Lists</strong></h3>
<div class="bd-anchor" id="1-access-control-lists"></div>
<p><strong>One of the easiest ways to configure jCasbin is through Access Control Lists (ACLs).</strong> A typical configuration model might look like this:</p>
<pre><code class="language-plaintext"># Request definition
[request_definition]
r = sub, obj, act
# Policy definition
[policy_definition]
p = sub, obj, act
# Matchers
[matchers]
m = r.sub == p.sub &amp;&amp; r.obj == p.obj &amp;&amp; r.act == p.act
# Policy effect
[policy_effect]
e = some(where (p.eft == allow))</code></pre>
<p>Here, the <em>request_definition </em>section tells us what the shape of the data passed into <em>enforcer.enforce()</em> should be. It gives us names for the parameters we pass in &#8211; here, &#8220;sub&#8221;, &#8220;obj&#8221; and &#8220;act&#8221; are used as shorthand for &#8220;subject&#8221;, &#8220;object&#8221; and &#8220;action&#8221;.</p>
<p>The <em>policy_definition </em>section then uses the exact same names to map onto the policy data. If not specified, there&#8217;s also an implicit value called &#8220;eft&#8221; &#8211; short for &#8220;effect&#8221; &#8211; which defaults to &#8220;allow&#8221;.</p>
<p>The <em>matchers</em> section then explains how we can match policy rows to requests. In this case, we&#8217;re simply saying that every field must have exactly the same value.</p>
<p>Finally, the <em>policy_effect</em> section tells us how to determine the overall effect of our policy. The action is only allowed if this returns a positive match.</p>
<p><strong>In this mode, our policy data is a straight list of subjects, objects and allowed actions &#8211; as defined by our <em>policy_definition:</em></strong></p>
<pre><code class="language-plaintext">p, alice, data1, read
p, bob, data2, write</code></pre>
<p>This indicates that the subject &#8220;alice&#8221; can perform the action &#8220;read&#8221; on the object &#8220;data1&#8221;, whereas the subject &#8220;bob&#8221; can perform the action &#8220;write&#8221; on the object &#8220;data2&#8221;.</p>
<pre><code class="language-java">assertTrue(enforcer.enforce("alice", "data1", "read"));
assertTrue(enforcer.enforce("bob", "data2", "write"));</code></pre>
<p>Any other checks are rejected because they don&#8217;t match our configuration.</p>
<h3 id="bd-2-super-users" data-id="2-super-users"><strong>5.2. Super Users</strong></h3>
<div class="bd-anchor" id="2-super-users"></div>
<p><strong>Sometimes we might want super users &#8211; users who can do anything regardless of what the policy data specifies.</strong></p>
<p>We express this in our configuration model by adjusting how the <em>matchers </em>section is defined:</p>
<pre><code class="language-plaintext">[matchers]
m = r.sub == p.sub &amp;&amp; r.obj == p.obj &amp;&amp; r.act == p.act || r.sub == "root"
</code></pre>
<p>Here, in addition to the same matching as before, we also match when the subject has the exact value &#8220;root&#8221;, regardless of what the rest of the policy expression says. <strong>Using this, a subject of &#8220;root&#8221; will always pass every check regardless:</strong></p>
<pre><code class="language-java">assertTrue(enforcer.enforce("root", "data2", "write"));</code></pre>
<p>This introduces some risks into our permission checks, so we need to use it carefully.</p>
<h3 id="bd-3-role-based-access-control" data-id="3-role-based-access-control"><strong>5.3. Role-Based Access Control</strong></h3>
<div class="bd-anchor" id="3-role-based-access-control"></div>
<p><strong>Role-Based Access Control (RBAC) introduces an additional layer of indirection into our configuration model. In this case, we assign roles to users, and can define permissions based on roles.</strong></p>
<p>We start by adding a <em>role_definition </em>section:</p>
<pre><code class="language-plaintext">[role_definition]
g = _, _</code></pre>
<p>This defines the <em>g()</em> function that we can use to define role membership.</p>
<p>We also need to update our <em>matchers</em> section to make use of this:</p>
<pre><code class="language-plaintext">[matchers]
m = g(r.sub, p.sub) &amp;&amp; r.obj == p.obj &amp;&amp; r.act == p.act</code></pre>
<p><strong>Instead of directly comparing the policy subjects with the request, this says they must match via the role definitions.</strong></p>
<p>We can now make use of this within our policy data:</p>
<pre><code class="language-plaintext">p, alice, data1, read
p, data2_admin, data2, read
p, data2_admin, data2, write
g, bob, data2_admin</code></pre>
<p>All of our rows that start with <em>p</em> define how permissions are assigned, either directly to users or else to roles. Here we&#8217;re assigning one permission directly to the subject &#8220;alice&#8221;, and two permissions to the role &#8220;data2_admin&#8221;.</p>
<p>The rows starting with <em>g </em>&#8211; from our <em>role_definition </em>section above &#8211; define how role membership works. Here, we&#8217;re defining that the subject &#8220;bob&#8221; is assigned the role &#8220;data2_admin&#8221;<em>. </em>This then means that this subject implicitly has all of the permissions assigned to this role as well:</p>
<pre><code class="language-java">assertTrue(enforcer.enforce("bob", "data2", "write"));</code></pre>
<p>We can also nest roles. That is, we can assign one role to another, generating an entire hierarchy of permissions:</p>
<pre><code class="language-plaintext">g, superuser, data2_admin
g, carol, superuser
</code></pre>
<p>Here, the role &#8220;data2_admin&#8221; has been assigned to the role &#8220;superuser&#8221;, and the role &#8220;superuser&#8221; has been assigned to the subject &#8220;carol&#8221;. This means that this subject has all the permissions all the way up:</p>
<pre><code class="language-java">assertTrue(enforcer.enforce("carol", "data2", "read"));</code></pre>
<p>We never gave &#8220;carol&#8221; permission on &#8220;data2&#8221; &#8211; either directly or indirectly. Instead, she inherited it through her role &#8220;superuser&#8221;.</p>
<h2 id="bd-management-api" data-id="management-api"><strong>6. Management API</strong></h2>
<div class="bd-anchor" id="management-api"></div>
<p><strong>In addition to checking permissions, we also have management APIs for working with our setup.</strong></p>
<p>We do this from the same <em>Enforcer </em>instance that we&#8217;ve already been using.</p>
<h3 id="bd-1-querying-subjects-objects-and-actions" data-id="1-querying-subjects-objects-and-actions"><strong>6.1. Querying Subjects, Objects and Actions</strong></h3>
<div class="bd-anchor" id="1-querying-subjects-objects-and-actions"></div>
<p><strong>The easiest thing we can do is query all the subjects and objects in our policy data.</strong> We do this using the <em>getAllSubjects()</em>, <em>getAllObjects()</em> and <em>getAllActions()</em> methods:</p>
<pre><code class="language-java">List&lt;String&gt; subjects = enforcer.getAllSubjects();
List&lt;String&gt; objects = enforcer.getAllObjects();
List&lt;String&gt; actions = enforcer.getAllActions();</code></pre>
<p>We can also query the actions that any given subject can perform on any given object:</p>
<pre><code class="language-java">Set&lt;String&gt; subjects = enforcer.getPermittedActions("alice", "data1");</code></pre>
<p>This works correctly for both ACL and RBAC setups, as well as for hierarchical roles.</p>
<h3 id="bd-2-querying-rbac-roles" data-id="2-querying-rbac-roles"><strong>6.2. Querying RBAC Roles</strong></h3>
<div class="bd-anchor" id="2-querying-rbac-roles"></div>
<p><strong>With an RBAC setup, we can also query exactly which roles are assigned to which subjects:</strong></p>
<pre><code class="language-java">List&lt;String&gt; roles = enforcer.getRolesForUser("carol");
// superuser</code></pre>
<p>This returns only the roles directly assigned, not those included in the configured hierarchy.</p>
<p>However, we can also query which roles are assigned to other roles in the same way:</p>
<pre><code class="language-java">List&lt;String&gt; roles = enforcer.getRolesForUser("superuser");
// data2_admin</code></pre>
<p>This means we can traverse the hierarchy if needed.</p>
<h3 id="bd-3-managing-permissions-and-roles" data-id="3-managing-permissions-and-roles"><strong>6.3. Managing Permissions and Roles</strong></h3>
<div class="bd-anchor" id="3-managing-permissions-and-roles"></div>
<p><strong>We also have a set of methods we can use to directly manage the permissions and roles that subjects have.</strong></p>
<p>We can directly add and remove permissions between a subject and an object:</p>
<pre><code class="language-java">enforcer.addPermissionForUser("alice", "data2", "read");
enforcer.deletePermissionForUser("alice", "data2", "read");</code></pre>
<p>These apply directly once called, and permission checks take effect straight away.</p>
<p><strong>When we&#8217;re working in RBAC mode, we can additionally add and remove roles from subjects:</strong></p>
<pre><code class="language-java">enforcer.addRoleForUser("alice", "superuser");
enforcer.deleteRoleForUser("alice", "superuser");</code></pre>
<p>We apply all of these changes in memory. We need to use the <em>savePolicy()</em> method to write them back to our backing store:</p>
<pre><code class="language-java">enforcer.savePolicy();</code></pre>
<p>However, we can only do this if we&#8217;re using certain adapters. It also depends on how the data was loaded. For example, we can&#8217;t write back if we loaded from an <em>InputStream</em>.</p>
<h2 id="bd-summary" data-id="summary"><strong>7. Summary</strong></h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we took a very quick look at jCasbin. There’s a lot more that we can do with this. Next time you need to manage access controls for your applications, why not give it a try?</p>
<p>As always, all the code from this article is available over on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/libraries-7">GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jcasbin-authorization">Authorization using jCasbin</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958532828/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958532828/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSecurity-Featured-Image-06-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958532828/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-jcasbin-authorization#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-jcasbin-authorization/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958532828/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Security-Featured-Image-06-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/hibernate-embedded-table</feedburner:origLink>
		<title>Guide to @EmbeddedTable in Hibernate</title>
		<link>https://feeds.feedblitz.com/~/958532831/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958532831/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Abderrahim Azhrioun]]></dc:creator>
		<pubDate>Sat, 27 Jun 2026 18:33:15 +0000</pubDate>
				<category><![CDATA[JPA]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[JPA Annotations]]></category>
		<category><![CDATA[JPA Entities]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/hibernate-embedded-table</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use the new <em>@EmbeddedTable</em> annotation of Hibernate to map embeddable objects to secondary tables more cleanly.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958532831/0/baeldung">Guide to @EmbeddedTable in Hibernate</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958532831/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2018%2f10%2fSocial-Javaee-5-k-featured-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-embedded-table#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-embedded-table/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>The <em>@EmbeddedTable</em> annotation is a new Hibernate feature that simplifies the logic of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-mapping-single-entity-to-multiple-tables">mapping an embedded object to a secondary table</a>.</p>
<p>In this short tutorial, we&#8217;ll shed light on the annotation and delve into its usage. First, we&#8217;ll start with some insight into the motivation behind <em>@EmbeddedTable</em>. Then, we’ll demonstrate how to use it in practice.</p>
<h2 id="bd-the-problem-with-traditional-mapping" data-id="the-problem-with-traditional-mapping">2. The Problem With Traditional Mapping</h2>
<div class="bd-anchor" id="the-problem-with-traditional-mapping"></div>
<p>Before this new annotation, JPA provided multiple annotations to map a single entity into multiple tables. However, this traditional approach requires <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-mapping-single-entity-to-multiple-tables#single-entity">@SecondaryTable</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-embedded-embeddable#embedded">@Embedded</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-embedded-embeddable#embeddable">@Embeddable</a></em>, and multiple <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-attributeoverride"><em>@AttributeOverride</em></a> annotations to override each column individually. <strong>This can quickly become verbose as the number of columns grows</strong>.</p>
<p>Let&#8217;s say we have a single <em>Person</em> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jpa-entities">entity</a> that stores personal and address information. Here, <strong>we assume that we want to store the address details in a separate secondary table</strong>.</p>
<p>First, let&#8217;s create the <em>Address</em> object we want to embed inside the <em>Person</em> entity:</p>
<pre><code class="language-java">@Embeddable
public class Address {
	
    private String street;
    private String city;
    private String code;
    // setters
    
}</code></pre>
<p>Now, we create the <em>Person</em> entity:</p>
<pre><code class="language-java">@Entity
@SecondaryTable(name = "person_address")
public class Person {
    
    @Id
    private int id;
    
    @Column(name= "first_name")
    private String firstName;
    
    @Column(name = "last_name")
    private String lastName;
    
    @Embedded 
    private Address address;
    // setters
}
</code></pre>
<p>That&#8217;s not all, <strong>as mapping the <em>Address</em> fields to a secondary table requires the <em>@AttributeOverride</em> annotation for each field</strong>:</p>
<pre><code class="language-java">@Embedded 
@AttributeOverride(name = "street", column = @Column(table = "person_address")) 
@AttributeOverride(name = "city", column = @Column(table = "person_address"))
@AttributeOverride(name = "code", column = @Column(table = "person_address"))
private Address address;</code></pre>
<p>This approach quickly becomes cumbersome and error-prone since missing a single override can silently map a column to the wrong table. So, this is where <em>@EmbeddedTable</em> comes into play.</p>
<h2 id="bd-using-embeddedtable" data-id="using-embeddedtable">3. Using <em>@EmbeddedTable</em></h2>
<div class="bd-anchor" id="using-embeddedtable"></div>
<p>Starting with Hibernate 7.2, we can implement the same mapping we did in the previous section far more concisely with <em>@EmbeddedTable</em>:</p>
<pre><code class="language-java">@EmbeddedTable(value = "person_address")
private Address address;</code></pre>
<p>In a nutshell, <strong>Hibernate automatically maps every field of the embedded object</strong> <em>Address</em> to the specified secondary table <em>person_address</em> without overriding each one individually.</p>
<p><strong>This is particularly useful when dealing with large embeddable objects containing several fields</strong>.</p>
<p>So, let&#8217;s add a test case to confirm that everything works:</p>
<pre><code class="language-java">@Test
void whenUsingEmbeddedTableThenMapIntoTwoSeparateTables() {
    Address address = new Address();
    address.setStreet("12 Av. Tamassint center");
    address.setCity("Tamassint");
    address.setCode("10000");
    Person person = new Person();
    person.setId(1);
    person.setFirstName("Azhrioun");
    person.setLastName("Abderrahim");
    person.setAddress(address);
    session.persist(person);
    session.flush();
    Object[] addressRow = (Object[]) session.createNativeQuery(
      "select street, city from person_address",
      Object[].class
    ).uniqueResult();
    assertEquals("12 Av. Tamassint center", addressRow[0]);
    assertEquals("Tamassint", addressRow[1]);
    Object[] personRow = (Object[]) session.createNativeQuery(
      "select first_name, last_name from person",
      Object[].class
    ).uniqueResult();
    assertEquals("Azhrioun", personRow[0]);
    assertEquals("Abderrahim", personRow[1]);
}</code></pre>
<p>As expected, the test case runs successfully.</p>
<h2 id="bd-the-limitations" data-id="the-limitations">4. The Limitations</h2>
<div class="bd-anchor" id="the-limitations"></div>
<p>Although convenient, there are a couple of points worth keeping in mind when using <em>@EmbeddedTable</em>.</p>
<p>First, it&#8217;s a Hibernate-specific annotation and not part of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jakarta-persistence-3-2">Jakarta Persistence specification</a>. <strong>If portability across JPA providers matters, the <em>@AttributeOverride</em> annotation is always the safer choice</strong>.</p>
<p>Additionally, <em>@EmbeddedTable</em> is marked <em>@Incubating</em>, which means the API is still evolving and not yet considered stable and mature.</p>
<p>Typically, <em>@EmbeddedTable</em> is intended for top-level embedded objects. So, using it on nested objects isn&#8217;t supported yet.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this short article, we explored the new Hibernate <em>@EmbeddedTable</em> annotation.</p>
<p>Along the way, we learned that it provides a cleaner alternative to multiple <em>@AttributeOverride</em> annotations when mapping embedded objects to secondary tables.</p>
<p>The full source is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/azhwani/tutorials/tree/master/persistence-modules/hibernate-annotations">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-embedded-table">Guide to @EmbeddedTable in Hibernate</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958532831/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958532831/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2018%2f10%2fSocial-Javaee-5-k-featured-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958532831/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/hibernate-embedded-table#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/hibernate-embedded-table/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958532831/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2018/10/Social-Javaee-5-k-featured-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-652</feedburner:origLink>
		<title>Java Weekly, Issue 652</title>
		<link>https://feeds.feedblitz.com/~/958459364/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958459364/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 12:57:04 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204137</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>The agent harness seems to be the one thing in 2026. For really good reason.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958459364/0/baeldung">Java Weekly, Issue 652</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958459364/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-652#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-652/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://lucumr.pocoo.org/2026/6/23/the-coming-loop/">&gt;&gt; The Coming Loop</a></strong> [<span style="color: #993300;">lucumr.pocoo.org</span>]</p>
<p>A frame for the next phase of AI-assisted development &#8211; an agent harness, which generates, runs, inspects, corrects, and repeat quickly enough that the system becomes genuinely useful instead of just impressive. Anecdotally I&#8217;ve been running one for a few months now <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> A good read.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/23/spring-ai-self-correcting-structured-output" target="_blank" rel="noopener"><strong>Self-Correcting Structured Output in Spring AI 2.0</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/06/21/better-tools-immutable-data/" target="_blank" rel="noopener"><strong>Better Tools for Immutable Data</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/06/block-450-jvm-monorepo-migration/" target="_blank" rel="noopener"><strong>Behind the Scenes: Block 450 JVM Repositories into Monorepo to Reduce Dependency Drift</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/jurassic-jdk-migrate-or-extinct/" target="_blank" rel="noopener"><strong>Jurassic JDK: Migrate or Extinct</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/programming-languages-targets-platforms/" target="_blank" rel="noopener"><strong>On programming languages, targets, and platforms</strong></a> [<span style="color: #800000;">frankel.ch</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/amper/2026/06/kotlin-toolchain-0-11/" target="_blank" rel="noopener"><strong>Kotlin Toolchain 0.11: The Next Step for Amper</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/pi4j-drivers-simplifying-sensor-and-hardware-integration-in-java/" target="_blank" rel="noopener"><strong>Pi4J Drivers: Simplifying Sensor and Hardware Integration in Java</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/systematic-ai-coding-my-takeaways-from-the-eclipse-foundation-workshop-in-brussels/" target="_blank" rel="noopener"><strong>Systematic AI Coding: My Takeaways from the Eclipse Foundation Workshop in Brussels</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://advancedweb.hu/backups-with-restic-2-year-retrospective/" target="_blank" rel="noopener"><strong>Backups with Restic: 2-year retrospective</strong></a> [<span style="color: #800000;">advancedweb.hu</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mnot.net/blog/2026/well_known_uris" target="_blank" rel="noopener"><strong>So You Want To Define a Well-Known URI</strong></a> [<span style="color: #800000;">mnot.net</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/06/18/working-effectively-with-claude-code.html" target="_blank" rel="noopener"><strong>Working Effectively with Claude Code</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/06/19/sustainable-acceleration-and-the-agentic-software-development-life-cycle.html" target="_blank" rel="noopener"><strong>Sustainable acceleration and the Agentic Software Development Life Cycle</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/how-netflix-simplified-batch-compute-with-kueue-87860682629c" target="_blank" rel="noopener"><strong>How Netflix Simplified Batch Compute with Kueue</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/toward-more-controllable-ai-video-editing-an-early-research-exploration-at-netflix-eb8160ed60a2" target="_blank" rel="noopener"><strong>Toward More Controllable AI Video Editing: An Early Research Exploration at Netflix</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/quarkus-unpacked-insights-from-the-foojay-podcast/" target="_blank" rel="noopener"><strong>Quarkus Unpacked: Insights from the Foojay Podcast</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/06/25/podcast-060/" target="_blank" rel="noopener"><strong>Episode 60 &#8220;How JEPs Drive Java&#8217;s Evolution&#8221; [AtA]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/25/a-bootiful-podcast-francesco-ciulla" target="_blank" rel="noopener"><strong>A Bootiful Podcast: My friend Francesco Ciulla on developer advocacy and more</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/25/spring-boot-3-5-16-available-now" target="_blank" rel="noopener"><strong>Spring Boot 3.5.16 available now</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/24/spring-data-2025-0-13-released" target="_blank" rel="noopener"><strong>Spring Data 2025.0.13 released</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/CVE-2026-50559/" target="_blank" rel="noopener"><strong>Emergency releases to fix CVE-2026-50559 in all supported streams</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.36.3" target="_blank" rel="noopener"><strong>Quarkus 3.36.3</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.33.2.1" target="_blank" rel="noopener"><strong>3.33.2.1</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.27.4.1" target="_blank" rel="noopener"><strong>3.27.4.1</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.20.6.2" target="_blank" rel="noopener"><strong>3.20.6.2</strong></a> [<span style="color: #800000;">github.com/quarkusio</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.1.3" target="_blank" rel="noopener"><strong>Vert.x 5.1.3</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v8.19.17" target="_blank" rel="noopener"><strong>Elasticsearch 8.19.17</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.3.6" target="_blank" rel="noopener"><strong>9.3.6</strong></a> [<span style="color: #800000;">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.16" target="_blank" rel="noopener"><strong>Zuul v3.6.16</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.6.15" target="_blank" rel="noopener"><strong>v3.6.15</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.2" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.2</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/3.2.18" target="_blank" rel="noopener"><strong>Helidon 3.2.18</strong></a> [<span style="color: #800000;">github.com/helidon-io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wildfly.org/news/2026/06/19/WildFly-40-0-1-is-released/" target="_blank" rel="noopener"><strong>WildFly 40.0.1 is released!</strong></a> [<span style="color: #800000;">wildfly.org</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">2.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://yusufaytas.com/old-software-was-fast-because-it-had-no-choice">&gt;&gt; Old Software Was Fast Because It Had No Choice</a></strong> [<span style="color: #993300;">yusufaytas.com</span>]</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-652">Java Weekly, Issue 652</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958459364/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958459364/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958459364/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-652#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-652/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/958459364/0/baeldung/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-agent-skills</feedburner:origLink>
		<title>A Guide to Agent Skills in Spring AI</title>
		<link>https://feeds.feedblitz.com/~/958380014/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958380014/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Hardik Singh Behl]]></dc:creator>
		<pubDate>Wed, 24 Jun 2026 10:26:06 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[OpenAI]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-ai-guide-agent</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Explore Agent Skills using Spring AI to locally define, package, and grant capabilities to an AI agent for lightweight tasks for which MCPs would be an overkill.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958380014/0/baeldung">A Guide to Agent Skills in Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958380014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958380014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958380014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958380014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958380014/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-agent-skills#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-agent-skills/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>Modern web applications are increasingly integrating with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/large-language-models">Large Language Models (LLMs)</a> to build solutions that go beyond simple question answering. To create AI agents capable of handling complex user requests, we often connect them to multiple <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-model-context-protocol-mcp">Model Context Protocol (MCP)</a> servers that provide them with specific capabilities via tools.</p>
<p>However, creating and running MCP servers can be overkill for lightweight and local automation tasks where we just need to expose a simple capability to a single agent.</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://agentskills.io/home">Agent Skills</a> is a specification that provides a structured way to locally define, package, and expose these capabilities to an AI agent.</p>
<p>In this tutorial, we&#8217;ll explore the Agent Skills capability in Spring AI. We&#8217;ll configure a custom skill and integrate it with a simple chatbot to summarize articles.</p>
<h2 id="bd-what-are-agent-skills" data-id="what-are-agent-skills">2. What are Agent Skills?</h2>
<div class="bd-anchor" id="what-are-agent-skills"></div>
<p><strong>Agent Skills is an open specification for defining various capabilities that an AI agent can invoke</strong>. <strong>A skill is essentially a directory containing a <em>SKILL.md</em> file, which acts as its manifest, alongside any associated code</strong> like Python or Bash scripts, or additional resources that the skill relies on.</p>
<p>The <em>SKILL.md</em> file contains a frontmatter block with a name and description, followed by a set of natural language instructions that tell the agent how to use the skill.</p>
<p><strong>When the agent receives a user request, it reads the descriptions of all available skills and decides if any is relevant</strong>. If yes, it then loads the relevant files into context and follows the instructions inside the matching skill to fulfill the request. And if no skill matches the request, the agent simply responds using its general capabilities without invoking any skill.</p>
<p>We&#8217;ll see an agent invoking our custom skill practically in action in the upcoming sections.</p>
<h2 id="bd-setting-up-the-project" data-id="setting-up-the-project">3. Setting up the Project</h2>
<div class="bd-anchor" id="setting-up-the-project"></div>
<p>Before we dive into the implementation, we’ll need to include the necessary dependencies and configure our application correctly.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">3.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p>Let&#8217;s start by adding the necessary dependencies to our project’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-agent-utils&lt;/artifactId&gt;
    &lt;version&gt;0.10.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 a chat model. Support for agent skills is available in Spring AI 2 and later, so we need to make sure we&#8217;re using the correct version.</p>
<p>Next, we import the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/spring-ai-agent-utils">agent-utils dependency</a> from the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~github.com/spring-ai-community">Spring AI community</a>, which enables us to add the agent skills capability to our chat models.</p>
<h3 id="bd-2-configuring-a-chat-model" data-id="2-configuring-a-chat-model">3.2. Configuring a Chat Model</h3>
<div class="bd-anchor" id="2-configuring-a-chat-model"></div>
<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:
        options:
          model: gpt-5.5</code></pre>
<p>We use the <em>${}</em> property placeholder to load the value of our API Key from an <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-properties-env-variables#use-environment-variable-in-applicationyml-file">environment variable</a>.</p>
<p>Additionally, 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 that supports the agent skills specification, as the specific AI model or provider is irrelevant for this demonstration.</p>
<p>With these two properties set, <strong>Spring AI automatically creates a bean of type <em>ChatModel</em>, allowing us to interact with the specified model</strong>.</p>
<h2 id="bd-defining-our-custom-skill" data-id="defining-our-custom-skill">4. Defining Our Custom Skill</h2>
<div class="bd-anchor" id="defining-our-custom-skill"></div>
<p>Now let&#8217;s define a custom agent skill that can fetch an article from a URL and summarize it.</p>
<p>Agent skills follow a specific directory structure, so let&#8217;s set that up. <strong>We&#8217;ll start by creating an <em>.openai/skills</em> directory in our project&#8217;s root directory. We can define multiple subdirectories inside this, each representing a distinct agent skill</strong>.</p>
<p>Next, <strong>we&#8217;ll create an <em>article-summarizer</em> subdirectory to represent our custom skill inside .openai/skills</strong> and define our main <em>SKILL.md</em> file inside it:</p>
<pre><code class="language-markdown">---
name: article-summarizer
description: Summarizes articles into concise digests. Useful when user asks to summarize or get key points from an article.
---
# Article Summarizer
## Instructions
When summarizing an article:
1. If given a URL: Run `uv run scripts/fetch_article.py &lt;url&gt;` to retrieve the content.
2. Once content is available, extract the main thesis, few key points, and conclusion.
3. Structure the output as a TL;DR, key points, and a bottom line.</code></pre>
<p>In the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.github.com/en/contributing/writing-for-github-docs/using-yaml-frontmatter">frontmatter block</a>, we define the name and description of our skill. <strong>The description is especially important as the agent uses it to determine whether this skill is relevant for a given user request</strong>. Next, we define the instructions that tells the agent exactly what steps to follow when the skill is invoked, including which script to run and how to structure the final output.</p>
<p>Next, let&#8217;s create the <em>fetch_article.py</em> script inside a new <em>scripts</em> subdirectory that we reference in the instructions:</p>
<pre><code class="language-python">ARTICLE = """
... hardcoding sample article for demonstration
"""
print(ARTICLE)</code></pre>
<p>Here, for our demonstration, we simply print a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-mcp-elicitations">hardcoded article about MCP elicitations</a> instead of actually making a web request. <strong>The AI model will run this script and read the standard output to get this article&#8217;s content regardless of the URL in the request </strong>.</p>
<p>Also, <strong>it&#8217;s important to note that we can define our scripts in any language of our choice</strong>. We just need to make sure that we pre-install the required runtimes for our agent to execute the necessary commands.</p>
<h2 id="bd-creating-a-simple-chatbot" data-id="creating-a-simple-chatbot">5. Creating a Simple Chatbot</h2>
<div class="bd-anchor" id="creating-a-simple-chatbot"></div>
<p>With our configurations in place, let’s build a simple chatbot.</p>
<p>In Spring AI, <strong>the <em>ChatClient</em> class acts as the main entry point for interacting with our configured chat completion model</strong>. Let&#8217;s define its bean using the auto-configured <em>ChatModel</em> bean:</p>
<pre><code class="language-java">@Bean
ChatClient chatClient(ChatModel chatModel) {
    String skillsRootDirectory = ".openai/skills";
    return ChatClient
      .builder(chatModel)
      .defaultTools(
        SkillsTool.builder()
          .addSkillsDirectory(skillsRootDirectory)
          .build(),
        FileSystemTools.builder()
          .allowedDirectory(skillsRootDirectory)
          .build(),
        ShellTools.builder()
          .build()
      ).build();
}</code></pre>
<p>In our bean definition, <strong>we first register our custom skills directory using <em>SkillsTool</em>, pointing it to the <em>.openai/skills</em> directory</strong>.</p>
<p>Secondly, <strong>we register <em>FileSystemTools</em>, which gives the agent the ability to read and write any files on the local filesystem</strong>. To restrict the tool&#8217;s operations to the configured skills directory, we use the <em>allowedDirectory()</em> method.</p>
<p>Finally, <strong>we register <em>ShellTools</em>, which allows the agent to execute shell commands</strong>, enabling it to run the Python script we&#8217;ve defined.</p>
<p>However, it&#8217;s important to note that <em>ShellTools</em> executes our scripts directly on the local machine without <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/sandboxing-fundamentals">sandboxing</a>. As such, <strong>we should carefully review the scripts we expose to our agent</strong> and consider <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/dockerizing-spring-boot-application">containerizing our application</a> to limit potential exposure.</p>
<p>Next, let’s inject the <em>ChatClient</em> bean in a controller class and expose a REST API:</p>
<pre><code class="language-java">@PostMapping("/chat")
ResponseEntity&lt;ChatbotResponse&gt; chat(@RequestBody ChatbotRequest chatbotRequest) {
    String answer = chatClient
      .prompt()
      .user(chatbotRequest.question)
      .call()
      .content();
    return ResponseEntity.ok(new ChatbotResponse(answer));
}
record ChatbotRequest(String question) {}
record ChatbotResponse(String answer) {}</code></pre>
<p>Here, we simply pass the user’s <em>question</em> to the <em>chatClient</em> instance and return the LLM&#8217;s response. We’ll use this API endpoint to interact with our chatbot in the upcoming section.</p>
<h2 id="bd-interacting-with-our-chatbot" data-id="interacting-with-our-chatbot">6. Interacting With Our Chatbot</h2>
<div class="bd-anchor" id="interacting-with-our-chatbot"></div>
<p>Now that we’ve built our implementation, let’s interact with our chatbot and test the agent skill capability.</p>
<p>We’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 the chatbot’s API endpoint:</p>
<pre class="add-horizontal-scrollbar"><code class="language-bash">http POST :8080/chat question="Can you summarize the following article: https://www.baeldung.com/sample-non-existing-article"</code></pre>
<p>Here, we ask the chatbot to summarize a specific article by passing a URL in the question. <strong>We deliberately provide the URL of a non-existent article to verify that the chatbot summarizes the article we&#8217;ve hardcoded in our Python script</strong>.</p>
<p>Let’s see what we get as a response:</p>
<pre class="add-horizontal-scrollbar"><code class="language-json">{
  "answer": "## TL;DR\nThis article explains how to implement MCP Elicitations in Spring AI, allowing MCP servers to request additional user information dynamically during tool execution.
    \n\n## Key Points\n- MCP Elicitations solve the problem of missing user information during MCP tool execution.
    \n- The tutorial demonstrates building an MCP server using Spring AI.
    \n- The MCP server exposes a tool that fetches author details and conditionally requests additional information.
    \n- The `elicit()` method is used to pause execution and gather required details from the user.
    \n- The article also demonstrates configuring an MCP client and integrating it with an Anthropic Claude model
    \n- Spring AI automatically creates MCP clients and tool callback providers from configuration.
    \n- An `@McpElicitation` handler is used on the client side to respond to elicitation requests.
    \n- The tutorial concludes with a working chatbot example and log outputs showing the complete elicitation flow.
    \n\n## Bottom Line\nMCP Elicitations enable interactive AI applications where tools can dynamically collect additional context from users during execution,
    making MCP-based systems more flexible and user-aware."
}</code></pre>
<p>As we can see, <strong>the LLM summarizes our hardcoded article and the response is structured exactly as our skill&#8217;s instructions prescribed, with a TL;DR, a set of key points, and a bottom line</strong>.</p>
<p>Behind the scenes, the agent matched the user&#8217;s request to the <em>article-summarizer</em> skill based on its description, loaded the instructions into context using <em>FileSystemTools</em>, executed the <em>fetch_article.py</em> script via <em>ShellTools</em> to retrieve the article content, and then structured the response following the instructions in our <em>SKILL.md</em> file.</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 explored the concept of Agent Skills using Spring AI.</p>
<p>We started by understanding what Agent Skills are and how they help us in exposing reusable capabilities to an AI agent.</p>
<p>Then, we defined a custom article summarizer skill and wired it into a chatbot. Finally, we interacted with our chatbot to confirm that it correctly discovers and invokes our skill.</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-agent-skills">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-agent-skills">A Guide to Agent Skills in Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958380014/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958380014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958380014/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958380014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958380014/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958380014/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-agent-skills#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-agent-skills/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/~/958380014/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-10-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-spring-dynamic-authorization-scopes</feedburner:origLink>
		<title>Dynamic Authorization Scopes in Spring Authorization Server</title>
		<link>https://feeds.feedblitz.com/~/958357049/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958357049/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Philippe Sevestre]]></dc:creator>
		<pubDate>Wed, 24 Jun 2026 10:09:15 +0000</pubDate>
				<category><![CDATA[Spring Security]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[OAuth Spring]]></category>
		<category><![CDATA[Spring Boot 4]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-spring-dynamic-authorization-scopes</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how we can modify the Spring Authorization Server's default configuration to support dynamic scopes.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958357049/0/baeldung">Dynamic Authorization Scopes in Spring Authorization Server</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958357049/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958357049/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958357049/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958357049/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958357049/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-spring-dynamic-authorization-scopes#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-spring-dynamic-authorization-scopes/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>In this tutorial, we&#8217;ll show how to extend the Spring Authorization Server to handle dynamic OAuth 2.0 scopes, which are required in advanced authentication/authorization use cases.</p>
<h2 id="bd-when-to-use-dynamic-scopes" data-id="when-to-use-dynamic-scopes">2. When to Use Dynamic Scopes?</h2>
<div class="bd-anchor" id="when-to-use-dynamic-scopes"></div>
<p>OAuth2-based applications use scopes to request access tokens with a defined set of privileges, like reading fields from a user&#8217;s profile, performing an automation task, and so on. Usually, the scopes are fixed and known in advance both by the authorization server and the client application. In some scenarios, however, this is not the case.</p>
<p><strong>For instance, suppose your client application needs an access token to execute a single operation, like a transfer request or updating sensitive information</strong>. One option is to implement a custom authorization scheme between the client and the resource server, but we&#8217;d be reinventing the wheel. From a security perspective, this is also not ideal, since it implies a bigger surface attack, as those protocols would need to be implemented across every resource server and client.</p>
<p>This is where dynamic scopes come into play. <strong>In a nutshell, a dynamic scope is one that a client can request, but it&#8217;s not known beforehand by the authorization server.</strong></p>
<p>Usually, a dynamic scope requires some sort of validation service to exist, so it can be validated. In practical terms, this requires a consent repository to store the requested scope, which will be used to validate it and, if the end user grants it, to update its status.</p>
<p><strong>When the client receives the access token, it will have the dynamic scope associated with it, allowing it to complete the desired operation.</strong></p>
<h2 id="bd-dynamic-scopes-in-spring-authorization-server" data-id="dynamic-scopes-in-spring-authorization-server">3. Dynamic Scopes in Spring Authorization Server</h2>
<div class="bd-anchor" id="dynamic-scopes-in-spring-authorization-server"></div>
<p>The Spring Authorization Server, true to its roots, has a modular architecture, <strong>which allows us to replace many of its components with custom ones</strong>. We&#8217;ve already explored this characteristic <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-multitenancy">to implement multitenancy support</a>, a feature not available by default. In our case, these are the aspects we need to modify:</p>
<ul>
<li>Scope validation logic</li>
<li>Consent Validation</li>
<li>Consent page: optional, but usually required</li>
</ul>
<p>Moreover, it&#8217;s a good idea to separate the Spring-specific code from the actual scope validation logic. We&#8217;ll create a <em>DynamicScopeService</em> for that purpose, leaving to a <em>@Configuration</em> class the task of creating an adapter between the expected method&#8217;s signature and the service.</p>
<p>Now, let&#8217;s move to the implementation.</p>
<h2 id="bd-project-setup" data-id="project-setup">4. Project Setup</h2>
<div class="bd-anchor" id="project-setup"></div>
<p>Dynamic Scopes don&#8217;t need any extra dependencies besides those needed by the Spring Authorization Server itself:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-security-oauth2-authorization-server&lt;/artifactId&gt;
    &lt;version&gt;4.1.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-security-oauth2-authorization-server-test&lt;/artifactId&gt;
    &lt;version&gt;4.1.0&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
</code></pre>
<p>The latest versions of these dependencies are available on Maven Central:</p>
<ul>
<li><em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security-oauth2-authorization-server">spring-boot-starter-security-oauth2-authorization-server</a></em></li>
<li><em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security-oauth2-authorization-server-test">spring-boot-starter-security-oauth2-authorization-server-test</a></em></li>
</ul>
<h2 id="bd-security-configuration" data-id="security-configuration">5. Security Configuration</h2>
<div class="bd-anchor" id="security-configuration"></div>
<p>The Spring Authorization Server needs two security filter chains to operate:</p>
<ul>
<li>one for handling OAuth/OIDC protocol endpoints</li>
<li>a &#8220;catch-all&#8221; chain that handles everything else, including user authentication</li>
</ul>
<p>As usual in Spring Security, the built-in ones are only created when there are no application-defined chains available. <strong>In our case, we need to tweak just the first one, but by doing so, the auto-configuration for the catch-all chain will be disabled, so we&#8217;ll need to provide both.</strong></p>
<h3 id="bd-1-authorization-server-chain" data-id="1-authorization-server-chain">5.1. Authorization Server Chain</h3>
<div class="bd-anchor" id="1-authorization-server-chain"></div>
<p>This is the modified chain where we&#8217;ll add our customization logic:</p>
<pre><code class="language-java">@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) {
    http.oauth2AuthorizationServer(authorizationServer -&gt; {
        http.securityMatcher(authorizationServer.getEndpointsMatcher());
        authorizationServer
          .oidc(withDefaults())
          .authorizationEndpoint(ap -&gt; {
              ap.consentPage("/consent");
              ap.authenticationProviders(providers -&gt; {
                  providers.stream()
                    .filter(OAuth2AuthorizationCodeRequestAuthenticationProvider.class::isInstance)
                    .map(p -&gt; (OAuth2AuthorizationCodeRequestAuthenticationProvider)p)
                    .findFirst()
                    .ifPresent(p -&gt; {
                        p.setAuthenticationValidator(dynamicScopesAuthenticationValidator());
                        p.setAuthorizationConsentRequired(dynamicScopesConsentValidator());
                    });
              });
          });
    });
    http.authorizeHttpRequests(authorize -&gt; authorize.anyRequest().authenticated());
    http.oauth2ResourceServer(resourceServer -&gt; resourceServer.jwt(withDefaults()));
    http.exceptionHandling(exceptions -&gt; exceptions.defaultAuthenticationEntryPointFor(
      new LoginUrlAuthenticationEntryPoint("/login"), createRequestMatcher()));
    return http.build();
}
</code></pre>
<p>The scope validation takes place at the <em>OAuth2AuthorizationCodeRequestAuthenticationProvider</em>, which uses two collaborating objects to perform its task:</p>
<ul>
<li>An <em>AuthenticationValidator</em> to validate the submitted parameters</li>
<li>A <em>Predicate</em> that decides whether to ask for consent</li>
</ul>
<p>Since the authorization server uses multiple <em>AuthenticationProvider</em>s in its implementation, we need to go through all of them until we find the proper one. All this happens in the <em>authorizationEndpointCustomizer</em>, which exposes the list of authentication providers.</p>
<p>The same customizer also allows us to set the <em>URI</em> for a custom consent page, which will be &#8220;/consent&#8221; in our case. We&#8217;ll come back to the consent page later in this tutorial.</p>
<h3 id="bd-2-catch-all-chain" data-id="2-catch-all-chain">5.2. &#8220;Catch-All&#8221; Chain</h3>
<div class="bd-anchor" id="2-catch-all-chain"></div>
<p>This chain, which has no <em>securityMatcher()</em> call in its DSL, will apply the following rules to incoming requests:</p>
<ul>
<li>Any request must be authenticated</li>
<li>When receiving a non-authenticated request, use form-based login with default settings</li>
</ul>
<pre><code class="language-java">@Bean
@Order(SecurityFilterProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) {
    http.authorizeHttpRequests(authorize -&gt; {
        authorize.anyRequest().authenticated();
      })
      .formLogin(withDefaults());
    return http.build();
}
</code></pre>
<p>Here, we&#8217;re just replicating the default configuration. In a real-world scenario, we&#8217;d probably want to change some aspects of its properties. For instance, this is where we&#8217;d provide the <em>URI</em> for a custom login page, enable two-factor authentication, or add support for a federated identity provider.</p>
<h2 id="bd-scope-validation" data-id="scope-validation">6. Scope Validation</h2>
<div class="bd-anchor" id="scope-validation"></div>
<p>The <em>dynamicScopesAuthenticationValidator()</em> method creates an adapter that isolates the Spring Security aspects of the scope validation from the business logic.</p>
<p>In our tutorial, we&#8217;ll assume that the dynamic scope will be a string that starts with the &#8220;TX:&#8221; prefix, followed by a unique transaction identifier. When a client needs an access token with this scope, it must add it to the scope request parameter of an authentication request, as in this example:</p>
<pre><code class="language-shell">https://idp/oauth2/authorize?scope=openid TX:1234&amp;...other params omitted</code></pre>
<p>The authentication provider will perform the standard validation steps and, eventually, will call our adapter to perform the validation logic:</p>
<pre><code class="language-java">private Consumer&lt;OAuth2AuthorizationCodeRequestAuthenticationContext&gt; dynamicScopesAuthenticationValidator() {
    return ctx -&gt; {
        OAuth2AuthorizationCodeRequestAuthenticationToken auth = ctx.getAuthentication();
        var requestedScopes = new HashSet&lt;&gt;(auth.getScopes());
        if ( requestedScopes.isEmpty() ) {
            return;
        }
        var registeredClient = ctx.getRegisteredClient();
        var allowedScopes = registeredClient.getScopes();
        requestedScopes.removeIf(allowedScopes::contains);
        if (requestedScopes.isEmpty() ) {
            return;
        }
        // Now, let's validate the remaining scopes using the provided validation service
        try {
            if (!dynamicScopeService.validate(registeredClient.getId(), requestedScopes)) {
                throw new OAuth2AuthorizationCodeRequestAuthenticationException(
                  new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE),
                  auth
                );
            }
        } catch (Exception ex) {
            throw new OAuth2AuthorizationCodeRequestAuthenticationException(
              new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR), 
              ex, 
              auth
            );
        }
    };
}
</code></pre>
<p>The adapter receives an <em>OAuth2AuthorizationCodeRequestAuthenticationContext</em> instance that contains information about the authorization request. <strong>Notice that, at this point, the end user is not yet known, so we can&#8217;t use it as part of this logic</strong>.</p>
<p>These are the steps performed by the adapter:</p>
<ul>
<li>Firstly, recover the requested scopes and client information from the context.</li>
<li>Secondly, remove any static scopes from the list.</li>
<li>If there are no dynamic scopes, return immediately</li>
<li>Otherwise, pass the client identifier and dynamic scopes to the validation service</li>
</ul>
<p>The validation service&#8217;s job is to return just a yes/no answer depending on whether the request should be authorized. Our implementation, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-auth-server/src/main/java/com/baeldung/auth/server/dynamicscopes/components/impl/DynamicScopeServiceImpl.java">available online</a>, just checks for a valid pattern. A real-world one would perform more complex validation, such as querying a database and applying any rules required by the specific use case.</p>
<h2 id="bd-consent-validation" data-id="consent-validation">7. Consent Validation</h2>
<div class="bd-anchor" id="consent-validation"></div>
<p>The other tweak we&#8217;ve made to support dynamic scopes is to add custom logic to determine whether the authorization needs to ask for user consent before issuing an access token. <strong>This was done using the AuthenticationProvider&#8217;s <em>setAuthorizationConsentRequentid()</em> method, which accepts a <em>Predicate</em> argument.</strong></p>
<p>This <em>Predicate</em> should return <em>true</em> when an authorization request needs user consent, and <em>false</em> otherwise. As we&#8217;ve done in the scope validation case, we&#8217;ll use an adapter to extract the client&#8217;s identifier and requested scopes from the <em>OAuth2AuthorizationCodeRequestAuthenticationContext</em> instance passed to the <em>Predicate</em>. Once the basic checks are done, we invoke the validation service to get a final decision about the consent:</p>
<pre><code class="language-java">private Predicate&lt;OAuth2AuthorizationCodeRequestAuthenticationContext&gt; dynamicScopesConsentValidator() {
    return ctx -&gt; {
        var previousConsent = ctx.getAuthorizationConsent();
        if ( previousConsent == null ) {
            // First consent, so consent is required
            return true;
        }
        OAuth2AuthorizationCodeRequestAuthenticationToken auth = ctx.getAuthentication();
        var requestedScopes = new HashSet&lt;&gt;(auth.getScopes());
        if ( requestedScopes.isEmpty() ) {
            return false;
        }
        // Remove already consented scopes
        var alreadyConsented = new HashSet&lt;&gt;(previousConsent .getScopes());		
        requestedScopes.removeIf(alreadyConsented::contains);
        if (requestedScopes.isEmpty() ) {
            return false;
        }
        // Any remaining scopes are dynamic scopes or static ones with no previous consent
        return dynamicScopeService.isConsentNeeded(ctx.getRegisteredClient().getId(), requestedScopes);
    };
}
</code></pre>
<p>A key distinction of this validation is that it happens after a successful authentication. <strong>This means that the business logic can use any of the standard Spring Security methods to get information about the current user and use it as part of the decision logic</strong>.</p>
<h2 id="bd-consent-page" data-id="consent-page">8. Consent Page</h2>
<div class="bd-anchor" id="consent-page"></div>
<p>This page will be displayed to the user after a successful authentication, right before issuing an authorization code to a client application.</p>
<p><strong>From the user agent&#8217;s perspective, this page will be accessed through a redirect from the login page</strong>. The <em>Location</em> header will point to the configured <em>URI</em> along with the following query parameters:</p>
<ul>
<li><em>client_id</em>: Client application&#8217;s identifier</li>
<li><em>scope</em>: Requested scopes</li>
<li><em>state</em>: Server-side state associated with the current authorization request.</li>
</ul>
<p><strong>Please note that the state parameter in this case has no relation to the one with the same name that&#8217;s included in the initial authorization <em>URL</em> created by the client!</strong></p>
<p>Let&#8217;s implement a simple controller that takes these parameters, prepares a Model, and forwards the request to a view:</p>
<pre><code class="language-java">@Controller
public class ConsentController {
    private final RegisteredClientRepository registeredClientRepository;
    private final OAuth2AuthorizationConsentService authorizationConsentService;
    // ... constructor omitted
    @GetMapping("/consent")
    public String consent(Principal principal, Model model,
      @RequestParam(name = OAuth2ParameterNames.CLIENT_ID) String clientId,
      @RequestParam(name = OAuth2ParameterNames.SCOPE) String scope,
      @RequestParam(name = OAuth2ParameterNames.STATE) String state) {
        var client = registeredClientRepository.findByClientId(clientId);
        assert client != null;
        var currentConsent =  authorizationConsentService.findById(client.getId(), principal.getName());
        Set&lt;String&gt; authorizedScopes = currentConsent != null ? currentConsent.getScopes() : Set.of();
        // Remove already authorized scopes from the requested scopes and the special 'openid' scope.
        var neededScopes = Set.of(scope.split(" ")).stream()
          .filter(s -&gt; !authorizedScopes.contains(s) &amp;&amp; !OidcScopes.OPENID.equals(s))
          .toList();
        model.addAttribute("clientId", clientId);
        model.addAttribute(
          "clientName", 
          client.getClientName() != null ? client.getClientName() : client.getClientId()
        );
        model.addAttribute("scopes", neededScopes);
        model.addAttribute("state", state);
        model.addAttribute("authorizedScopes", authorizedScopes);
        return "consent";
   }
}
</code></pre>
<p>We&#8217;ve injected two services in this controller to look for additional information needed to build the view:</p>
<ul>
<li><em>registeredClientRepository</em>: Allow us to retrieve detailed information for the client, given its identifier</li>
<li><em>authorizationConsentService</em>: Returns an <em>OAuthAuthorizationConsent</em> containing all consents given to a client by a user</li>
</ul>
<p>We use those services to build a list of the newly required consents and store it in a Spring MVC <em>Model</em>.</p>
<p>Here, we could also query a business service to enrich the <em>Model</em> with details related to any requested dynamic scope. For instance, if our dynamic scope represents a transfer request, we could look up its details and include them in the model.</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-auth-server/src/main/resources/templates/consent.html">The view itself</a> uses a Thymeleaf template to display the consent request and present to the user an option to accept or deny it.<strong> Note that the form&#8217;s submit target is, by default, protected with a CSRF token, so make sure to use the th:action=&#8221;<em>@{/path}&#8221;</em> syntax</strong>. Thanks to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.thymeleaf.org/doc/tutorials/3.1/thymeleafspring.html#integration-with-requestdatavalueprocessor">Thymeleaf&#8217;s Spring integration</a>,  this will automatically include a hidden input field with the correct CSRF token value.</p>
<p>Last, but not least, note that the form&#8217;s action target should point to the server&#8217;s OAuth2 authorization endpoint. Later, if we decide to change this <em>URI</em> from the default value, we must make sure that this form points to the same place.</p>
<h2 id="bd-testing" data-id="testing">9. Testing</h2>
<div class="bd-anchor" id="testing"></div>
<p>Now, let&#8217;s write a test to verify our customized authorization server&#8217;s behavior, at least for the &#8220;happy path&#8221; case. The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-auth-server/src/test/java/com/baeldung/auth/server/dynamicscopes/DynamicScopesAuthServerUnitTest.java">full code</a> is a bit long, so here we&#8217;ll just discuss the main caveats when implementing it.</p>
<p>The first thing is that, to validate end-to-end calls, it&#8217;s better to use an environment that&#8217;s close to the real one. This is why we use <em>WebEnvironment.RANDOM_PORT</em>, <strong>which starts a full servlet stack bound to a free local port.</strong> The port&#8217;s actual value is injected into the test using the <em>@LocalPort</em> annotation, so we can use it to build all request <em>URLs</em>.</p>
<p>Secondly, we need to be able to keep session cookies between requests sent throughout the authentication/authorization process. This means we need to configure a <em>RestTestClient</em> with a factory request with this support enabled.</p>
<p>In our case, there&#8217;s another issue. We can instruct a <em>RestTestClient</em>  to either follow all redirects or none. However, once configured, it&#8217;s not trivial to modify this behavior. We could opt to use manual redirect handling only, but that would require additional coding. <strong>Instead, we&#8217;ve opted to define two clients: one that follows redirects, and another that doesn&#8217;t</strong>. Both clients share the same <em>CookieManager</em> instance, so we can use either one in the test without getting into session-related issues:</p>
<pre><code class="language-java">@BeforeEach
void setupRestClient() {
    CookieManager cookieManager = new CookieManager();
    var followRedirectsHttpClient = HttpClient.newBuilder()
      .followRedirects(HttpClient.Redirect.ALWAYS)
      .cookieHandler(cookieManager)
      .build();
    var followRedirectsRequestFactory = new JdkClientHttpRequestFactory(followRedirectsHttpClient);
    restTestClient = RestTestClient
      .bindToServer(followRedirectsRequestFactory)
      .baseUrl("http://localhost:" + port)
      .build();
    var noRedirectsHttpClient = HttpClient.newBuilder()
      .followRedirects(HttpClient.Redirect.NEVER)
      .cookieHandler(cookieManager)
      .build();
    var noRedirectsRequestFactory = new JdkClientHttpRequestFactory(noRedirectsHttpClient);
    noRedirecRestTestClient = RestTestClient
      .bindToServer(noRedirectsRequestFactory)
      .baseUrl("http://localhost:" + port)
      .build();
}
</code></pre>
<p>Our &#8220;happy-path&#8221; test covers the following:</p>
<ul>
<li>Endpoint discovery</li>
<li>Building an authorization request with a dynamic scope</li>
<li>Submitting user credentials to the login form</li>
<li>Submitting values in the consent form</li>
<li>Using the generated authentication code to retrieve an access token</li>
<li>Validate that the access token has the request dynamic scope associated with it</li>
</ul>
<p><strong>Those steps cover a complete authorization flow, producing an access token that we can use to send a request to a resource server.</strong></p>
<h2 id="bd-10-conclusion" data-id="10-conclusion">10. Conclusion</h2>
<div class="bd-anchor" id="10-conclusion"></div>
<p>In this tutorial, we&#8217;ve seen how we can modify the Spring Authorization Server&#8217;s default configuration to support dynamic scopes. This expands the range of use cases where we can use it, making it a suitable candidate for complex authorization-related scenarios.</p>
<p>As always, the full source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-auth-server">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-spring-dynamic-authorization-scopes">Dynamic Authorization Scopes in Spring Authorization Server</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958357049/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958357049/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958357049/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958357049/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958357049/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958357049/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-spring-dynamic-authorization-scopes#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-spring-dynamic-authorization-scopes/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/~/958357049/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-651</feedburner:origLink>
		<title>Java Weekly, Issue 651</title>
		<link>https://feeds.feedblitz.com/~/958199051/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/958199051/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 19 Jun 2026 15:01:01 +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=204055</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Spring AI 2.0 is finally out. Time to start updating the course :)</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/958199051/0/baeldung">Java Weekly, Issue 651</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958199051/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958199051/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958199051/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958199051/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958199051/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-651#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-651/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/15/spring-ai-composable-tool-calling">&gt;&gt; Tool Calling in Spring AI 2.0: A Composable, Agentic Architecture</a></strong> [<span style="color: #993300;">spring.io</span>]</p>
<p>Spring AI 2.0 is out this week (see below) and turns tool calling into a first-class element, making agent loops easier to observe, compose, and extend. A strong read for anyone building agentic systems on Spring who needs more than a black-box function call.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/06/15/java-microservices-fast-go-2026-benchmark/" target="_blank" rel="noopener"><strong>Can Java Microservices Be As Fast As Go? A 2026 Benchmark Update</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/introducing-quarkus-data/" target="_blank" rel="noopener"><strong>Introducing Quarkus Data: One Gateway for Data Access</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://blog.frankel.ch/bigdecimal-vs-double/" target="_blank" rel="noopener"><strong>double, BigDecimal, or Fixed-Point?</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/spring-quarkus-jooby-vertx-java-framework-comparison/" target="_blank" rel="noopener"><strong>Spring vs Quarkus vs Jooby vs Vert.x: Pick Your Next Java Framework</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/06/spring-boot-4-1/" target="_blank" rel="noopener"><strong>Spring Boot 4.1 Adds gRPC Auto-Configuration, SSRF Mitigation, and Kotlin 2.3 Support</strong></a> [<span style="color: #800000;">infoq.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/news/2026/06/oracle-genai-policies/" target="_blank" rel="noopener"><strong>Oracle&#8217;s OpenJDK Bans Generative AI Contributions While Oracle&#8217;s GraalVM Allows Them</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://inside.java/2026/06/14/cline-migrate-java-oca/" target="_blank" rel="noopener"><strong>How Agentic Coding Can Help You Migrate Java Applications Faster</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/06/11/thesis-simplify-weak-reference-processing-zgc/" target="_blank" rel="noopener"><strong>Simplifying Weak Reference Processing in ZGC</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/observing-apache-netbeans-with-opentelemetry-and-dash0/" target="_blank" rel="noopener"><strong>Observing Apache NetBeans with OpenTelemetry and Dash0</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/ask-a-lille-dev-what-java-developers-really-think-about-quality-frameworks-communities-and-careers/" target="_blank" rel="noopener"><strong>Ask a Lille Dev: What Java Developers Really Think About Quality, Frameworks, Communities, and Careers</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.christianposta.com/difference-between-microservices-and-ai-agents/" target="_blank" rel="noopener"><strong>The Differences Between Microservices and AI Agents</strong></a> [<span style="color: #800000;">christianposta.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/reliable-llm-bayer.html" target="_blank" rel="noopener"><strong>Building Reliable Agentic AI Systems</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://foojay.io/today/did-ai-just-break-software-security-for-ever/" target="_blank" rel="noopener"><strong>Did AI Just Break Software Security For Ever?</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.scottlogic.com/2026/06/16/ponytail-yagni-and-the-problem-with-prompt-benchmarks.html" target="_blank" rel="noopener"><strong>Ponytail? YAGNI! And the Problem with Prompt Benchmarks</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/06/16/What-a-good-transformation-looks-like.html" target="_blank" rel="noopener"><strong>What a Good Transformation Looks Like</strong></a> [<span style="color: #800000;">scottlogic.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/foojay-podcast-98/" target="_blank" rel="noopener"><strong>Foojay Podcast #98: The End of JNI Pain: How WebAssembly Is Quietly Replacing Native Libraries in Java</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/presentations/java-aws-serverless/" target="_blank" rel="noopener"><strong>Practical Performance Tuning for Serverless Java on AWS</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://spring.io/blog/2026/06/12/spring-ai-2-0-0-GA-available-now" target="_blank" rel="noopener"><strong>Spring AI 2.0 is here!</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/15/spring-tools-5-2-0-released" target="_blank" rel="noopener"><strong>Spring Tools 5.2.0 released</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/06/12/spring-ai-1-1-8-1-0-9-avaialble-now" target="_blank" rel="noopener"><strong>Spring AI 1.0.9, 1.1.8 available now</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://in.relation.to/2026/06/16/orm-80-beta1/" target="_blank" rel="noopener"><strong>Hibernate 8.0.0.Beta1</strong></a> [<span style="color: #800000;">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.37.0" target="_blank" rel="noopener"><strong>Quarkus 3.37.0</strong></a> [<span style="color: #800000;">github.com/quarkusio</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.1" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.1</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/4.5.0" target="_blank" rel="noopener"><strong>Helidon 4.5.0</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/apache/grails-core/releases/tag/v7.1.2" target="_blank" rel="noopener"><strong>Grails 7.1.2</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.0.12" target="_blank" rel="noopener"><strong>7.0.12</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/Netflix/zuul/releases/tag/v3.6.14" target="_blank" rel="noopener"><strong>Zuul 3.6.14</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://www.wildfly.org/news/2026/06/11/A2A-Jakarta-1-0-0-CR1-is-released/" target="_blank" rel="noopener"><strong>A2A Jakarta 1.0.0.CR1 is released!</strong></a> [<span style="color: #800000;">wildfly.org</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">2.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p>Paul Graham&#8217;s short essay is a crisp reminder that the most valuable ideas are often both broadly useful and genuinely surprising. It is a useful lens for technical work too: even a small amount of novelty can matter when it attaches to a big enough idea.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.paulgraham.com/sun.html">&gt;&gt; General and Surprising</a></strong> [<span style="color: #993300;">paulgraham.com</span>]</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-651">Java Weekly, Issue 651</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/958199051/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/958199051/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/958199051/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/958199051/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/958199051/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/958199051/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-651#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-651/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/~/958199051/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
</channel></rss>

