<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://feeds.feedblitz.com/feedblitz_rss.xslt"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:webfeeds="http://webfeeds.org/rss/1.0"  xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
<channel>
	<title>Baeldung</title>
	<atom:link href="https://www.baeldung.com/feed" rel="self" type="application/rss+xml" />
	<link>https://www.baeldung.com</link>
	<description>Java, Spring and Web Development tutorials</description>
	<lastBuildDate>Fri, 31 Jul 2026 18:24:51 +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/spark-dataframe-empty</feedburner:origLink>
		<title>Check if Spark Dataframe Is Empty</title>
		<link>https://feeds.feedblitz.com/~/965089607/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/965089607/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Yadu Krishnan]]></dc:creator>
		<pubDate>Fri, 31 Jul 2026 18:24:51 +0000</pubDate>
				<category><![CDATA[Data]]></category>
		<category><![CDATA[Spark]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204544</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" fetchpriority="high" /><p>Learn how to check whether a Spark DataFrame is empty to improve performance, especially when working with large datasets.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/965089607/0/baeldung">Check if Spark Dataframe Is Empty</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/965089607/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/965089607/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/965089607/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/965089607/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/965089607/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spark-dataframe-empty#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spark-dataframe-empty/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" 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-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>When working with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-spark">Apache Spark</a>, we often encounter situations where a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spark-dataframes">DataFrame</a> becomes empty after applying transformations, filters, or joins.</p>
<p><strong>Before triggering expensive actions or saving data to external storage, it&#8217;s a best practice to verify if the DataFrame contains any rows</strong>. While this may seem like a trivial check, different approaches trigger different Spark actions and can have a noticeable impact on performance, especially for large datasets.</p>
<p>In this tutorial, we&#8217;ll explore different ways to check whether a Spark DataFrame is empty.</p>
<h2 id="bd-setup" data-id="setup">2. Setup</h2>
<div class="bd-anchor" id="setup"></div>
<p>First, let&#8217;s create the following sample DataFrame:</p>
<pre><code class="language-java">public static Dataset getDataFrame(SparkSession spark) {
    List&lt;Row&gt; players = List.of(
      RowFactory.create(1, "Messi", "Argentina"),
      RowFactory.create(2, "Ronaldo", "Portugal"),
      RowFactory.create(3, "Mbappe", "France"));
    return spark.createDataFrame(players, PLAYER_SCHEMA);
}</code></pre>
<p>We&#8217;ll use this DataFrame throughout the examples.</p>
<h2 id="bd-using-the-isempty-method" data-id="using-the-isempty-method">3. Using the <em>isEmpty()</em> method</h2>
<div class="bd-anchor" id="using-the-isempty-method"></div>
<p><strong>Since Spark 2.4.0, the easiest way to check whether a Dataset is empty is by using the <em>isEmpty()</em> method</strong>.</p>
<p>Let&#8217;s create an empty DataFrame by filtering for a country that doesn&#8217;t exist in the dataset.</p>
<pre><code class="language-java">Dataset&lt;Row&gt; englandPlayers = allPlayers.filter(col("country").equalTo("England"));
Assertions.assertTrue(englandPlayers.isEmpty());
</code></pre>
<p>Since there are no England players in the dataframe, the filter returns an empty dataframe and <em>isEmpty()</em> returns true.</p>
<h2 id="bd-using-the-count-method" data-id="using-the-count-method">4. Using the <em>count()</em> method</h2>
<div class="bd-anchor" id="using-the-count-method"></div>
<p>Another way to check for emptiness is by counting the number of rows in a DataFrame and checking if it&#8217;s zero:</p>
<pre><code class="language-java">Dataset&lt;Row&gt; englandPlayers = allPlayers.filter(col("country").equalTo("England"));
Assertions.assertEquals(0, englandPlayers.count());</code></pre>
<p>Here, we invoked <em>count()</em> and verified that the number of rows is 0.</p>
<h2 id="bd-using-the-takeaslist-method" data-id="using-the-takeaslist-method">5. Using the <em>takeAsList()</em> Method</h2>
<div class="bd-anchor" id="using-the-takeaslist-method"></div>
<p>Another approach is to retrieve at most one row using <em>takeAsList():</em></p>
<pre><code class="language-">Dataset&lt;Row&gt; englandPlayers = allPlayers.filter(col("country").equalTo("England"));
Assertions.assertEquals(0, englandPlayers.takeAsList(1).size());</code></pre>
<p>Here we called the <em>takeAsList(1)</em>, which returns at most one row. We can then verify that the returned list is empty by checking whether its size is 0. <strong>Since only one row is requested, Spark can stop processing once it finds the first matching row</strong>, making this approach more efficient than counting every row.</p>
<p>Alternatively, we can also check if the list is empty instead of <em>size(</em>):</p>
<pre><code class="language-">Assertions.assertTrue(englandPlayers.takeAsList(1).isEmpty());</code></pre>
<h2 id="bd-comparing-methods-for-performance" data-id="comparing-methods-for-performance">6. Comparing Methods for Performance</h2>
<div class="bd-anchor" id="comparing-methods-for-performance"></div>
<p>Although all of these approaches can determine whether a DataFrame is empty, they don&#8217;t have the same performance characteristics.The <em>isEmpty()</em> method is the recommended approach because it&#8217;s specifically designed for this purpose. Internally, <strong>Spark only needs to determine whether at least one row exists, allowing it to stop processing as soon as a row is found</strong>.</p>
<p>Similarly, <em>takeAsList(1)</em> requests only a single row. Once Spark retrieves the first row, it can terminate the scan, making this approach much more efficient than counting every row. In contrast, <em>count()</em> computes the total number of rows in the DataFrame. Since Spark must process the entire dataset to produce an exact count, this approach is considerably more expensive for large DataFrames.</p>
<p>In general, we should prefer <em>isEmpty()</em> whenever it&#8217;s available. If we&#8217;re working with an older Spark version that doesn&#8217;t support it, <em>takeAsList(1).isEmpty()</em> is a good alternative. We should reserve <em>count()</em> for situations where we also need the total number of rows.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored several ways to determine whether a Spark DataFrame is empty. We started with the <em>isEmpty()</em> method, which is the simplest and most expressive solution, and then looked at alternatives based on <em>count()</em> and <em>takeAsList()</em>.</p>
<p><strong>In most situations, <em>isEmpty()</em> is the preferred choice because it&#8217;s both expressive and efficient</strong>. For older Spark versions, <em>takeAsList(1).isEmpty()</em> provides a practical alternative, while <em>count()</em> is best reserved for cases where the total number of rows is also needed.</p>
<p>As always, the sample code used in this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/apache-spark-2">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spark-dataframe-empty">Check if Spark Dataframe Is Empty</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/965089607/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/965089607/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/965089607/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/965089607/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/965089607/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/965089607/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spark-dataframe-empty#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spark-dataframe-empty/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/965089607/0/baeldung/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/apache-camel-docling-langchain4j</feedburner:origLink>
		<title>Building Intelligent Document Processing with Apache Camel, Docling and LangChain4j</title>
		<link>https://feeds.feedblitz.com/~/964749371/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/964749371/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Olayemi Michael]]></dc:creator>
		<pubDate>Thu, 30 Jul 2026 23:35:25 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Apache Camel]]></category>
		<category><![CDATA[LangChain]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/apache-camel-docling-langchain4j</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="Two co-workers; the one on the left is holding up a magnifying glass to an artistic representation of files and folders. The one on the right is looking at a projection of some Java code. In between them there is an office plant. To the left of the image are the words &quot;Java on Baeldung&quot;" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" /><p>Build an AI-powered document processing system using Apache Camel, Docling, and LangChain4j.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/964749371/0/baeldung">Building Intelligent Document Processing with Apache Camel, Docling and LangChain4j</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964749371/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/964749371/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-14-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/964749371/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/964749371/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/964749371/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/apache-camel-docling-langchain4j#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/apache-camel-docling-langchain4j/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-14-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="Two co-workers; the one on the left is holding up a magnifying glass to an artistic representation of files and folders. The one on the right is looking at a projection of some Java code. In between them there is an office plant. To the left of the image are the words &quot;Java on Baeldung&quot;" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14.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>An LLM can answer questions about documents&#8217; content when they are in the context window. However, many real-world documents are stored in formats such as PDF, which may be difficult for AI applications to process directly. Docling converts these documents into a structured format that can be efficiently parsed.</p>
<p>In this tutorial, we&#8217;ll build a document processing system using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-camel-intro">Apache Camel</a>, Docling, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-langchain-basics">LangChain4j</a>. Also, we&#8217;ll define a simple question-answering API using an embedded Undertow server.</p>
<h2 id="bd-problem-statement" data-id="problem-statement">2. Problem Statement</h2>
<div class="bd-anchor" id="problem-statement"></div>
<p>Many real-world documents contain structural information such as headings, tables, and images, which is difficult to preserve during text extraction. As a result, AI pipelines may lose valuable context, reducing the quality of retrieval and accuracy of generated responses.</p>
<p><strong>A structured representation helps address this problem by preserving the document&#8217;s organization and relationships</strong>. Structured formats such as Markdown, HTML, and JSON retain much of the document&#8217;s semantic structure, making them easier to process and interpret.</p>
<p><strong>IBM Docling bridges this gap by converting unstructured or semi-structured documents into structured representations that downstream AI applications can efficiently consume. </strong>We can store the generated Markdown on disk. Later, an application can load it and include it in the prompt for the LLM. This avoids converting the same document for every request the application processes.</p>
<h2 id="bd-project-setup" data-id="project-setup">3. Project Setup</h2>
<div class="bd-anchor" id="project-setup"></div>
<p>Let&#8217;s bootstrap a Java project using Apache Camel and LangChain4j.</p>
<h3 id="bd-1-maven-dependencies" data-id="1-maven-dependencies">3.1. Maven Dependencies</h3>
<div class="bd-anchor" id="1-maven-dependencies"></div>
<p>First, let&#8217;s add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.camel/camel-core"><em>camel-core</em></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.camel/camel-main"><em>camel-main</em></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/dev.langchain4j/langchain4j"><em>langchain4j</em></a> dependencies to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.camel&lt;/groupId&gt;
    &lt;artifactId&gt;camel-core&lt;/artifactId&gt;
    &lt;version&gt;4.20.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.apache.camel&lt;/groupId&gt;
    &lt;artifactId&gt;camel-main&lt;/artifactId&gt;
    &lt;version&gt;4.20.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;langchain4j&lt;/artifactId&gt;
    &lt;version&gt;1.11.8&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>The <em>camel-core</em> dependency provides classes such as <em>RouteBuilder</em> for defining a camel route using the Java DSL,<em> camel-main</em> provides a lightweight runtime for standalone Camel applications, and <em>langchain4j</em> provides the core APIs for interacting with LLMs.</p>
<p>Next, let&#8217;s add <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.camel/camel-docling"><em>camel-docling</em></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.camel/camel-langchain4j-chat"><em>camel-langchain4j-chat</em></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/dev.langchain4j/langchain4j-open-ai"><em>langchain4j-open-ai</em></a> dependencies to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.camel&lt;/groupId&gt;
    &lt;artifactId&gt;camel-docling&lt;/artifactId&gt;
    &lt;version&gt;4.20.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.apache.camel&lt;/groupId&gt;
    &lt;artifactId&gt;camel-langchain4j-chat&lt;/artifactId&gt;
    &lt;version&gt;4.20.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;langchain4j-open-ai&lt;/artifactId&gt;
    &lt;version&gt;1.11.8&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Here, <em>camel-docling</em> integrates Apache Camel with a Docling server, allowing routes to submit documents for conversion into Docling&#8217;s structured document representation. Also, <em>camel-langchain4j-chat</em> enables Camel routes to send prompts to a LangChain4j chat model and process the generated response. <strong>The <em>langchain4j-open-ai</em> dependency provides the implementation needed to connect LangChain4j to OpenAI-compatible chat models</strong>.</p>
<p>All Apache Camel dependencies, including <em>camel-docling, </em>use the same version to ensure compatibility and avoid unexpected runtime errors.</p>
<p>Let&#8217;s also add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.camel/camel-undertow"><em>camel-undertow</em></a> dependency to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.camel&lt;/groupId&gt;
    &lt;artifactId&gt;camel-undertow&lt;/artifactId&gt;
    &lt;version&gt;4.20.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>It enables Camel to expose HTTP endpoints using an embedded <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jboss-undertow">Undertow server</a>.</p>
<p>Finally, let&#8217;s create a directory named <em>document</em> in the project&#8217;s root directory and place the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/libraries-llms-2/document/policies.pdf"><em>policies.pdf</em> </a>file inside it.</p>
<h3 id="bd-2-docker-compose-file" data-id="2-docker-compose-file">3.2. Docker Compose File</h3>
<div class="bd-anchor" id="2-docker-compose-file"></div>
<p>The easiest way to run Docling locally is by using <em>docling-serve</em>, which exposes Docling&#8217;s document conversion capabilities through a REST API. For simplicity, we run <em>docling-serve</em> in a Docker container.</p>
<p>First, let&#8217;s create a new file <em>docker-compose.yml </em>in our project root directory and add the following configuration:</p>
<pre><code class="language-yaml">services:
  docling-serve:
    image: quay.io/docling-project/docling-serve:latest
    container_name: docling-serve
    ports:
      - "5001:5001"
    environment:
      - DOCLING_STORAGE_DIR=/data
      - DOCLING_PORT=5001
    volumes:
      - ./data/docling:/data
    restart: unless-stopped</code></pre>
<p>The configuration starts a <em>docling-serve</em> container from the <em>quay.io/docling-project/docling-serve</em> image. If the image isn&#8217;t available locally, Docker automatically pulls it from the registry. It also maps port <em>5001</em> on the host to port <em>5001</em> inside the container, allowing our application to communicate with the Docling server.</p>
<p>Finally, let&#8217;s start the Docling server by running the following command:</p>
<pre><code class="language-bash">$ docker compose up</code></pre>
<h3 id="bd-3-application-entry-point" data-id="3-application-entry-point">3.3. Application Entry Point</h3>
<div class="bd-anchor" id="3-application-entry-point"></div>
<p>Let&#8217;s create a <em>CamelDoclingApplication</em> class that serves as our application entry point:</p>
<pre><code class="language-java">public class CamelDoclingApplication {
    public static void main(String[] args) throws Exception {
        OpenAiChatModel model = OpenAiChatModel.builder()
          .baseUrl("http://langchain4j.dev/demo/openai/v1")
          .apiKey("demo")
          .modelName("gpt-4o-mini")
          .build();
        Main main = new Main();
        main.bind("chatModel", model);
        main.run(args);
    }
}</code></pre>
<p>We create an <em>OpenAiChatModel</em> object configured to use the <em>gpt-4o-mini</em> model. Although OpenAI models normally require a valid API key, LangChain4j provides a public demo endpoint and API key for experimentation. We configure the client to use this endpoint by setting the <em>baseUrl</em> to the LangChain4j demo service. Alternatively, we could configure LangChain4j to communicate with a locally hosted model through Ollama or replace the demo endpoint with our own OpenAI API credentials.</p>
<p>The <em>Main</em> instance provides a lightweight runtime for standalone Camel applications. We bind the chat model to Camel&#8217;s registry under the name <em>chatModel</em>, that Camel components and routes can reference during message processing.</p>
<h2 id="bd-converting-a-pdf-document-to-markdown" data-id="converting-a-pdf-document-to-markdown">4. Converting a PDF Document to Markdown</h2>
<div class="bd-anchor" id="converting-a-pdf-document-to-markdown"></div>
<p>Now that the Docling server is running, let&#8217;s create our first Camel route to convert <em>policies.pdf</em> into Markdown:</p>
<pre><code class="language-java">class ConversionRoute extends RouteBuilder {
    @Override
    public void configure() {
        from("file:documents?include=.*\\.(pdf|docx|pptx|html|md)&amp;noop=true&amp;idempotent=true")
          .routeId("document-analysis-workflow")
          .log("Processing document: ${header.CamelFileName}")
          .to("docling:CONVERT_TO_MARKDOWN?useDoclingServe=true&amp;doclingServeUrl=http://localhost:5001&amp;contentInBody=true")
          .setProperty("convertedMarkdown", body())
          .setHeader(Exchange.FILE_NAME, simple("${header.CamelFileName.replaceFirst('\\.[^.]+$', '')}.md"))
          .to("file:output") 
    }
}</code></pre>
<p>Here, we monitor the documents directory for supported document types. The <em>noop=true</em> option leaves the original file in place after processing, while <em>idempotent=true</em> prevents the same file from being processed more than once. Note that the Camel <em>file</em> component already provides the information that the Docling component needs to process the document. Although we could explicitly replace the message body with the document&#8217;s path, doing so would be redundant because the conversion works correctly without it.</p>
<p>Next, <strong>the <em>docling:CONVERT_TO_MARKDOWN </em>endpoint sends the document to the locally running <em>docling-serve</em> instance, which converts it into Markdown and returns the generated content in the message body</strong>.</p>
<p>Finally, we store the converted Markdown as an exchange property, rename the output file by replacing its original extension with <em>.md</em>, and use the Camel file component to write it in the output directory.</p>
<p><strong>If there are multiple documents in the <em>documents</em> directory, Camel processes each one independently and generates a corresponding Markdown file in the <em>output</em> directory.</strong></p>
<p>Let&#8217;s run our application by registering <em>ConversionRoute</em> with the Camel runtime:</p>
<pre><code class="language-java">Main main = new Main();
main.bind("chatModel", model);
main.configure()
  .addRoutesBuilder(new ConversionRoute());
main.run(args);</code></pre>
<p>Here, we register the <em>ConversionRoute</em> with Camel through the <em>Main</em> instance.</p>
<h2 id="bd-passing-the-markdown-document-to-langchain4j" data-id="passing-the-markdown-document-to-langchain4j">5. Passing the Markdown Document to LangChain4j</h2>
<div class="bd-anchor" id="passing-the-markdown-document-to-langchain4j"></div>
<p>Now that we&#8217;ve converted the document to Markdown, let&#8217;s send it to our chat model and ask it to analyze the document:</p>
<pre><code class="language-java">class ConversionRoute extends RouteBuilder {
    @Override
    public void configure() {
        from("file:documents?include=.*\\.(pdf|docx|pptx|html|md)&amp;noop=true&amp;idempotent=true")
          .routeId("document-analysis-workflow")
          // ...
          .setBody(simple("""
              You are a helpful document analysis assistant. Please analyze
              the following document and provide:
              1. A brief summary (2-3 sentences)
              2. Key topics and main points
              3. Any important findings or conclusions
              Document content:
              ${exchangeProperty.convertedMarkdown}
              """))
          .to("langchain4j-chat:analysis?chatModel=#chatModel")
          .setHeader(
              Exchange.FILE_NAME,
              simple("${header.CamelFileName.replaceFirst('\\.[^.]+$', '')}-analysis.md")
          )
          .to("file:analysis");
    }
}</code></pre>
<p>In this route, we construct a prompt that includes the Markdown generated by Docling and instruct the model to summarize and analyze the document. The <em>${exchangeProperty.convertedMarkdown}</em> expression inserts the Markdown stored earlier in the route into the prompt before it&#8217;s sent to the model.</p>
<p>Next, <strong>the <em>langchain4j-chat</em> component sends the prompt to the chat model referenced by <em>#chatModel</em></strong>. Camel resolves this reference from its registry, where we previously bound the <em>OpenAiChatModel</em> instance. The <em>analysis </em>in <em>langchain4j-chat:analysis </em>is the endpoint name. It&#8217;s primarily used to distinguish this endpoint from others.</p>
<p>Finally, we rename the output file by appending <em>-analysis</em> to the original filename and use the File component to write the model&#8217;s response to the <em>analysis</em> directory.</p>
<h2 id="bd-interactive-qampa-api" data-id="interactive-qampa-api">6. Interactive Q&amp;A API</h2>
<div class="bd-anchor" id="interactive-qampa-api"></div>
<p>Now, it&#8217;s time to define a question-answering API that uses the converted Markdown document as its knowledge source.</p>
<p>Let&#8217;s create a Camel route that accepts a question through an HTTP endpoint:</p>
<pre><code class="language-java">public class QuestionAndAnswerRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        from("undertow:http://0.0.0.0:8080/api/ask?httpMethodRestrict=POST").routeId("document-qa-api")
          .setProperty("question", bodyAs(String.class))
          .pollEnrich("file:output?fileName=policies.md&amp;noop=true&amp;idempotent=false")
          .setProperty("markdown", bodyAs(String.class))
          .setBody(simple("""
              You are a helpful document assistant.
              Answer ONLY using the document below.
              DOCUMENT
              =========
              ${exchangeProperty.markdown}
              QUESTION
              =========
              ${exchangeProperty.question}
              """))
          .to("langchain4j-chat:analysis?chatModel=#chatModel")
          .setHeader(Exchange.CONTENT_TYPE, constant("text/plain"));
    }
}
</code></pre>
<p>What happens here?</p>
<p>We expose the POST <em>/api/ask</em> endpoint through the embedded Undertow server. The request body contains the user&#8217;s question, which we store as an exchange property before replacing the message body later in the route.</p>
<p>The <em>pollEnrich</em> Enterprise Integration Pattern (EIP) reads the previously generated Markdown document from the <em>output</em> directory and places its contents into the current exchange. Rather than converting the PDF each time a request arrives, this route reads the Markdown file. This keeps the HTTP request lightweight by reusing the preprocessed document instead of invoking Docling for each question.</p>
<p>Also, we set <em>idempotent=false</em> so the File component can read the same Markdown file on every API request. By default, <em>noop=true</em> enables <em>idempotent=true</em>, which prevents the same file from being consumed more than once.</p>
<p>Then, we construct a prompt containing both the document and the user&#8217;s question before sending it to the <em>langchain4j-chat</em> component.</p>
<p>Finally, the model&#8217;s response is returned as the HTTP response body with a <em>text/plain</em> content type.</p>
<p>Let&#8217;s update our application entry point to register the <em>QuestionAndAnswerRoute</em>:</p>
<pre><code class="language-java">// ...
main.configure()
  .addRoutesBuilder(new QuestionAndAnswerRoute());
// ...</code></pre>
<p>Once the application is up,  we can run the following <em>curl</em> command to send a question to the API:</p>
<pre><code class="language-bash">curl -X POST \
  http://localhost:8080/api/ask \
  -H "Content-Type: text/plain" \
  -d "How many lifecycle stages does a task move through?"</code></pre>
<p>Here&#8217;s an API response we can expect:</p>
<pre><code class="language-bash">A task moves through four lifecycle stages: PENDING, IN_PROGRESS, REVIEW, and DONE.</code></pre>
<p>The API successfully answers the question based on the document we supplied.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we learned how to integrate Apache Camel, Docling, and LangChain4j to build an intelligent document processing application. We used Docling to convert an unstructured document into a structured representation, orchestrated the conversion workflow with Apache Camel, and analyzed the converted content using an LLM through LangChain4j.</p>
<p>Additionally, we exposed a simple question-answer API that uses the converted document as context to generate grounded responses.</p>
<p>By converting documents into a structured representation once and reusing the generated Markdown for subsequent interactions, we simplify document processing workflows, avoid repeated conversions, and make it easier for LLMs to process the document content.</p>
<p>As always, the source code for the examples is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/libraries-llms-2">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-camel-docling-langchain4j">Building Intelligent Document Processing with Apache Camel, Docling and LangChain4j</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/964749371/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964749371/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/964749371/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-14-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/964749371/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/964749371/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/964749371/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/apache-camel-docling-langchain4j#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/apache-camel-docling-langchain4j/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/~/964749371/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/hibernate-common-table-expressions</feedburner:origLink>
		<title>CTE Support in Hibernate</title>
		<link>https://feeds.feedblitz.com/~/964749374/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/964749374/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Graham Cox]]></dc:creator>
		<pubDate>Thu, 30 Jul 2026 23:28:05 +0000</pubDate>
				<category><![CDATA[Persistence]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[HQL]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/hibernate-common-table-expressions</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 Common Table Expressions in Hibernate to help simplify writing complex queries by using intermediate named HQL result sets.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/964749374/0/baeldung">CTE Support in Hibernate</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964749374/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/964749374/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/964749374/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/964749374/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/964749374/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-common-table-expressions#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-common-table-expressions/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-introduction" data-id="introduction"><strong>1. Introduction</strong></h2>
<div class="bd-anchor" id="introduction"></div>
<p>In this article, we’re going to look at how to use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/sql/common-table-expressions">Common Table Expressions</a> (CTE) in Hibernate. We’ll see what a CTE is, what they&#8217;re used for, and how to use them in Hibernate queries.</p>
<p>Note that the introduction of CTE support is specific to Hibernate and HQL. This is not part of the standard JPA specification, and alternative implementations may not support CTE queries at all, or may support them in different ways.</p>
<h2 id="bd-what-are-common-table-expressions" data-id="what-are-common-table-expressions"><strong>2. What are Common Table Expressions</strong></h2>
<div class="bd-anchor" id="what-are-common-table-expressions"></div>
<p><strong>A Common Table Expression, or CTE, is a construct we can use within our queries to create a named result set that we can then refer to from within our main query.</strong></p>
<p>At their simplest, we can treat these as equivalent to subqueries or joins:</p>
<pre><code class="language-sql">-- Subquery
SELECT customer_id, total_spent
FROM (
    SELECT customer_id, SUM(amount) AS total_spent
    FROM orders
    GROUP BY customer_id
) customer_totals
WHERE total_spent &gt; (
    SELECT AVG(total_spent)
    FROM (
        SELECT customer_id, SUM(amount) AS total_spent
        FROM orders
        GROUP BY customer_id
    ) customer_totals
);
-- CTE
WITH customer_totals AS (
    SELECT customer_id, SUM(amount) AS total_spent
    FROM orders
    GROUP BY customer_id
)
SELECT customer_id, total_spent
FROM customer_totals
WHERE total_spent &gt; ( SELECT AVG(total_spent) FROM customer_totals );</code></pre>
<p>These two SQL statements do the same thing. They each return the total spend of all customers who have spent more than the average amount. However, the CTE version works by defining a name, <em>customer_totals, </em>for our subquery and then allowing us to reference that name whenever we want. This is both easier to understand and more efficient for the database to execute.</p>
<h2 id="bd-setting-up" data-id="setting-up"><strong>3. Setting Up</strong></h2>
<div class="bd-anchor" id="setting-up"></div>
<p>Now that we know what CTEs are, we&#8217;re ready to start using them. Before we can do this, we need to do a little bit of setting up.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies"><strong>3.1. Dependencies</strong></h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p><strong>To use CTEs in <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core">Hibernate</a>, we need to use version 6.2 or newer. The newest release at the time of writing is <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core/7.4.4.Final">7.4.4.Final</a>:</strong></p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.hibernate.orm&lt;/groupId&gt;
    &lt;artifactId&gt;hibernate-core&lt;/artifactId&gt;
    &lt;version&gt;7.4.4.Final&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>If we&#8217;re getting Hibernate via Spring Boot, this means Spring Boot version 3.1 or newer is needed:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<p>Now we have our dependencies, let&#8217;s next consider what data we&#8217;ll be querying.</p>
<h3 id="bd-2-database" data-id="2-database"><strong>3.2. Database</strong></h3>
<div class="bd-anchor" id="2-database"></div>
<p><strong>For this article, we need to set up a database.</strong> Our table will look as follows:</p>
<pre><code class="language-sql">CREATE TABLE employee (
    id         BIGINT PRIMARY KEY,
    name       VARCHAR(100)   NOT NULL,
    title      VARCHAR(100)   NOT NULL,
    department VARCHAR(50)    NOT NULL,
    salary     NUMERIC(10, 2) NOT NULL,
    manager_id BIGINT REFERENCES employee (id)
);
</code></pre>
<p>Note that the <em>manager_id </em>column references back to the same <em>employee </em>table. This allows us to store a hierarchical structure in a single table.</p>
<p>We then need some data in our database:</p>
<table style="height: 312px;width: 100%;border-collapse: collapse">
<tbody>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>ID</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none"><strong>Name</strong></td>
<td style="width: 26.2009%;height: 24px;border-style: none"><strong>Title</strong></td>
<td style="width: 14.9563%;height: 24px;border-style: none"><strong>Department</strong></td>
<td style="width: 10.0437%;height: 24px;border-style: none"><strong>Salary</strong></td>
<td style="width: 15.5022%;height: 24px;border-style: none"><strong>Manager</strong></td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>1</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Alice Turner</td>
<td style="width: 26.2009%;height: 24px;border-style: none">CEO</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Executive</td>
<td style="width: 10.0437%;height: 24px;border-style: none">220,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">—</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>2</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Ben Rodgers</td>
<td style="width: 26.2009%;height: 24px;border-style: none">VP Engineering</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Engineering</td>
<td style="width: 10.0437%;height: 24px;border-style: none">180,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">Alice Turner (1)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>3</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Carla Nunes</td>
<td style="width: 26.2009%;height: 24px;border-style: none">VP Sales</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Sales</td>
<td style="width: 10.0437%;height: 24px;border-style: none">175,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">Alice Turner (1)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>4</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">David Kim</td>
<td style="width: 26.2009%;height: 24px;border-style: none">Engineering Manager</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Engineering</td>
<td style="width: 10.0437%;height: 24px;border-style: none">140,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">Ben Rodgers (2)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>5</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Ella Novak</td>
<td style="width: 26.2009%;height: 24px;border-style: none">Engineering Manager</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Engineering</td>
<td style="width: 10.0437%;height: 24px;border-style: none">138,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">Ben Rodgers (2)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>6</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Frank Osei</td>
<td style="width: 26.2009%;height: 24px;border-style: none">Sales Manager</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Sales</td>
<td style="width: 10.0437%;height: 24px;border-style: none">120,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">Carla Nunes (3)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>7</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Grace Lin</td>
<td style="width: 26.2009%;height: 24px;border-style: none">Senior Engineer</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Engineering</td>
<td style="width: 10.0437%;height: 24px;border-style: none">115,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">David Kim (4)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>8</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Hugo Alvarez</td>
<td style="width: 26.2009%;height: 24px;border-style: none">Software Engineer</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Engineering</td>
<td style="width: 10.0437%;height: 24px;border-style: none">95,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">David Kim (4)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>9</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Ivy Chen</td>
<td style="width: 26.2009%;height: 24px;border-style: none">Software Engineer</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Engineering</td>
<td style="width: 10.0437%;height: 24px;border-style: none">98,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">Ella Novak (5)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>10</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Jack Meyer</td>
<td style="width: 26.2009%;height: 24px;border-style: none">Sales Rep</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Sales</td>
<td style="width: 10.0437%;height: 24px;border-style: none">80,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">Frank Osei (6)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>11</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Kara Diaz</td>
<td style="width: 26.2009%;height: 24px;border-style: none">Sales Rep</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Sales</td>
<td style="width: 10.0437%;height: 24px;border-style: none">82,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">Frank Osei (6)</td>
</tr>
<tr style="height: 24px">
<td style="width: 16.6667%;height: 24px;border-style: none"><strong>12</strong></td>
<td style="width: 16.4847%;height: 24px;border-style: none">Liam Foster</td>
<td style="width: 26.2009%;height: 24px;border-style: none">Junior Engineer</td>
<td style="width: 14.9563%;height: 24px;border-style: none">Engineering</td>
<td style="width: 10.0437%;height: 24px;border-style: none">75,000</td>
<td style="width: 15.5022%;height: 24px;border-style: none">Grace Lin (7)</td>
</tr>
</tbody>
</table>
<p>This gives us a set of employees that make a tree structure, rooted at &#8220;Alice&#8221;.</p>
<h3 id="bd-2-jpa-entity" data-id="2-jpa-entity"><strong>3.2. JPA Entity</strong></h3>
<div class="bd-anchor" id="2-jpa-entity"></div>
<p><strong>Finally, since we&#8217;re going to be working with Hibernate, we also need a Hibernate entity to represent our data:</strong></p>
<pre><code class="language-java">@Entity
@Table(name = "employee")
public class Employee {
    @Id
    private Long id;
    private String name;
    private String title;
    private String department;
    private BigDecimal salary;
    @ManyToOne
    @JoinColumn(name = "manager_id")
    private Employee manager;
}</code></pre>
<p>The <em>Employee</em> entity gives us access to all of our data. <strong>As with our table structure, we&#8217;ve also got our reference back to the same entity.</strong> This time it&#8217;s in the form of a <em>manager </em>field that gives us the complete row.</p>
<h2 id="bd-basic-ctes-in-hibernate" data-id="basic-ctes-in-hibernate"><strong>4. Basic CTEs in Hibernate</strong></h2>
<div class="bd-anchor" id="basic-ctes-in-hibernate"></div>
<p>Now that we&#8217;ve got our database and entity, we&#8217;re ready to query it.</p>
<p><strong>As with SQL, Hibernate allows us to use the <em>WITH </em>clause in our HQL queries. This is used to define a new named result set that we can treat as any table or view within our main query.</strong></p>
<p>For example:</p>
<pre><code class="language-java">List&lt;Employee&gt; employees = session.createQuery("""
    WITH engineers AS (
        SELECT e.id AS id
        FROM Employee e
        WHERE e.department = 'Engineering'
    )
    SELECT e
    FROM Employee e
    WHERE e.id IN (SELECT id FROM engineers)
    AND e.salary &gt; 100000
    ORDER BY e.name ASC
""", Employee.class).getResultList();
</code></pre>
<p>We can consider this in two parts. Our <em>WITH </em>clause generates a new result set named <em>engineers </em>consisting of all of the IDs of employees in the &#8220;Engineering&#8221; department. Our main query then uses this in a sub-select to only return the <em>Employee </em>records that match.</p>
<p><strong>Note that the return type from our <em>WITH </em>clause is always an <em>AnonymousTupleType</em>.</strong>This means that we can only include individual values in it, and not entire entities. We also need to give every value a distinct name with <em>AS</em>. We can then refer to these values by name in the main query.</p>
<p>Here, the <em>engineers </em>result set contains IDs 2, 4, 5, 7, 8, 9, and 12. Our <em>IN </em>clause then matches only those when selecting <em>Employee </em>entities, before further filtering by salary, to give an overall result of &#8220;Ben&#8221;, &#8220;David&#8221;, &#8220;Ella&#8221; and &#8220;Grace&#8221;.</p>
<p>We can also make our <em>WITH </em>clause more complicated, allowing us to resolve this complexity once and refer to it as needed in our main query:</p>
<pre><code class="language-java">List&lt;Employee&gt; employees = session.createQuery("""
    WITH dept_avg AS (
        SELECT e.department AS department, AVG(e.salary) AS avgSalary
        FROM Employee e
        GROUP BY e.department
    )
    SELECT e
    FROM Employee e
    JOIN dept_avg d ON e.department = d.department
    WHERE e.salary &gt; d.avgSalary
    ORDER BY e.department, e.salary DESC
""", Employee.class).getResultList();
</code></pre>
<p>This time, our <em>WITH </em>clause is an aggregate query that will calculate the average salary for each department. On a very large table, this could be expensive. Writing it as a CTE separates the calculation from the main query, making the query easier to read and maintain. <strong>Depending on the database and its optimizer, the CTE may be materialized once or inlined into the main query, whichever produces the most efficient execution plan.</strong></p>
<p>Here, our <em>dept_avg </em>result set will be as follows:</p>
<table style="border-collapse: collapse;width: 100%">
<tbody>
<tr>
<td style="width: 50%"><strong>department</strong></td>
<td style="width: 50%"><strong>avgSalary</strong></td>
</tr>
<tr>
<td style="width: 50%">CEO</td>
<td style="width: 50%">220,000</td>
</tr>
<tr>
<td style="width: 50%">Engineering</td>
<td style="width: 50%">120,142.85714286</td>
</tr>
<tr>
<td style="width: 50%">Sales</td>
<td style="width: 50%">114,250</td>
</tr>
</tbody>
</table>
<p>Our main query can then join to this by name, as if it were a table or view in the database. This then allows us to combine the data from our <em>dept_avg </em>result set with the data from our main <em>Employee </em>entity to match the rows that we&#8217;re interested in. In this case, &#8220;Ben&#8221;, &#8220;David&#8221;, &#8220;Ella&#8221;, &#8220;Carla&#8221; and &#8220;Frank&#8221;.</p>
<h2 id="bd-recursive-ctes" data-id="recursive-ctes"><strong>5. Recursive CTEs</strong></h2>
<div class="bd-anchor" id="recursive-ctes"></div>
<p><strong>The place that CTEs can become really powerful is when we want to do any graph-based queries. We can write our <em>WITH </em>clause to be recursive.</strong> We do this by splitting it into two parts with a <em>UNION</em>, and can then reference back to the same named <em>WITH </em>clause from within this second part:</p>
<p>For example, let&#8217;s start with a simple CTE query:</p>
<pre><code class="language-java">List&lt;Employee&gt; employees = session.createQuery("""
    WITH subordinates AS (
        SELECT e.id AS id FROM Employee e WHERE e.manager.id = 2
    )
    SELECT e
    FROM Employee e
    WHERE e.id IN (SELECT s.id FROM subordinates s)
    ORDER BY e.department, e.name
    """, Employee.class).getResultList();
</code></pre>
<p>On its own, this is an overly complicated way to return every <em>Employee </em>who has a manager ID of &#8220;2&#8221;. Running this as-is will return &#8220;David&#8221; and &#8220;Ella&#8221;.</p>
<p>However, we can extend the <em>WITH </em>clause to be recursive as follows:</p>
<pre><code class="language-java">List&lt;Employee&gt; employees = session.createQuery("""
    WITH subordinates AS (
        SELECT e.id AS id FROM Employee e WHERE e.manager.id = 2
    
        UNION
    
        SELECT e2.id AS id FROM Employee e2 JOIN subordinates s ON e2.manager.id = s.id
    )
    SELECT e
    FROM Employee e
    WHERE e.id IN (SELECT s.id FROM subordinates s)
    ORDER BY e.department, e.name
    """, Employee.class).getResultList();
</code></pre>
<p>All we&#8217;ve added here is the <em>UNION </em>and the second part of our <em>WITH </em>clause. <strong>This now selects every <em>Employee </em>whose manager ID is either &#8220;2&#8221; or is part of the already built list of IDs.</strong> This will then return &#8220;David&#8221;, &#8220;Ella&#8221;, &#8220;Grace&#8221;, &#8220;Hugo&#8221;, &#8220;Ivy&#8221; and &#8220;Liam&#8221;.</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/cte-employees.png"><img loading="lazy" decoding="async" class="alignnone wp-image-255575 size-large" src="https://www.baeldung.com/wp-content/uploads/2026/07/cte-employees-1024x783.png" alt="A hierarchical tree diagram showing nine numbered people connected by parent-child relationships. At the top is Alice (1). Alice branches to Ben (2) on the left and Carla (3) on the right. Ben has two children: David (4) and Ella (5). David has two children, Grace (7) and Hugo (8). Grace has one child, Liam (12). Ella has one child, Ivy (9). Carla has one child, Frank (6). The nodes for David (4), Ella (5), Grace (7), Hugo (8), Ivy (9), and Liam (12) are shaded light green, while Alice (1), Ben (2), Carla (3), and Frank (6) have white backgrounds. Arrows point from each parent to their children." width="580" height="443" /></a>
<p>This query even reached as far as &#8220;Liam&#8221;, who is 3 steps away from our original ID, and it would continue as far as the data allowed it.</p>
<p>Note that by using <em>UNION </em>instead of <em>UNION ALL</em>, if we ever encounter a situation where the data is cyclical, we&#8217;ll only return each unique row once.</p>
<h2 id="bd-summary" data-id="summary"><strong>6. Summary</strong></h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we&#8217;ve had a very brief look at using CTEs in Hibernate. We&#8217;ve seen what they are, how they work, and how we can use them. Next time you need queries like this, why not give it a go?</p>
<p>As always, all the code from this article is available over on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate-queries-2">GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-common-table-expressions">CTE Support in Hibernate</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/964749374/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964749374/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/964749374/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/964749374/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/964749374/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/964749374/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-common-table-expressions#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-common-table-expressions/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/~/964749374/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/llm-integration-apache-camel-openai-component</feedburner:origLink>
		<title>LLM Integration With Apache Camel OpenAI Component</title>
		<link>https://feeds.feedblitz.com/~/964742375/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/964742375/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Burak Gökmen]]></dc:creator>
		<pubDate>Thu, 30 Jul 2026 23:25:13 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[OpenAI]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204320</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn about LLM integration with the Apache Camel OpenAI component with examples.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/964742375/0/baeldung">LLM Integration With Apache Camel OpenAI Component</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964742375/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/964742375/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/964742375/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/964742375/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/964742375/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/llm-integration-apache-camel-openai-component#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/llm-integration-apache-camel-openai-component/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://camel.apache.org/components/4.18.x/openai-component.html">The Apache Camel OpenAI component</a>, introduced in Camel 4.17, enables integration of Java applications with OpenAI or OpenAI-compatible LLM (Large Language Model) servers such as Ollama or vLLM. Basically, it uses Camel routes for interaction with LLMs and handles message conversion between Camel Exchanges and OpenAI requests. It uses <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-openai-api-client">the OpenAI Java API library</a> under the hood.</p>
<p>In this tutorial, we’ll discuss LLM integration with the Apache Camel OpenAI component and see several examples.</p>
<h2 id="bd-maven-dependencies" data-id="maven-dependencies">2. Maven Dependencies</h2>
<div class="bd-anchor" id="maven-dependencies"></div>
<p>We have to add the following <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.camel/camel-openai">Maven dependency</a> to our <em>pom.xml</em>:</p>
<pre><code class="language-plaintext">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.camel&lt;/groupId&gt;
    &lt;artifactId&gt;camel-openai&lt;/artifactId&gt;
    &lt;version&gt;4.18.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-basic-interaction-with-an-llm" data-id="basic-interaction-with-an-llm">3. Basic Interaction With an LLM</h2>
<div class="bd-anchor" id="basic-interaction-with-an-llm"></div>
<p><strong>The component uses the standard Camel URI (Uniform Resource Identifier) format: <em>openai:operation[?options]</em></strong>. The <em>openai</em> part of the URI is fixed.</p>
<p>The <em>operation</em> part corresponds to the URI’s path parameter. <strong>Two operations are supported: <em>chat-completion</em> and <em>embeddings</em></strong>. <em>chat-completion</em>, which is of interest to us, generates chat responses using a model. The other operation, <em>embeddings</em>, is useful for creating vector embeddings, for example, in <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/retrieval-augmented-generation">RAG (Retrieval-Augmented Generation)</a> pipelines.</p>
<p>The <em>options</em> part corresponds to the URI’s query parameters. These are generally configuration options such as model choices, generation settings, and authentication parameters. For example, the <em>model</em> option specifies which LLM to use. We can also pass these options using message headers instead of the URI.</p>
<h3 id="bd-1-chat-application" data-id="1-chat-application">3.1. Chat Application</h3>
<div class="bd-anchor" id="1-chat-application"></div>
<p>In this section, we’ll examine a simple chat application that interacts with an LLM. The application sends a prompt to the model and displays the model’s response.</p>
<p>Let&#8217;s start by using the Apache Camel’s <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://camel.apache.org/components/4.18.x/others/main.html"><em>Main</em></a> class to configure, run, and handle the lifecycle of a standalone Apache Camel application:</p>
<pre><code class="language-java">Main main = new Main();
main.configure()
  .addRoutesBuilder(new ChatRoute());
main.start();
</code></pre>
<p>The <em>addRoutesBuilder()</em> method registers a custom routing class, <em>ChatRoute</em>, that implements the routing rules. <strong>The routing logic includes how Apache Camel takes a prompt, wraps it in an HTTP call to an LLM, and processes the response</strong>. The <em>start()</em> method of the <em>Main</em> class starts the runtime, instantiates the routes, and begins processing prompts.</p>
<p>Then, let&#8217;s send a message to an endpoint using the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://camel.apache.org/manual/producertemplate.html"><em>ProducerTemplate</em></a> class:</p>
<pre><code class="language-java">ProducerTemplate template = main.getCamelContext()
    .createProducerTemplate();
String aiResponse = template.requestBody("direct:startChat", userPrompt, String.class);
</code></pre>
<p>We use the <em>requestBody()</em> method of <em>ProducerTemplate</em> to pass the prompt to the route. The first argument, <em>&#8220;direct:startChat&#8221;</em>, is the endpoint that the LLM exposes. <strong><em>direct:</em> is an Apache Camel component providing synchronous invocation of routes</strong>. <em>startChat</em> is the name we give to the endpoint.</p>
<p>Our application uses this endpoint to communicate with the LLM. We pass the second argument, <em>userPrompt</em>, to the LLM and store its response in the variable <em>aiResponse</em>. The third argument, <em>String.class</em>, tells Apache Camel that we expect the response to be of type <em>String</em>. Therefore, the type of <em>aiResponse</em> is <em>String</em>.</p>
<h3 id="bd-2-the-routing-class" data-id="2-the-routing-class">3.2. The Routing Class</h3>
<div class="bd-anchor" id="2-the-routing-class"></div>
<p>Next, let’s discuss the routing class. <strong>We extend the <em>ChatRoute</em> class from the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://camel.apache.org/manual/route-builder.html"><em>RouteBuilder</em></a> class to create routing rules using the Java <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-integration-java-dsl">DSL (Domain-Specific Language)</a></strong>:</p>
<pre><code class="language-java">public class ChatRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        onException(Exception.class).handled(true)
          .log(org.apache.camel.LoggingLevel.INFO, "OpenAI Error: ${exception.message}")
          .setBody(simple("OpenAI request failed."));
        from("direct:startChat")
          .setHeader(
            OpenAIConstants.SYSTEM_MESSAGE,
            constant("You are a helpful, brief IT support assistant."))
          .setHeader(OpenAIConstants.TEMPERATURE, constant(0.1))
          .setHeader(OpenAIConstants.MODEL, constant("gpt-4o-mini"))
          .to("openai:chat-completion")
          .log("AI Response: ${body}");
    }
}
</code></pre>
<p>We override the <em>configure()</em> method, which acts as the entry point. <strong><em>from(&#8220;direct:startChat&#8221;)</em> defines a synchronous endpoint named <em>startChat</em></strong>. As we saw in the previous subsection, applications can send user prompts to this endpoint by calling <em>template.requestBody(&#8220;direct:startChat&#8221;, userPrompt, String.class)</em>.</p>
<p>The three <em>setHeader()</em> calls attach OpenAI-specific message headers by injecting the following key-value pairs:</p>
<ul>
<li><em>SYSTEM_MESSAGE</em> sets the system prompt to <em>&#8220;You are a helpful, brief IT support assistant.&#8221;</em> and therefore defines the behavior and tone of the model.</li>
<li><em>MODEL</em> picks <em>gpt-4o-mini</em> as the target LLM.</li>
<li><em>TEMPERATURE</em> sets the sampling temperature to <em>1</em>, thereby making the model more deterministic.</li>
</ul>
<p><strong><em>to(&#8220;openai:chat-completion&#8221;)</em></strong><strong> is the actual call to the OpenAI component</strong>. It extracts the message body (i.e., the user prompt) and the headers, performs the OpenAI chat completion request, and waits for the response.</p>
<p>Instead of using message headers above, we can also use the Camel URI. For example, we can set the <em>model</em> and <em>temperature</em> options using the URI:</p>
<pre><code class="language-java">.to("openai:chat-completion?model=gpt-4o-mini&amp;temperature=0.1")</code></pre>
<p>Finally,<em> log(&#8220;AI Response: ${body}&#8221;)</em> prints the model’s response.</p>
<p><strong>We must authenticate our application while making requests to OpenAI’s developer platform</strong>. One method for authentication is to set the confidential OpenAI API key to the <em>OPENAI_API_KEY</em> environment variable from the terminal we’ll run the application in Linux:</p>
<pre><code class="language-bash">$ export OPENAI_API_KEY=&lt;our_confidential_actual_key_here&gt;</code></pre>
<p>Let&#8217;s see the response of the LLM when we send the prompt, <em>&#8220;Give me information about Baeldung&#8221;</em>:</p>
<pre><code class="language-plaintext">AI Response: Baeldung is a popular online platform that provides tutorials and articles primarily focused on Java, 
Spring Framework, and related technologies.
...
The content is well-structured and often includes practical examples, 
making it a valuable resource for both beginners and experienced developers.</code></pre>
<p>The output shows that we received a successful response to our prompt. We abbreviated the response as it’s long.</p>
<h2 id="bd-using-other-openai-compatible-endpoints" data-id="using-other-openai-compatible-endpoints">4. Using Other OpenAI-Compatible Endpoints</h2>
<div class="bd-anchor" id="using-other-openai-compatible-endpoints"></div>
<p><strong>The Apache Camel OpenAI component allows us to switch easily to other OpenAI-compatible endpoints using the <em>baseUrl</em> option</strong>. The endpoint can point to local or third-party providers. For example, we just need to change the router code as follows to use a local Ollama server:</p>
<pre><code class="language-java">.setHeader(OpenAIConstants.MODEL, constant("qwen3.5:9b"))
.to("openai:chat-completion?baseUrl=http://localhost:11434/v1")
</code></pre>
<p><strong>The default value of <em>baseUrl</em> is </strong><strong><em>https://api.openai.com/v1</em></strong>. We used the default OpenAI endpoint in the previous section. Therefore, we didn’t specify it explicitly. However, by setting <em>baseUrl</em> to <em>http://localhost:11434/v1</em>, we send the requests to the local Ollama server instead of OpenAI’s servers.</p>
<p>We also set the model to <em>qwen3.5:9b</em> in our example. Before sending prompts to the model, the model must have been run locally using the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/linux/genai-ollama-installation#4-installing-and-testing-a-large-language-model"><em>ollama run</em></a> command. We don’t need to use a real API key while using Ollama locally. But an API key is still required. So we can set it to a dummy value for compatibility with OpenAI. But third-party providers over the cloud, like Ollama’s cloud service, require a real API key.</p>
<h2 id="bd-keeping-conversational-memory" data-id="keeping-conversational-memory">5. Keeping Conversational Memory</h2>
<div class="bd-anchor" id="keeping-conversational-memory"></div>
<p><strong>By default, conversational memory isn’t retained by the OpenAI component; i.e., OpenAI doesn’t remember the responses to previous prompts</strong>. For example, if we tell an LLM our name in one prompt and then ask for it in the next prompt, it will usually respond that it doesn&#8217;t know our name.</p>
<p>To maintain conversational memory with the OpenAI component, we need to make two changes to the previous example. Firstly, <strong>we must reuse the same <em>Exchange</em> object on the application side</strong>:</p>
<pre><code class="language-java">Exchange exchange = template.getCamelContext()
  .getEndpoint("direct:startChatWithMemory")
  .createExchange();
for (String prompt : prompts) {
    exchange.getMessage()
      .setBody(prompt);
    template.send("direct:startChatWithMemory", exchange);
                
aiResponses.add(exchange.getMessage()
  .getBody(String.class));
</code></pre>
<p>Once the <em>Exchange</em> object is created, namely <em>exchange</em> in the code snippet above, we use the same object in a <em>for</em> loop for processing the prompts. The <em>prompts</em> variable is a list of strings containing the prompts:</p>
<pre><code class="language-java">List&lt;String&gt; prompts = List.of("My name is Burak and I am a Java developer.", "What is my name and what do I do?"
);
</code></pre>
<p>The first prompt specifies the user’s name and job, and the second prompt asks them.</p>
<p><strong>Secondly, we must set the <em>conversationMemory</em> option to <em>true</em> on the router side</strong>:</p>
<pre><code class="language-java">.to("openai:chat-completion?conversationMemory=true")</code></pre>
<p>When we run the application, the response to the first prompt, <em>&#8220;My name is Burak and I am a Java developer.&#8221;</em>, is as follows:</p>
<pre><code class="language-plaintext">Hi Burak! How can I assist you today with your Java development?</code></pre>
<p>Let&#8217;s see the response to the second prompt, <em>&#8220;What is my name and what do I do?&#8221;</em>:</p>
<pre><code class="language-plaintext">Your name is Burak, and you are involved in Java development. How can I assist you further?</code></pre>
<p>Therefore, reusing the same <em>Exchange</em> object while setting the <em>conversationMemory</em> option to <em>true</em> enables us to persist storage between calls.</p>
<h2 id="bd-providing-structured-output" data-id="providing-structured-output">6. Providing Structured Output</h2>
<div class="bd-anchor" id="providing-structured-output"></div>
<p><strong>The OpenAI component can also present the LLM’s response in a deterministic format</strong>. For example, we can map the response to a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-pojo-class">POJO (Plain Old Java Object)</a>, which can be directed safely to a database. Let’s discuss an example that returns a book’s name, author, and summary:</p>
<pre><code class="language-java">public static BookInfo runChat(String userPrompt) throws Exception {
    ...
    BookInfo bookInfo = template.requestBody("direct:startBookChat", userPrompt, BookInfo.class);
    return bookInfo;
    ...
}
</code></pre>
<p>The <em>runChat()</em> member function in the above code snippet takes a user prompt as a string and passes it to the router by calling the <em>requestBody()</em> method of <em>ProducerTemplate</em>. <strong>The third argument of <em>requestBody()</em>, <em>BookInfo.class</em>, specifies the response to be a <em>BookInfo</em> object</strong>. The <em>BookInfo</em> class consists of three public member variables:</p>
<pre><code class="language-java">public class BookInfo {
    public String name;
    public String author;
    public String summary;
}</code></pre>
<p>We also need to update the router class:</p>
<pre><code class="language-java">from("direct:startBookChat")
  .setHeader(OpenAIConstants.SYSTEM_MESSAGE, constant("You are a helpful, brief book advisor."))
  .setHeader(OpenAIConstants.MODEL, constant("gpt-4o-mini"))
  .setHeader(OpenAIConstants.TEMPERATURE, constant(0.1))
  .to("openai:chat-completion?outputClass=com.baeldung.apachecamel.BookInfo")
  .unmarshal()
  .json(JsonLibrary.Jackson, BookInfo.class)
  .log("AI Response: ${body}");</code></pre>
<p>Here, <strong>we use the <em>outputClass</em> option to instruct the LLM to respond in JSON format that matches the <em>BookInfo</em> schema</strong>. The next calls, <em>unmarhal()</em> and<em> json(JsonLibrary.Jackson, BookInfo.class)</em>, use the Apache Camel JSON Jackson component. They convert the raw JSON string in the message body and deserialize it to <em>BookInfo</em>.</p>
<p>Let&#8217;s see an example of asking for the author and summary of a book, <em>The Time Regulation Institute</em>:</p>
<pre><code class="language-java">String userPrompt = "Can you provide the author and summary of the following book: The Time Regulation Institute";
BookInfo book = runChat(userPrompt);
LOGGER.info("Book: {}", book.name);
LOGGER.info("Author: {}", book.author);
LOGGER.info("Summary: {}", book.summary);</code></pre>
<p>Here is the abbreviated output:</p>
<pre><code class="language-plaintext">Book: The Time Regulation Institute
Author: Ahmet Hamdi Tanpınar
Summary: A satirical novel that explores the absurdities of modernity and the clash between 
tradition and progress in Turkish society, focusing on the establishment of a time regulation 
institute and its impact on the characters' lives.</code></pre>
<p>The output shows that the response was successfully mapped to the Java class <em>BookInfo</em>.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we discussed LLM integration with the Apache Camel OpenAI component.</p>
<p>Firstly, we learned how to send prompts to an LLM using Camel routes and implemented a chat completion application based on them. Then, we saw how to change the OpenAI-compatible endpoint. We also learned to maintain conversational memory so that the model can remember previous prompts throughout a chat. Finally, we discussed formatting an LLM’s responses in a predefined format.</p>
<p>Besides the applications we discussed, there are other use cases. For example, we can process an LLM’s responses chunk by chunk rather than waiting for the full completion of a response. We have to set the <em>streaming</em> option to <em>true</em>. Another example is the support for multi-modal inputs. We can use text files and images with vision-capable models.</p>
<p>As usual, the complete source code for the examples is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/libraries-llms-2">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/llm-integration-apache-camel-openai-component">LLM Integration With Apache Camel OpenAI Component</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/964742375/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964742375/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/964742375/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/964742375/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/964742375/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/964742375/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/llm-integration-apache-camel-openai-component#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/llm-integration-apache-camel-openai-component/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/~/964742375/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-ahead-of-time-cache</feedburner:origLink>
		<title>A Guide to Ahead-of-Time Cache in the Java</title>
		<link>https://feeds.feedblitz.com/~/964742378/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/964742378/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Bhaskar Ghosh]]></dc:creator>
		<pubDate>Thu, 30 Jul 2026 23:21:22 +0000</pubDate>
				<category><![CDATA[JVM]]></category>
		<category><![CDATA[>= Java 26]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204500</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use Ahead-of-Time (AOT) cache to reuse a build's output, so your app can start faster in the subsequent excecutions.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/964742378/0/baeldung">A Guide to Ahead-of-Time Cache in the Java</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964742378/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/964742378/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/964742378/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/964742378/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/964742378/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-ahead-of-time-cache#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-ahead-of-time-cache/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>Every time we start a Java app, the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jvm-series">JVM</a> rebuilds the same classes from scratch, which shows up in the startup time. The Ahead-of-Time (AOT) cache lets us use caching to reuse the build&#8217;s output, so later runs start faster. In this tutorial, we&#8217;ll use the AOT cache on a sample app. We&#8217;ll build a cache for the app, confirm that the JVM really uses the AOT cache, and then train the cache. We&#8217;ll use JDK 26 for this tutorial.</p>
<h2 id="bd-what-is-the-ahead-of-time-cache-in-java" data-id="what-is-the-ahead-of-time-cache-in-java">2. What Is the Ahead-of-Time Cache in Java</h2><div class="bd-anchor" id="what-is-the-ahead-of-time-cache-in-java"></div>
<p>The feature first arrived in JDK 24 through <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/483">JEP 483</a>, as the first piece of Project Leyden. <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-25-features">JDK 25</a> added method profiles so the JIT can warm up sooner, and JDK 26 lifted the old garbage-collector limit through <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/516">JEP 516</a>. In the following sections we look at how the cache achieves what it claims. Let&#8217;s consider a small catalog service that we already have on hand: its code lives in one app.jar, packaged next to the handful of library JARs it depends on, and it boots through a <em>com.example.App</em> main class. <strong>We&#8217;ll build a cache for the app, confirm the JVM uses the cache, and then train well enough to see speed gains in its loading time.</strong></p>
<h3 id="bd-1-how-the-cache-speeds-up-startup" data-id="1-how-the-cache-speeds-up-startup">2.1. How the Cache Speeds Up Startup</h3><div class="bd-anchor" id="1-how-the-cache-speeds-up-startup"></div>
<p>Normally the JVM reads, loads, and links each class as the app boots, and it repeats that work on every run.<strong> The cache changes this by saving those classes once, so that on the next run they come straight from disk instead of being rebuilt</strong>. Because our cache captures the work of one specific app, we reuse it whenever we run that same app. The speedup comes from moving the heavy lifting earlier, which leaves the classes ready the moment the program starts.</p>
<h3 id="bd-2-training-and-production-runs" data-id="2-training-and-production-runs">2.2. Training and Production Runs</h3><div class="bd-anchor" id="2-training-and-production-runs"></div>
<p>Before we discuss the caching workflow, it helps to discuss two words we&#8217;ll lean on throughout: training and production. <strong>A training run is one we carry out deliberately, ahead of time, to watch the app and build the cache from what we observe, so it never serves real users. </strong>A production run, by contrast, is the real deployment that serves traffic, and it loads the finished cache to start faster.</p>
<h3 id="bd-2-the-three-phase-workflow" data-id="2-the-three-phase-workflow">2.2. The Three-Phase Workflow</h3><div class="bd-anchor" id="2-the-three-phase-workflow"></div>
<p><strong>The original workflow of the caching process runs in three phases, namely record, create, and run.</strong> Each step feeds the next. We start with the record phase. Here the JVM watches the app and writes what it sees to a configuration file. We switch it on with a flag and name the file through a configuration:</p>
<pre><code class="language-java">java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf \
  -cp app.jar com.example.App ...</code></pre>
<p>As it runs, this phase notes which classes load and which methods turn hot, then stores those findings in <em>app.aotconf</em>. Once the file is created, the next step comes to effect. We feed the file into the create phase, which turns the recorded data into a real cache:</p>
<pre><code class="language-java">java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf \
  -XX:AOTCache=app.aot -cp app.jar</code></pre>
<p>Once the cache exists, the run phase simply points at it and benefits from the faster start:</p>
<pre><code class="language-java">java -XX:AOTCache=app.aot -cp app.jar com.example.App ...</code></pre>
<p>We observe here that since the classes are already prepared, this final run skips the work the first two phases did. In the three phases discussed above, the record and create steps make up the training side, while the run phase stands in for production.</p>
<h2 id="bd-how-to-build-the-cache" data-id="how-to-build-the-cache">3. How to Build the Cache</h2><div class="bd-anchor" id="how-to-build-the-cache"></div>
<p>We should now focus on building the cache. Running the three phase workflow everytime is not very developer friendly. Also, it&#8217;s more than most apps need. In the following sections, <strong>we discuss how JDK 25 folded and converged the cache building steps.</strong></p>
<h3 id="bd-1-making-the-cache-in-one-step" data-id="1-making-the-cache-in-one-step">3.1. Making the Cache in One Step</h3><div class="bd-anchor" id="1-making-the-cache-in-one-step"></div>
<p>We hand a destination to <em>-XX:AOTCacheOutput</em>, and the JVM builds the cache for us. It shuts down and outputs it in the mentioned destination:</p>
<pre><code class="language-java">java -XX:AOTCacheOutput=app.aot \
  -cp app.jar com.example.App ...</code></pre>
<p>If we&#8217;d rather keep the training command separate from deployment, JEP 514 offers a two-step variant that records once and then reuses the cache:</p>
<pre><code class="language-java">java -XX:AOTCacheOutput=app.aot \
  -cp app.jar com.example.App ...
java -XX:AOTCache=app.aot -cp app.jar com.example.App ...</code></pre>
<p>For the majority of apps, this shorter path is what is used.</p>
<h3 id="bd-2-splitting-the-work-for-small-machines" data-id="2-splitting-the-work-for-small-machines">3.2. Splitting the Work for Small Machines</h3><div class="bd-anchor" id="2-splitting-the-work-for-small-machines"></div>
<p>Convenient as the one-step path is, it comes with a memory bill. <strong>While it assembles the cache, the JVM spins up a second heap the same size as the training heap, so the peak demand climbs to roughly twice the size we set.</strong> With <em>-Xms2g -Xmx2g</em>, for instance, the machine needs about 4 GB before it finishes. <strong>That overhead is why the three-phase split still earns its place on small cloud servers.</strong> We can record on a modest instance that mirrors production, and then assemble the cache on a larger one where there&#8217;s room to spare. The recording stays faithful to the real environment, while the heavy assembly runs where memory is plentiful.</p>
<h2 id="bd-how-to-shape-the-cache-our-app-needs" data-id="how-to-shape-the-cache-our-app-needs">4. How to Shape the Cache Our App Needs</h2><div class="bd-anchor" id="how-to-shape-the-cache-our-app-needs"></div>
<p><strong>A cache only pays off when the training run and the production are in line</strong>. And this require a handful of conditions to be satisfied.</p>
<h3 id="bd-1-keeping-training-and-production-aligned" data-id="1-keeping-training-and-production-aligned">4.1. Keeping Training and Production aligned</h3><div class="bd-anchor" id="1-keeping-training-and-production-aligned"></div>
<p>Let&#8217;s see a few rules which ensure the training and production runs align:</p>
<ul>
<li>the JAR timestamps stay the same across training runs</li>
<li>both runs use the same JDK, hardware, and OS</li>
<li>the training run acts like the real one, so the busy paths match</li>
<li>the classpath is a list of plain JARs, with no folders, wildcards, or nested JARs</li>
<li>the production classpath holds at least what the training one holds</li>
<li>no JVMTI agent adds to the boot or system class search at run time</li>
</ul>
<p><strong>Following the above, we should ideally see the deployment run produce the same result as training, and perhaps faster.</strong></p>
<h3 id="bd-2-checking-that-the-cache-is-valid" data-id="2-checking-that-the-cache-is-valid">4.2. Checking That the Cache Is Valid</h3><div class="bd-anchor" id="2-checking-that-the-cache-is-valid"></div>
<p>Rather than trusting the cache blindly, we can ask the JVM to treat a broken setup as a hard failure. Adding <em>-XX:AOTMode=on</em> does exactly that, turning a silent fallback into a clear error:</p>
<pre><code class="language-java">java -XX:AOTCache=app.aot -XX:AOTMode=on \
  -cp app.jar com.example.App ...</code></pre>
<p>So if the cache goes missing, or one of the conditions above slips, the run stops instead of quietly dropping the speedup. <strong>It&#8217;s worth remembering, though, that the cache is valid for a single build only. </strong>A code change, a new or updated library, or a JDK upgrade each invalidate it. <strong>This means we regenerate the cache alongside every build.</strong> And when the expected gain fails to appear, we can trace what the cache loads by adding <em>-Xlog:aot,class+path=info</em>.</p>
<h2 id="bd-tips-for-good-training-runs" data-id="tips-for-good-training-runs">5. Tips for Good Training Runs</h2><div class="bd-anchor" id="tips-for-good-training-runs"></div>
<p>Of the three phases we&#8217;ve covered, the training run is the one that carries the real weight. The create and run phases are mostly mechanical: they package whatever the training run observed and replay it on startup, without judging whether that was the right thing to capture. So the cache is only ever as good as the run that built it. Let&#8217;s discuss a few tips for architecting a good training run.</p>
<h3 id="bd-1-matching-what-production-loads" data-id="1-matching-what-production-loads">5.1. Matching What Production Loads</h3><div class="bd-anchor" id="1-matching-what-production-loads"></div>
<p>There&#8217;s a natural trade-off between how realistic a training run is and how easy it is to stage. <strong>A true production run opens connections, queries the database, and writes logs, all of which are difficult to reproduce.</strong> Therefore a synthetic run that follows the same paths usually strikes the better balance. What matters most is overlap. <strong>The closer the training run loads the same classes as production, the better the startup we get.</strong> To see that overlap, we can list everything a run loads by adding<em> -verbose:class</em> when we launch it. <strong>A light smoke test gives us a tidy way to drive those results.</strong> It exercises a normal startup without pulling in heavy suites, which keeps the cache lean. We named it in BDD style while keeping the framework minimal:</p>
<pre><code class="language-java">class StartupSmokeTest {
    @Test
    void givenFreshApplication_whenMainPathRuns_thenCoreServicesInitialize() {
        App app = App.bootstrap();
        assertTrue(app.isReady());
        assertNotNull(app.catalogService());
    }
}</code></pre>
<p>We rely on tests like this for the common startup paths. Also, we are deliberately leaving the stress and regression suites out of the training run.</p>
<h3 id="bd-2-finding-hot-methods-with-jfr" data-id="2-finding-hot-methods-with-jfr">5.2. Finding Hot Methods With JFR</h3><div class="bd-anchor" id="2-finding-hot-methods-with-jfr"></div>
<p>Now and then a method matters at run time yet never surfaces during a shallow training run. <strong>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-flight-recorder-monitoring">JDK Flight Recorder</a> is how we catch those cases. </strong>We begin by enabling the class-load event, then record the app from the moment it starts. Let&#8217;s start by first enabling the event: <code class="language-java">jfr configure jdk.ClassLoad#enabled=true
</code> Now, let&#8217;s start the recording: <code class="language-java">java -XX:StartFlightRecording:settings=custom.jfc,duration=60s,filename=/tmp/AOT.jfr</code> With a recording in hand, we can check the loaded classes and the hottest methods:</p>
<pre><code class="language-java">jfr print --events "jdk.ClassLoad" /tmp/AOT.jfr
jfr view hot-methods /tmp/AOT.jfr</code></pre>
<p><strong>If a hot method turns out to be missing, we extend the smoke test to walk through it. We start by leaning on a temporary folder, then a local network, or a mocked database wherever the path demands one.</strong></p>
<h2 id="bd-best-practices" data-id="best-practices">6. Best Practices</h2><div class="bd-anchor" id="best-practices"></div>
<p>A healthy cache really comes down to a few recurring habits and practices:</p>
<ul>
<li>Validity, since every rebuild or JDK upgrade calls for a fresh cache</li>
<li>Portability, since the cache is bound to one JVM and platform</li>
<li>Coverage, since the training run has to walk the normal startup paths</li>
<li>Setup, since both the JAR and the cache should run with least privilege</li>
</ul>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2><div class="bd-anchor" id="conclusion"></div>
<p>In this tutorial, we followed the whole AOT cache promise. We built a cache both in a single step and across three phases. Additionally, we confirmed that the JVM was really using it, and then trained it with a light smoke test so it covered the paths that count. Taken together, these steps make the cache easy to add and just as easy to keep current.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-ahead-of-time-cache">A Guide to Ahead-of-Time Cache in the Java</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/964742378/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964742378/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/964742378/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/964742378/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/964742378/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/964742378/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-ahead-of-time-cache#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-ahead-of-time-cache/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/~/964742378/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/spring-ai-short-term-memory-sessions</feedburner:origLink>
		<title>A Guide to Short-Term Memory Sessions in Spring AI</title>
		<link>https://feeds.feedblitz.com/~/964742381/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/964742381/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Yadier Betancourt]]></dc:creator>
		<pubDate>Thu, 30 Jul 2026 23:16:25 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204323</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="Two co-workers; the one on the left is holding up a magnifying glass to an artistic representation of files and folders. The one on the right is looking at a projection of some Java code. In between them there is an office plant. To the left of the image are the words &quot;Java on Baeldung&quot;" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Discover Spring AI Session, an event-sourced short-term memory layer for managing conversation history efficiently.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/964742381/0/baeldung">A Guide to Short-Term Memory Sessions in Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964742381/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/964742381/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-14-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/964742381/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/964742381/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/964742381/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-short-term-memory-sessions#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-short-term-memory-sessions/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-14-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="Two co-workers; the one on the left is holding up a magnifying glass to an artistic representation of files and folders. The one on the right is looking at a projection of some Java code. In between them there is an office plant. To the left of the image are the words &quot;Java on Baeldung&quot;" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14.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>Large language models are stateless, so every request we send is independent unless we replay the earlier conversation ourselves. Spring AI has traditionally solved this with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-chat-memory"><em>ChatMemory</em></a>, but as conversations grow, naively replaying every message quickly overflows the model&#8217;s context window.</p>
<p>In this tutorial, we&#8217;ll explore Spring AI Session, an event-sourced short-term memory layer that stores conversation history and shrinks it intelligently when it gets too large. We&#8217;ll create and inspect sessions, plug memory into a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-chatclient"><em>ChatClient</em></a> through an <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-advisors">advisor</a>, and review the available compaction strategies.</p>
<h2 id="bd-what-are-memory-sessions" data-id="what-are-memory-sessions">2. What Are Memory Sessions?</h2>
<div class="bd-anchor" id="what-are-memory-sessions"></div>
<p><strong>A session is a container for a single conversation, identified by an ID and optionally tied to a user.</strong> Instead of storing a flat list of messages, it records an ordered stream of <em>SessionEvent</em> objects, each wrapping a <em>Message</em> with a timestamp, a unique ID, and optional branch information.</p>
<p>The library groups these events into <em>turns</em>. <strong>A turn is one user message plus every assistant reply, tool call, and tool result that follows it, until the next user message.</strong> Turns are the atomic unit the library never breaks apart.</p>
<p>That last point is the key improvement over the older <em>ChatMemory</em> API. <strong>When history grows too large, Spring AI Session compacts it along turn boundaries rather than evicting the oldest individual messages.</strong> This way, we never end up with a dangling tool call whose result was dropped. For reference, a <em>MessageWindowChatMemory</em> capped at 20 messages becomes a <em>TurnCountTrigger(20)</em> paired with a sliding-window strategy.</p>
<p>Every event is immutable and timestamped. This lets the same session support more advanced scenarios, such as isolating the histories of cooperating agents through branch labels.</p>
<p>Sessions are currently incubating in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/spring-ai-community/spring-ai-session"><em>spring-ai-community</em></a> project and are slated to replace <em>ChatMemory</em> in a future Spring AI release.</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><strong>The Session API requires Spring AI 2.x and Spring Boot 4.x.</strong> We add the core module, which ships the in-memory repository and all the compaction building blocks:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springaicommunity&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-session-management&lt;/artifactId&gt;
    &lt;version&gt;0.5.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>The latest version of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springaicommunity/spring-ai-session-management"><em>spring-ai-session-management</em></a> is available in the Maven Repository.</p>
<p>To talk to a model, we add a Spring AI chat starter. The session layer is provider-agnostic, so OpenAI, Anthropic, or a local model works just as well. Here, we use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-google-genai">spring-ai-starter-model-google-genai</a> with our Gemini API key:</p>
<pre><code class="language-properties">spring.ai.google.genai.api-key=${GEMINI_API_KEY}
spring.ai.google.genai.chat.options.model=gemini-3.5-flash
</code></pre>
<p>For production, we&#8217;d typically swap the in-memory store for a relational one. Adding the <em>spring-ai-starter-session-jdbc</em> starter auto-configures a JDBC repository for PostgreSQL, MySQL, MariaDB, or H2. In this tutorial, we&#8217;ll stick with the in-memory repository to keep the examples self-contained.</p>
<h2 id="bd-creating-and-managing-sessions" data-id="creating-and-managing-sessions">4. Creating and Managing Sessions</h2>
<div class="bd-anchor" id="creating-and-managing-sessions"></div>
<p><strong>A <em>SessionService</em> is the entry point for the whole API.</strong> We start by exposing it as a bean backed by an <em>InMemorySessionRepository</em>:</p>
<pre><code class="language-java">@Bean
public SessionService sessionService() {
    return DefaultSessionService.builder()
      .sessionRepository(InMemorySessionRepository.builder().build())
      .build();
}
</code></pre>
<p>Let&#8217;s create a session, append a couple of messages, and read them back as plain Spring AI messages:</p>
<pre><code class="language-java">@Test
void givenSession_whenAppendingMessages_thenStoredInOrder() {
    Session session = sessionService.create(CreateSessionRequest.builder()
      .userId("alice")
      .build());
    sessionService.appendMessage(session.id(), new UserMessage("What is Spring AI?"));
    sessionService.appendMessage(session.id(),
      new AssistantMessage("It's an application framework for AI engineering."));
    List&lt;Message&gt; messages = sessionService.getMessages(session.id());
    assertThat(messages).hasSize(2);
    assertThat(messages.get(0).getMessageType()).isEqualTo(MessageType.USER);
    assertThat(messages.get(1).getMessageType()).isEqualTo(MessageType.ASSISTANT);
}
</code></pre>
<p>For lower-level access, <em>getEvents()</em> returns the richer <em>SessionEvent</em> stream, including timestamps and metadata. The service also lets us look up a conversation later with <em>findById()</em> or list everything for a user through <em>findByUserId()</em>. Finally, we can set a time-to-live on <em>CreateSessionRequest</em> so stale sessions expire automatically.</p>
<h2 id="bd-using-the-sessionmemoryadvisor" data-id="using-the-sessionmemoryadvisor">5. Using the SessionMemoryAdvisor</h2>
<div class="bd-anchor" id="using-the-sessionmemoryadvisor"></div>
<p>Managing the service by hand is useful, but most applications want memory to work transparently, much like the advisors we lean on when building an <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-assistant">AI assistant</a>. <strong>The <em>SessionMemoryAdvisor</em> hooks the session into the <em>ChatClient</em> pipeline</strong>, loading history before each call and appending the new exchange afterward. It&#8217;s a standard Spring AI advisor.</p>
<h3 id="bd-1-configuring-the-advisor-bean" data-id="1-configuring-the-advisor-bean">5.1. Configuring the Advisor Bean</h3>
<div class="bd-anchor" id="1-configuring-the-advisor-bean"></div>
<p>Now, let&#8217;s expose the advisor as a bean, attaching a compaction trigger and strategy:</p>
<pre><code class="language-java">@Bean
public SessionMemoryAdvisor sessionMemoryAdvisor(SessionService sessionService) {
    return SessionMemoryAdvisor.builder(sessionService)
      .defaultUserId("alice")
      .compactionTrigger(new TurnCountTrigger(20))
      .compactionStrategy(SlidingWindowCompactionStrategy.builder()
          .maxEvents(10)
          .build())
      .build();
}
</code></pre>
<p><strong>This configuration keeps the ten most recent events and compacts once a session passes twenty turns.</strong></p>
<h3 id="bd-2-wiring-it-into-chatclient" data-id="2-wiring-it-into-chatclient">5.2. Wiring It Into ChatClient</h3>
<div class="bd-anchor" id="2-wiring-it-into-chatclient"></div>
<p>Next, we register the advisor as a default on a <em>ChatClient</em>, then identify the conversation with the session-ID parameter on each call:</p>
<pre><code class="language-java">@Component
public class ChatService {
    private final ChatClient chatClient;
    public ChatService(ChatModel chatModel, SessionMemoryAdvisor sessionMemoryAdvisor) {
        this.chatClient = ChatClient.builder(chatModel)
          .defaultAdvisors(sessionMemoryAdvisor)
          .build();
    }
    public String chat(String sessionId, String prompt) {
        return chatClient.prompt()
          .user(prompt)
          .advisors(a -&gt; a.param(SessionMemoryAdvisor.SESSION_ID_CONTEXT_KEY, sessionId))
          .call()
          .content();
    }
}
</code></pre>
<p>The advisor stores every exchange under that session ID, so a follow-up question resolves against the earlier context. In the test below, we share a name in the first message, then ask for it back in a second one. The assertion confirms the reply still recalls the name, proving the session preserved context across the two turns:</p>
<pre><code class="language-java">@Test
void givenSessionId_whenChattingAcrossTurns_thenContextIsRemembered() {
    chatService.chat("session-abc", "My name is Yadier, remember it.");
    String response = chatService.chat("session-abc", "What is my name?");
    assertThat(response).containsIgnoringCase("Yadier");
    assertThat(sessionService.getMessages("session-abc")).hasSize(4);
}
</code></pre>
<p>The second call answers with the remembered name, which confirms the advisor replayed the first turn from the session before calling the model.</p>
<h2 id="bd-compaction-strategies" data-id="compaction-strategies">6. Compaction Strategies</h2>
<div class="bd-anchor" id="compaction-strategies"></div>
<p><strong>A compaction trigger decides <em>when</em> to shrink history, while a strategy decides <em>how</em>.</strong> Besides <em>TurnCountTrigger</em>, we can trigger on estimated tokens with <em>TokenCountTrigger</em>, or combine conditions with <em>CompositeCompactionTrigger</em>.</p>
<h3 id="bd-1-comparing-the-strategies" data-id="1-comparing-the-strategies">6.1. Comparing the Strategies</h3>
<div class="bd-anchor" id="1-comparing-the-strategies"></div>
<p>The library ships four strategies, all of which respect turn boundaries:</p>
<table class="table-styled">
<thead>
<tr>
<th>Strategy</th>
<th>LLM required</th>
<th>Best for</th>
</tr>
</thead>
<tbody>
<tr>
<td><em>SlidingWindowCompactionStrategy</em></td>
<td>No</td>
<td>Cost-sensitive, recent-context chats</td>
</tr>
<tr>
<td><em>TurnWindowCompactionStrategy</em></td>
<td>No</td>
<td>Keeping the last N complete turns</td>
</tr>
<tr>
<td><em>TokenCountCompactionStrategy</em></td>
<td>No</td>
<td>Hard context-window limits</td>
</tr>
<tr>
<td><em>RecursiveSummarizationCompactionStrategy</em></td>
<td>Yes</td>
<td>Long-running sessions needing recall</td>
</tr>
</tbody>
</table>
<p>The first three strategies drop older events outright, so they&#8217;re fast and free — a good default when we just need to cap history cheaply. Recursive summarization instead preserves the gist of what it removes, at the cost of an extra model call. In short, <strong>we reach for recursive summarization only when we need to recall older context,</strong> otherwise, the cheaper windowing strategies are enough.</p>
<h3 id="bd-2-running-compaction-manually" data-id="2-running-compaction-manually">6.2. Running Compaction Manually</h3>
<div class="bd-anchor" id="2-running-compaction-manually"></div>
<p><strong>The <em>SessionMemoryAdvisor</em> runs compaction automatically as a session grows, but we can also trigger it ourselves by calling <em>compact()</em> directly.</strong> That&#8217;s handy in tests or batch jobs where we decide exactly when history shrinks. Let&#8217;s use the <em>SlidingWindowCompactionStrategy</em> to keep only the most recent events, passing it and a trigger to <em>compact()</em>, which returns a <em>CompactionResult</em> describing what it archived:</p>
<pre><code class="language-java">@Test
void givenMultiTurnConversation_whenCompacting_thenOlderEventsAreArchived() {
    Session session = sessionService.create(CreateSessionRequest.builder()
      .userId("alice")
      .build());
    for (int turn = 1; turn &lt;= 4; turn++) {
        sessionService.appendMessage(session.id(), new UserMessage("Question " + turn));
        sessionService.appendMessage(session.id(), new AssistantMessage("Answer " + turn));
    }
    CompactionResult result = sessionService.compact(session.id(),
      new TurnCountTrigger(2),
      SlidingWindowCompactionStrategy.builder()
        .maxEvents(4)
        .build());
    assertThat(result.eventsRemoved()).isPositive();
    assertThat(sessionService.getEvents(session.id())).hasSameSizeAs(result.compactedEvents());
}
</code></pre>
<p>First we build up four turns, then we compact the session. Finally, we verify that some events were removed and the stored history now matches the compacted result. The assertions confirm that older events were archived and that the session now holds only the compacted set.</p>
<h3 id="bd-3-summarizing-with-an-llm" data-id="3-summarizing-with-an-llm">6.3. Summarizing With an LLM</h3>
<div class="bd-anchor" id="3-summarizing-with-an-llm"></div>
<p><strong>Recursive summarization is the one strategy that needs a <em>ChatClient</em>.</strong> The other three simply drop or window events, but this one asks a model to write a summary of the events it removes, then replaces them with a single synthetic summary event. That extra model call is the price for keeping older context available in condensed form:</p>
<pre><code class="language-java">@Test
void givenLongConversation_whenSummarizing_thenOlderEventsAreReplacedBySummary() {
    Session session = sessionService.create(CreateSessionRequest.builder()
      .userId("alice")
      .build());
    for (int turn = 1; turn &lt;= 4; turn++) {
        sessionService.appendMessage(session.id(), new UserMessage("Question " + turn));
        sessionService.appendMessage(session.id(), new AssistantMessage("Answer " + turn));
    }
    ChatClient chatClient = ChatClient.builder(chatModel).build();
    CompactionResult result = sessionService.compact(session.id(),
      new TurnCountTrigger(2),
      RecursiveSummarizationCompactionStrategy.builder(chatClient)
        .maxEventsToKeep(4)
        .build());
    SessionEvent summary = result.compactedEvents().stream()
      .filter(SessionEvent::isSynthetic)
      .findFirst()
      .orElseThrow();
    assertThat(result.eventsRemoved()).isPositive();
    assertThat(summary.getMessage().getText()).isNotBlank();
}
</code></pre>
<p>The synthetic event in the compacted set is the model-generated summary, so the session keeps the gist of the dropped turns in condensed form.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored Spring AI&#8217;s short-term memory sessions. We saw how a <em>SessionService</em> stores conversations as turn-aware events. The <em>SessionMemoryAdvisor</em> makes that memory transparent to the <em>ChatClient</em>. Finally, pluggable triggers and strategies keep history within the model&#8217;s context window.</p>
<p>As the API graduates from incubation, it&#8217;s positioned to become the default replacement for <em>ChatMemory</em>, so investing in it now sets us up for the long term.</p>
<p>As always, the full source code is available over on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-session">GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-short-term-memory-sessions">A Guide to Short-Term Memory Sessions in Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/964742381/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/964742381/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/964742381/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-14-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/964742381/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/964742381/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/964742381/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-short-term-memory-sessions#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-short-term-memory-sessions/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/~/964742381/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-14-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-application-startup-tracking</feedburner:origLink>
		<title>Application Startup Tracking in Spring</title>
		<link>https://feeds.feedblitz.com/~/963082187/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/963082187/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Michael Krimgen]]></dc:creator>
		<pubDate>Sat, 25 Jul 2026 19:15:03 +0000</pubDate>
				<category><![CDATA[Spring Boot]]></category>
		<category><![CDATA[Java Flight Recorder (JFR)]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[popular]]></category>
		<category><![CDATA[Spring Boot Actuator]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-application-startup-tracking</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to track and analyze Spring application startup with <em>ApplicationStartup</em>, <em>BufferingApplicationStartup</em>, <em>FlightRecorderApplicationStartup</em>, and custom startup steps.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/963082187/0/baeldung">Application Startup Tracking in Spring</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/963082187/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/963082187/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-09-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/963082187/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/963082187/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/963082187/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-application-startup-tracking#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-application-startup-tracking/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<div>
<p>Application startup aims to speed up overall performance post-load. This means creating all necessary objects for initial use, but also caching or preparing data that&#8217;s highly likely to be required. In addition to being a way to improve the user experience, application startup may be problematic.</p>
<p>In this tutorial, we&#8217;ll learn how to facilitate, track, and monitor the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-application-context">context</a> creation of a Spring application.</p>
<p>First, we&#8217;ll recap what the Spring context is and why we want to track its creation. After that, we&#8217;ll look at the three implementations that Spring Boot provides to easily monitor context creation. Finally, we&#8217;ll take a quick look at how and why we might want to implement custom startup tracking logic.</p>
<h2 id="bd-spring-application-context" data-id="spring-application-context">2. Spring Application Context</h2>
<div class="bd-anchor" id="spring-application-context"></div>
<p>The Spring Boot application context serves as the Inversion of Control (IoC) container. <strong>This container manages the lifecycle of a Spring application and provides dependency injection, Spring bean management, events, and configuration management</strong>.</p>
<p>The context is created during the startup of a Spring application. For simple applications, a Spring application typically starts quickly, but can take longer for complex ones and even freeze during initialization.</p>
<p>There are various ways to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-startup-speed">improve the startup time</a>. <strong>To help us better understand the startup process, Spring provides the ability to track the application context creation</strong>.</p>
<h2 id="bd-the-applicationstartup-interface" data-id="the-applicationstartup-interface">3. The <em>ApplicationStartup</em> Interface</h2>
<div class="bd-anchor" id="the-applicationstartup-interface"></div>
<p>Spring provides two implementations of the <em>ApplicationStartup</em> interface:</p>
<ul>
<li><em>DefaultApplicationStartup</em></li>
<li><em>FlightRecorderApplicationStartup</em></li>
</ul>
<p>The default implementation is a no-op, while <em>FlightRecorderApplicationStartup</em> provides an implementation for use with the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-flight-recorder-monitoring">Java Flight Recorder (JFR)</a>.</p>
<p>In addition, Spring Boot comes with the <em>BufferingApplicationStartup</em> implementation that records startup events in memory. <strong>If we want to use anything other than <em>DefaultApplicationStartup</em>, we need to initialize the Spring application with the desired <em>ApplicationStartup</em> implementation before we start the application</strong>.</p>
<h2 id="bd-bufferingapplicationstartup" data-id="bufferingapplicationstartup">4. <em>BufferingApplicationStartup</em></h2>
<div class="bd-anchor" id="bufferingapplicationstartup"></div>
<p>Let&#8217;s look at <em>BufferingApplicationStartup</em>, an implementation that stores startup events in memory and also provides an actuator endpoint for convenient access to the recorded events.</p>
<h3 id="bd-1-configuration" data-id="1-configuration">4.1. Configuration</h3>
<div class="bd-anchor" id="1-configuration"></div>
<p>First, we need to register <em>BufferingApplicationStartup</em> with the Spring Boot application:</p>
<pre><code class="language-java">@SpringBootApplication
public class ApplicationStartupTrackingApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ApplicationStartupTrackingApplication.class);
        app.setApplicationStartup(new BufferingApplicationStartup(2048));
        app.run(args);
    }
}</code></pre>
<p>The constructor parameter (<em>2048</em> in this example) specifies the buffer size, i.e., the maximum number of events that can be recorded.</p>
</div>
<h3 id="bd-2-access-via-the-actuator-endpoint" data-id="2-access-via-the-actuator-endpoint">4.2. Access via the Actuator Endpoint</h3>
<div class="bd-anchor" id="2-access-via-the-actuator-endpoint"></div>
<p>One way to retrieve the recorded events is via an actuator endpoint. Specifically, we can enable the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-actuator-startup">startup actuator</a> in the application properties:</p>
<pre><code class="language-yaml">management.endpoints.web.exposure.include=startup</code></pre>
<p>In case we have a Spring bean, we also make the necessary addition:</p>
<pre><code class="language-java">@Service
public class SpecialService {
}</code></pre>
<p>If we access the actuator, we can see an entry for the service bean:</p>
<pre><code class="language-bash">curl localhost:8080/actuator/startup | jq
{
  "duration": "PT0.000754S",
  "endTime": "2026-07-19T18:31:27.493778Z",
  "startTime": "2026-07-19T18:31:27.493024Z",
  "startupStep": {
    "id": 107,
    "name": "spring.beans.instantiate",
    "parentId": 4,
    "tags": [
      {
        "key": "beanName",
        "value": "specialService"
      }
    ]
  }
}</code></pre>
<p><strong>It&#8217;s important to note that the actuator only works with <em>BufferingApplicationStartup</em></strong>. If we configure the application with any other implementation of the <em>ApplicationStartup</em> interface, we see an error message:</p>
<pre><code class="language-bash">curl localhost:8080/actuator/startup | jq
{
  "timestamp": "2026-07-19T14:37:45.989Z",
  "status": 404,
  "error": "Not Found",
  "path": "/actuator/startup"
}</code></pre>
<p><strong>Furthermore, we can call the actuator endpoint with a GET or a POST request</strong>. In both cases, the response should be the same; however, GET returns a snapshot of the timeline, while POST returns the snapshot and also clears the buffer.</p>
<h3 id="bd-3-access-via-the-application-context" data-id="3-access-via-the-application-context">4.3. Access via the Application Context</h3>
<div class="bd-anchor" id="3-access-via-the-application-context"></div>
<p>In addition to using the actuator endpoint, we can programmatically access the timeline <em>BufferingApplicationStartup</em> provides. To do so, we access the configured <em>BufferingApplicationStartup</em> instance:</p>
<pre><code class="language-java">@Component
public class StartupTracker {
    private BufferingApplicationStartup startup;
    
    public StartupTracker(@Autowired ApplicationContext context) {
        startup = (BufferingApplicationStartup) (
            (ConfigurableApplicationContext) context).getApplicationStartup();
    }
    public List recorded() {
        List recordedEvents = new ArrayList&lt;&gt;();
        for(StartupTimeline.TimelineEvent event : startup.getBufferedTimeline().getEvents()) {
            for(StartupStep.Tag tag : event.getStartupStep().getTags()) {
                recordedEvents.add(
                    event.getStartupStep().getName() + "  " +
                        tag.getKey() + " " +
                        tag.getValue()
                );
            }
        }
        return recordedEvents;
    }
}</code></pre>
<p>Thus, we have access to the deeper startup context.</p>
<h3 id="bd-4-usage-in-spring-boot-tests" data-id="4-usage-in-spring-boot-tests">4.4. Usage in Spring Boot Tests</h3>
<div class="bd-anchor" id="4-usage-in-spring-boot-tests"></div>
<p>This way, we can analyze the timeline and create a custom output format, filter certain events, and also test the implementation:</p>
<pre><code class="language-java">@SpringBootTest(useMainMethod = SpringBootTest.UseMainMethod.ALWAYS)
public class ApplicationTest {
    @Autowired
    private StartupTracker startupTracker;
    @Test
    void givenTheApplicationStarts_whenRetrieveRecordedEvents_ThenContainsCustomBeans() {
        Assertions.assertThat(startupTracker.recorded()).contains(
            "spring.beans.instantiate  beanName specialService",
            "spring.beans.instantiate  beanName startupTracker"
        );
    }
}</code></pre>
<p>We can see that the list of events includes the two beans that we have created, <em>SpecialService</em> and <em>StartupTracker.</em></p>
<p>Notably, we need to configure the test with <em>SpringBootTest.UseMainMethod.ALWAYS</em>. <strong>This ensures that we start the application via the main method and Spring Boot is configured to use <em>BufferingApplicationStartup</em> instead of the no-op implementation</strong>.</p>
<div>
<h2 id="bd-flightrecorderapplicationstartup" data-id="flightrecorderapplicationstartup">5. <em>FlightRecorderApplicationStartup</em></h2>
<div class="bd-anchor" id="flightrecorderapplicationstartup"></div>
<p>Another option to track application startup is <em>FlightRecorderApplicationStartup</em>, <strong>an implementation that provides Spring startup metrics in a format suitable for the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-flight-recorder-monitoring">Java Flight Recorder</a> (JFR), which can be viewed in the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-flight-recorder-view">Java Mission Control</a> (JMC) tool</strong>.</p>
<p>Let&#8217;s configure the application to use <em>FlightRecorderApplicationStartup</em>:</p>
<pre><code class="language-java">app.setApplicationStartup(new FlightRecorderApplicationStartup());</code></pre>
<p>In addition, we need to provide a JVM parameter:</p>
<pre><code class="language-bash">-XX:StartFlightRecording:filename=recording.jfr,duration=20s</code></pre>
<p>Now, Spring records the application startup events in a file named <em>recording.jfr</em> that we can view in the Java Mission Control (JMC) viewer:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/Scherm­afbeelding-2026-07-12-om-22.16.36.png"><img loading="lazy" decoding="async" class="alignnone wp-image-254416 size-large" src="https://www.baeldung.com/wp-content/uploads/2026/07/Scherm­afbeelding-2026-07-12-om-22.16.36-1024x406.png" alt="Java Mission control view" width="580" height="230" /></a></p>
<p>In particular, we can find the startup steps under the category <em>Spring Application</em>.</p>
<h2 id="bd-adding-custom-steps" data-id="adding-custom-steps">6. Adding Custom Steps</h2>
<div class="bd-anchor" id="adding-custom-steps"></div>
</div>
<p><strong>The recommended way to customize the startup timeline is to add custom startup steps to the <em>ApplicationStartup</em> instance</strong> that we can retrieve from the application context. To demonstrate how to do that, let&#8217;s create a service class and add some custom steps:</p>
<pre><code class="language-java">@Service
public class SpecialService {
    private ApplicationStartup applicationStartup;
    public SpecialService(ApplicationContext context) {
        this.applicationStartup = ((ConfigurableApplicationContext) context).getApplicationStartup();
    }
    @PostConstruct
    public void init() {
        StartupStep startupStep1 = this.applicationStartup.start("com.baeldung.special.service");
        try {
            startupStep1.tag("init", "connect to databases");
            // some long-running initialization
        } finally {
            startupStep1.end();
        }
        StartupStep startupStep2 = this.applicationStartup.start("com.baeldung.special.service");
        try {
            startupStep2.tag("init", "connect to AI agent");
            // more long-running initialization
        } finally {
            startupStep2.end();
        }
    }
}</code></pre>
<p>So, we can now see a step for the initialization of the <em>SpecialService</em> bean as the parent step (<em>id=107</em>) and the two additional steps (<em>id=[108,109]</em>) in the response of the actuator:</p>
<pre><code class="language-json">[
  {
    "duration": "PT0.000006S",
    "endTime": "2026-07-18T19:15:12.096227Z",
    "startTime": "2026-07-18T19:15:12.096221Z",
    "startupStep": {
      "id": 108,
      "name": "com.baeldung.special.service",
      "parentId": 107,
      "tags": [
        {
          "key": "init",
          "value": "connect to databases"
        }
      ]
    }
  },
  {
    "duration": "PT0.000002S",
    "endTime": "2026-07-18T19:15:12.096238Z",
    "startTime": "2026-07-18T19:15:12.096236Z",
    "startupStep": {
      "id": 109,
      "name": "com.baeldung.special.service",
      "parentId": 107,
      "tags": [
        {
          "key": "init",
          "value": "connect to AI agent"
        }
      ]
    }
  },
  {
    "duration": "PT0.000682S",
    "endTime": "2026-07-18T19:15:12.096328Z",
    "startTime": "2026-07-18T19:15:12.095646Z",
    "startupStep": {
      "id": 107,
      "name": "spring.beans.instantiate",
      "parentId": 4,
      "tags": [
        {
          "key": "beanName",
          "value": "specialService"
        }
      ]
    }
  }
]</code></pre>
<p>The output shows metrics for both startup steps: <em>connect to databases</em> and <em>connect to AI agent</em>, as well as a step for the creation of the service itself <em>spring.beans.instantiate</em>. As seen earlier, we can also monitor these startup steps in the Java Flight Recorder or programmatically via the <em>BufferingApplicationStartup</em>.</p>
<div>
<h2 id="bd-implementing-the-applicationstartup-interface" data-id="implementing-the-applicationstartup-interface">7. Implementing the <em>ApplicationStartup</em> Interface</h2>
<div class="bd-anchor" id="implementing-the-applicationstartup-interface"></div>
<p>The recommended way to access Spring startup metrics is using one of the three implementations of the <em>ApplicationStartup</em> interface that Spring provides. However, it&#8217;s possible to provide a custom implementation:</p>
<pre><code class="language-java">public class CustomStartup implements ApplicationStartup {
    @Override
    public StartupStep start(String name) {
        return new CustomStartupStep(name);
    }
}</code></pre>
<p>In this case, we should also provide an implementation of the <em>StartupStep</em> interface:</p>
<pre><code class="language-java">public class CustomStartupStep implements StartupStep {
    private String name;
    public CustomStartupStep(String name) {
        this.name = name;
    }
    @Override
    public String getName() {
        return name;
    }
    // Override other methods
}</code></pre>
</div>
<p>Since it&#8217;s a rare scenario, we don&#8217;t discuss a complete implementation of a custom <em>ApplicationStartup</em> class. <strong>One reason to implement a custom startup logic is the possibility to observe the steps in real time during context creation</strong>. This way, if needed, we can take action immediately, for example, to send a message if a bean creation takes a long time, instead of waiting until the entire context is created.</p>
<div>
<h2 id="bd-conclusion" data-id="conclusion">8. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we looked at how Spring tracks the creation of its application context through the <em>ApplicationStartup</em> interface.</p>
<p>Specifically, <strong>we covered the three implementations that Spring provides</strong>:</p>
<ul>
<li><em>DefaultApplicationStartup</em></li>
<li><em>BufferingApplicationStartup</em></li>
<li><em>FlightRecorderApplicationStartup</em></li>
</ul>
<p>In all cases, <strong>we learned how to access the recorded steps and add further steps to the timeline</strong>. Lastly, we had a quick look at how to plug a custom implementation of the <em>ApplicationStartup</em> interface.</p>
<p>As usual, the code for this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-3-observation">over on GitHub</a>.</p>
</div><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-application-startup-tracking">Application Startup Tracking in Spring</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/963082187/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/963082187/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/963082187/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-09-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/963082187/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/963082187/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/963082187/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-application-startup-tracking#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-application-startup-tracking/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/~/963082187/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-09-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/apache-causeway</feedburner:origLink>
		<title>Introduction to Apache Causeway</title>
		<link>https://feeds.feedblitz.com/~/963082190/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/963082190/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Francesco Galgani]]></dc:creator>
		<pubDate>Sat, 25 Jul 2026 19:11:34 +0000</pubDate>
				<category><![CDATA[Spring Boot]]></category>
		<category><![CDATA[JPA Entities]]></category>
		<category><![CDATA[popular]]></category>
		<category><![CDATA[REST Basics]]></category>
		<category><![CDATA[Spring Data JPA]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/apache-causeway</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-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 about Apache Causeway by using it to build a domain-driven asset management application, including a generated UI, REST API, persistence, and business rules.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/963082190/0/baeldung">Introduction to Apache Causeway</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/963082190/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/963082190/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Boot-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/963082190/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/963082190/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/963082190/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/apache-causeway#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/apache-causeway/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-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/Spring-Boot-Featured-Image-02-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-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><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://causeway.apache.org/"><strong>Apache Causeway</strong></a><strong> is a framework for building domain-driven applications on top of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-start">Spring Boot</a></strong>. Instead of beginning with controllers and pages, we describe domain objects, their state, behavior, and business rules. Causeway turns that description into a metamodel at runtime.</p>
<p>After that, the Wicket viewer and RESTful Objects viewer use the same metamodel to provide a web UI and a hypermedia API. This makes Causeway particularly useful for internal management software, where broad coverage of the domain and fast feedback often matter more than a custom interface.</p>
<p>In this tutorial, <strong>we&#8217;ll use Apache Causeway 3.6.0, Java 21, and Maven to build a small asset management application</strong>. Specifically, we&#8217;ll model assets such as laptops, monitors, and phones, add lifecycle actions and business rules, run the generated UI, and invoke one of the generated REST endpoints.</p>
<h2 id="bd-mental-model" data-id="mental-model">2. Mental Model</h2>
<div class="bd-anchor" id="mental-model"></div>
<p>The domain is the business area that the software addresses.</p>
<h3 id="bd-1-domain" data-id="1-domain"><strong>2.1. Domain</strong></h3>
<div class="bd-anchor" id="1-domain"></div>
<p><strong>The domain model is the set of types, relationships, operations, and rules that represent that area</strong>.</p>
<p>In this case, the domain is the management of company hardware:</p>
<ul>
<li>which assets exist</li>
<li>who has them</li>
<li>which lifecycle transitions are valid</li>
</ul>
<p>So, the main domain object here is <em>Asset</em>. Because instances are stored in a database, <em>Asset</em> is also a domain entity. A view model, by contrast, could represent temporary or calculated information without having a persistent identity. Causeway exposes scalar values such as <em>type</em>, <em>serialNumber</em>, <em>status</em>, and <em>assignedTo</em> as properties. A relationship to several other objects would be a collection.</p>
<p>Behavior appears as actions such as <em>assignTo()</em>, <em>returnToInventory()</em>, and <em>retire()</em>. Operations that don&#8217;t naturally belong to one asset, such as creating or finding assets, live in the <em>Assets</em> domain service. <strong>Causeway uses these domain members as the common vocabulary for both its UI and REST viewers</strong>.</p>
<h3 id="bd-2-causeway-vs-traditional-spring-mvc-application" data-id="2-causeway-vs-traditional-spring-mvc-application">2.2. Causeway vs. Traditional Spring MVC Application</h3>
<div class="bd-anchor" id="2-causeway-vs-traditional-spring-mvc-application"></div>
<p>In a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mvc-tutorial">traditional Spring MVC application</a>, a request usually reaches a controller, which performs several actions:</p>
<ul>
<li>calls an application service</li>
<li>prepares a model or DTO</li>
<li>selects a view or produces a response</li>
</ul>
<p><strong>Even when the business logic is well isolated, each new use case generally needs explicit web-layer code</strong>.</p>
<p>Causeway changes the starting point. It introspects entities, domain services, annotations, and method signatures, and records them in its metamodel. The viewers render the properties, collections, actions, parameters, and validation messages that the metamodel describes. Therefore, <strong>we can add a basic use case without creating a matching controller, form, and template</strong>.</p>
<p>This doesn&#8217;t remove the rest of the application architecture. We still configure Spring modules, persistence, security, and menu layout, and we can place complex orchestration in application services when appropriate. <strong>Causeway reduces presentation-layer repetition by making the domain model drive the interaction</strong>.</p>
<h3 id="bd-3-when-causeway-is-a-good-fit" data-id="3-when-causeway-is-a-good-fit">2.3. When Causeway Is a Good Fit</h3>
<div class="bd-anchor" id="3-when-causeway-is-a-good-fit"></div>
<p>This model works well for back-office tools, administration systems, internal management applications, and domain-heavy prototypes. Such systems often combine structured data with many business operations, so a consistent generated interface lets a team validate terminology, workflows, and rules early.</p>
<p><strong>It&#8217;s a weaker fit when the primary requirement is a highly branded consumer experience, pixel-level interaction design, or a page flow that doesn&#8217;t map naturally to domain objects</strong>. In such cases, we can place a custom client in front of the Causeway APIs, but doing so sacrifices some of the development speed offered by the generic viewer.</p>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://causeway.apache.org/docs/3.6.0/what-is-apache-causeway/common-use-cases.html">common-use-case documentation</a> of the framework presents the same spectrum, from prototyping and generic line-of-business UIs to custom clients. <strong>The important decision is whether domain coverage or presentation control is the stronger requirement</strong>.</p>
<h2 id="bd-building-an-internal-asset-management-application" data-id="building-an-internal-asset-management-application">3. Building an Internal Asset Management Application</h2>
<div class="bd-anchor" id="building-an-internal-asset-management-application"></div>
<p>Let&#8217;s model an asset with several characteristics:</p>
<ul>
<li>type</li>
<li>serial number</li>
<li>status</li>
<li>assignee (optional)</li>
</ul>
<p>The normal asset lifecycle is <em>AVAILABLE</em> to <em>ASSIGNED</em>, then back to <em>AVAILABLE</em>, while an available asset can also become <em>RETIRED</em>.</p>
<p>A runnable Causeway application needs framework module imports, persistence, security, and menu configuration in addition to the central domain code. <strong>To follow along, let&#8217;s keep the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/apache-causeway">complete project on GitHub</a> open</strong>. The snippets that follow cover the decisions relevant to the tutorial, while the repository contains the supporting bootstrap and configuration files.</p>
<h3 id="bd-1-project-setup" data-id="1-project-setup">3.1. Project Setup</h3>
<div class="bd-anchor" id="1-project-setup"></div>
<p>To begin with, we need JDK 21 and Maven 3.9.11. <strong>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://central.sonatype.com/artifact/org.apache.causeway.app/causeway-app-starter-parent/3.6.0">Causeway application starter parent</a> manages the compatible Spring Boot and framework dependency versions</strong>. In <em>pom.xml</em>, let&#8217;s add the web application bundle, both viewers, Simple Security, JPA with EclipseLink, and an H2 in-memory database:</p>
<pre><code class="language-xml">&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
    &lt;parent&gt;
        &lt;groupId&gt;org.apache.causeway.app&lt;/groupId&gt;
        &lt;artifactId&gt;causeway-app-starter-parent&lt;/artifactId&gt;
        &lt;version&gt;3.6.0&lt;/version&gt;
        &lt;relativePath/&gt;
    &lt;/parent&gt;
    &lt;groupId&gt;com.baeldung&lt;/groupId&gt;
    &lt;artifactId&gt;apache-causeway&lt;/artifactId&gt;
    &lt;version&gt;1.0.0-SNAPSHOT&lt;/version&gt;
    &lt;!-- The name and description are omitted. --&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.causeway.mavendeps&lt;/groupId&gt;
            &lt;artifactId&gt;causeway-mavendeps-webapp&lt;/artifactId&gt;
            &lt;type&gt;pom&lt;/type&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.causeway.viewer&lt;/groupId&gt;
            &lt;artifactId&gt;causeway-viewer-wicket-viewer&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.causeway.viewer&lt;/groupId&gt;
            &lt;artifactId&gt;causeway-viewer-restfulobjects-jaxrsresteasy&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.causeway.security&lt;/groupId&gt;
            &lt;artifactId&gt;causeway-security-simple&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.causeway.persistence&lt;/groupId&gt;
            &lt;artifactId&gt;causeway-persistence-jpa-eclipselink&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.causeway.viewer&lt;/groupId&gt;
            &lt;artifactId&gt;causeway-viewer-wicket-applib&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.h2database&lt;/groupId&gt;
            &lt;artifactId&gt;h2&lt;/artifactId&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework&lt;/groupId&gt;
            &lt;artifactId&gt;spring-instrument&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!-- JUnit, Mockito, and Causeway test dependencies are omitted. --&gt;
    &lt;/dependencies&gt;
    &lt;!-- EclipseLink weaving and Spring Boot packaging plugins are omitted. --&gt;
    &lt;properties&gt;
        &lt;java.version&gt;21&lt;/java.version&gt;
        &lt;maven.compiler.release&gt;21&lt;/maven.compiler.release&gt;
    &lt;/properties&gt;
&lt;/project&gt;</code></pre>
<p>The web application bundle supplies the common runtime dependencies. The viewer artifacts add the generated Wicket interface and RESTful Objects API, while Simple Security and H2 keep this example self-contained.</p>
<p>Next, a small Spring configuration class identifies the application code:</p>
<pre><code class="language-java">@Configuration
@ComponentScan(basePackageClasses = Assets.class)
@EnableJpaRepositories(basePackageClasses = AssetRepository.class)
@EntityScan(basePackageClasses = Asset.class)
public class AssetManagementModule {
}</code></pre>
<p>Let&#8217;s break it down:</p>
<ul>
<li><em>@ComponentScan</em> discovers the domain service</li>
<li><em>@EnableJpaRepositories</em> enables the Spring Data repository</li>
<li><em>@EntityScan</em> registers the JPA entity</li>
</ul>
<p>Then, we import this configuration together with the required Causeway modules in <em>AppManifest</em>:</p>
<pre><code class="language-java">@Configuration
@Import({
    CausewayModuleApplibMixins.class,
    CausewayModuleCoreRuntimeServices.class,
    CausewayModuleSecuritySimple.class,
    CausewayModulePersistenceJpaEclipselink.class,
    CausewayModuleViewerRestfulObjectsJaxrsResteasy.class,
    CausewayModuleViewerWicketApplibMixins.class,
    CausewayModuleViewerWicketViewer.class,
    AssetManagementModule.class
})
@PropertySource(CausewayPresets.NoTranslations)
public class AppManifest {
    // Password encoding and the demo-only SimpleRealm configuration are omitted
}</code></pre>
<p>Importing a module makes its Spring beans and Causeway features available to the application.</p>
<p>The repository also configures H2, EclipseLink schema creation, the demo user, and the menu layout. In <em>application.yml</em>, we set <em>causeway.applib.annotation.action.explicit</em> to <em>true</em>, so only methods explicitly annotated with <em>@Action</em> become actions.</p>
<h3 id="bd-2-creating-the-asset-domain-entity" data-id="2-creating-the-asset-domain-entity">3.2. Creating the Asset Domain Entity</h3>
<div class="bd-anchor" id="2-creating-the-asset-domain-entity"></div>
<p>Let&#8217;s define the persistent type and give it a stable logical name:</p>
<pre><code class="language-java">@Entity
@Table(
    schema = "assets",
    name = "Asset",
    uniqueConstraints = @UniqueConstraint(
        name = "Asset__serialNumber__UNQ",
        columnNames = "serial_number"
    )
)
@EntityListeners(CausewayEntityListener.class)
@Named("assets.Asset")
@DomainObject
@DomainObjectLayout
public class Asset implements Comparable&lt;Asset&gt; {
    protected Asset() {
    }
    public Asset(final AssetType type, final String serialNumber) {
        this.type = type;
        this.serialNumber = serialNumber;
        this.status = AssetStatus.AVAILABLE;
    }
    // The identifier, version, comparison helpers, and members discussed below are omitted
}</code></pre>
<p>So, let&#8217;s take a closer look at what the code means:</p>
<ul>
<li>The JPA annotations map the entity and enforce a database-level uniqueness constraint on <em>serial_number</em>.</li>
<li><em>@Named</em> supplies the logical identifier that Causeway uses independently of the Java package name.</li>
<li><em>@DomainObject</em> explicitly identifies the class as a Causeway domain object.</li>
<li><em>CausewayEntityListener</em> connects JPA lifecycle events to Causeway, including service injection and lifecycle notifications.</li>
<li>The public constructor establishes the first lifecycle invariant: every new asset begins in the <em>AVAILABLE</em> status.</li>
</ul>
<p>JPA maps the stored values, while Causeway recognizes the corresponding getters as properties. To be clear, let&#8217;s see the identity properties:</p>
<pre><code class="language-java">@Enumerated(EnumType.STRING)
@Column(name = "type", nullable = false, length = 20)
private AssetType type;
@PropertyLayout(fieldSetId = LayoutConstants.FieldSetId.IDENTITY, sequence = "1")
public AssetType getType() {
    return type;
}
@Column(name = "serial_number", nullable = false, length = 80)
private String serialNumber;
@Title(prepend = "Asset: ")
@Property(maxLength = 80)
@PropertyLayout(fieldSetId = LayoutConstants.FieldSetId.IDENTITY, sequence = "2")
public String getSerialNumber() {
    return serialNumber;
}
// Status and assignedTo are omitted here to focus on the identity properties
</code></pre>
<p>There are several important details:</p>
<ul>
<li><em>EnumType.STRING</em> stores values such as <em>LAPTOP</em> instead of fragile ordinal numbers.</li>
<li><em>@PropertyLayout</em> groups and orders members in the generated UI.</li>
<li><em>@Title</em> makes the serial number part of the object&#8217;s display title.</li>
</ul>
<p>The remaining properties hold an <em>AssetStatus</em> and an optional employee name.</p>
<h3 id="bd-3-adding-domain-behavior-with-actions" data-id="3-adding-domain-behavior-with-actions">3.3. Adding Domain Behavior With Actions</h3>
<div class="bd-anchor" id="3-adding-domain-behavior-with-actions"></div>
<p>Notably, we don&#8217;t want callers to change lifecycle fields independently through setters. Instead, <strong>the entity exposes operations that update related state together</strong>.</p>
<p>First, the assignment action records an employee and changes the status:</p>
<pre><code class="language-java">@Action(semantics = SemanticsOf.IDEMPOTENT)
@ActionLayout(
    fieldSetId = LayoutConstants.FieldSetId.DETAILS,
    position = ActionLayout.Position.PANEL,
    describedAs = "Assigns an available asset to an employee"
)
public Asset assignTo(
    @Parameter(maxLength = 100)
    @ParameterLayout(named = "Employee") final String employee) {
    assignedTo = employee.trim();
    status = AssetStatus.ASSIGNED;
    return this;
}</code></pre>
<p>Causeway renders <em>assignTo()</em> as an action and derives its prompt from the parameter metadata. By returning <em>this</em>, it tells the viewer to continue with the updated asset. The <em>IDEMPOTENT</em> semantic describes the intended invocation semantics, but it doesn&#8217;t make the Java implementation idempotent automatically.</p>
<p>The other lifecycle actions return an asset to inventory or retire it:</p>
<pre><code class="language-java">@Action(semantics = SemanticsOf.IDEMPOTENT)
@ActionLayout(
    fieldSetId = LayoutConstants.FieldSetId.DETAILS,
    position = ActionLayout.Position.PANEL,
    describedAs = "Returns an assigned asset to inventory"
)
public Asset returnToInventory() {
    assignedTo = null;
    status = AssetStatus.AVAILABLE;
    return this;
}
@Action(semantics = SemanticsOf.IDEMPOTENT_ARE_YOU_SURE)
@ActionLayout(
    fieldSetId = LayoutConstants.FieldSetId.DETAILS,
    position = ActionLayout.Position.PANEL,
    describedAs = "Permanently retires an asset"
)
public Asset retire() {
    assignedTo = null;
    status = AssetStatus.RETIRED;
    return this;
}</code></pre>
<p>Specifically, <em>returnToInventory()</em> clears the assignee as it restores <em>AVAILABLE</em>. Retirement is deliberately explicit, and <em>IDEMPOTENT_ARE_YOU_SURE</em> asks the Wicket viewer for confirmation.</p>
<p>At this point the methods express the state changes, but we still need to control when each change is valid.</p>
<h3 id="bd-4-adding-business-rules" data-id="4-adding-business-rules">3.4. Adding Business Rules</h3>
<div class="bd-anchor" id="4-adding-business-rules"></div>
<p>Causeway associates supporting methods with domain members by naming convention. For example, <em>disableAssignTo()</em> controls the availability of <em>assignTo()</em>, while <em>validate0AssignTo()</em> validates parameter zero, which is the first parameter:</p>
<pre><code class="language-java">@MemberSupport
public String disableAssignTo() {
    return status == AssetStatus.AVAILABLE
        ? null
        : "Only available assets can be assigned";
}
@MemberSupport
public String validate0AssignTo(final String employee) {
    return employee == null || employee.isBlank()
        ? "Employee name is required"
        : null;
}
@MemberSupport
public String disableReturnToInventory() {
    return status == AssetStatus.ASSIGNED
        ? null
        : "Only assigned assets can be returned";
}
@MemberSupport
public String disableRetire() {
    if (status == AssetStatus.ASSIGNED) {
        return "Return the asset before retiring it";
    }
    return status == AssetStatus.RETIRED
        ? "The asset is already retired"
        : null;
}</code></pre>
<p>Here, <em>null</em> result permits the interaction. A message disables or rejects it and gives the viewer or API client a reason. Consequently, only an available asset can be assigned, only an assigned asset can be returned, and an assigned asset must be returned before retirement.</p>
<p><em>@MemberSupport</em> also lets Causeway validate that a supporting method still matches an existing domain member.</p>
<p><strong>String parameters are mandatory by default</strong>, so the framework rejects an empty <em>Employee</em> field before calling the action. The validator additionally handles whitespace-only input. These checks apply to Causeway-managed interactions; a direct Java call to <em>assignTo()</em> remains an ordinary method call and doesn&#8217;t invoke the supporting methods automatically.</p>
<p><strong>The business rules are part of the domain interaction, so both generated viewers can enforce them without duplicating conditions in a controller or page</strong>.</p>
<h3 id="bd-5-creating-an-asset-domain-service" data-id="5-creating-an-asset-domain-service">3.5. Creating an Asset Domain Service</h3>
<div class="bd-anchor" id="5-creating-an-asset-domain-service"></div>
<p>Entity actions operate on one existing asset. Creation and queries belong in a domain service backed by a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/the-persistence-layer-with-spring-data-jpa">Spring Data JPA repository</a>:</p>
<pre><code class="language-java">@Named("assets.Assets")
@DomainService
@Priority(PriorityPrecedence.EARLY)
public class Assets {
    private final RepositoryService repositoryService;
    private final AssetRepository assetRepository;
    @Inject
    public Assets(
        final RepositoryService repositoryService,
        final AssetRepository assetRepository) {
        this.repositoryService = repositoryService;
        this.assetRepository = assetRepository;
    }
    @Action(semantics = SemanticsOf.NON_IDEMPOTENT)
    @ActionLayout(promptStyle = PromptStyle.DIALOG_MODAL)
    public Asset create(
        @ParameterLayout(named = "Type") final AssetType type,
        @Parameter(maxLength = 80)
        @ParameterLayout(named = "Serial number") final String serialNumber) {
        return repositoryService.persist(new Asset(type, serialNumber.trim()));
    }
    @MemberSupport
    public String validate1Create(final String serialNumber) {
        if (serialNumber == null || serialNumber.isBlank()) {
            return "Serial number is required";
        }
        return assetRepository.findBySerialNumberIgnoreCase(serialNumber.trim()).isPresent()
            ? "An asset with this serial number already exists"
            : null;
    }
    // listAll() appears in the API section; findBySerialNumber() is in the complete project
}</code></pre>
<p><em>@DomainService</em> includes the service in the metamodel, <em>@Named</em> assigns the logical name used by the REST viewer, and <em>@Priority</em> places it early in Spring&#8217;s ordering.</p>
<p><em>RepositoryService</em> persists a new entity through the Causeway abstraction, whereas <em>AssetRepository</em> provides application-specific queries.</p>
<p>The repository itself uses Spring Data-derived queries, so it doesn&#8217;t need an implementation class:</p>
<pre><code class="language-java">public interface AssetRepository extends JpaRepository&lt;Asset, Long&gt; {
    List&lt;Asset&gt; findAllByOrderBySerialNumberAsc();
    List&lt;Asset&gt; findBySerialNumberContainingIgnoreCaseOrderBySerialNumberAsc(
        String serialNumber
    );
    Optional&lt;Asset&gt; findBySerialNumberIgnoreCase(String serialNumber);
}</code></pre>
<p>The method names describe the ordering, partial match, and case-insensitive exact match that Spring Data generates. <strong>This keeps persistence queries separate from the Causeway-facing service actions</strong>.</p>
<p>The supporting method name <em>validate1Create()</em> refers to parameter one, the second parameter of <em>create()</em>. It rejects blank serial numbers and existing case-insensitive duplicates before persistence. The database constraint additionally protects against concurrent exact-value duplicates. Enforcing case-insensitive uniqueness at the database level would require normalization, a suitable index, or a case-insensitive collation.</p>
<p>Finally, <em>menubars.layout.xml</em> explicitly places the three service actions in the <em>Assets</em> menu:</p>
<pre><code class="language-xml">&lt;!-- The menuBars root, namespaces, primary container, and non-Assets menus are omitted. --&gt;
&lt;mb3:menu&gt;
    &lt;mb3:named&gt;Assets&lt;/mb3:named&gt;
    &lt;mb3:section&gt;
        &lt;mb3:serviceAction objectType="assets.Assets" id="create"/&gt;
        &lt;mb3:serviceAction objectType="assets.Assets" id="findBySerialNumber"/&gt;
        &lt;mb3:serviceAction objectType="assets.Assets" id="listAll"/&gt;
    &lt;/mb3:section&gt;
&lt;/mb3:menu&gt;</code></pre>
<p>The <em>objectType</em> value matches the logical name of the service, and each <em>id</em> matches an action method. <em>@DomainService</em> registers the service in the metamodel, each <em>@Action</em> exposes a method, and the layout file determines this menu grouping.</p>
<h3 id="bd-6-entry-point" data-id="6-entry-point">3.6. Entry Point</h3>
<div class="bd-anchor" id="6-entry-point"></div>
<p>The application entry point activates the Causeway prototyping preset and then starts Spring Boot:</p>
<pre><code class="language-java">@SpringBootApplication
@Import(AppManifest.class)
public class AssetManagementApplication extends SpringBootServletInitializer {
    public static void main(final String[] args) {
        CausewayPresets.prototyping();
        SpringApplication.run(AssetManagementApplication.class, args);
    }
}</code></pre>
<p>So, let&#8217;s run the application.</p>
<h2 id="bd-running-the-internal-asset-management-application" data-id="running-the-internal-asset-management-application">4. Running the Internal Asset Management Application</h2>
<div class="bd-anchor" id="running-the-internal-asset-management-application"></div>
<p><strong>From the <em>apache-causeway</em> module, we can build and run the application</strong>:</p>
<pre><code class="language-bash">mvn clean install
mvn spring-boot:run</code></pre>
<p>Next, let&#8217;s open <em>http://localhost:8080/wicket/</em> and sign in with the demo credentials <em>admin</em> and <em>pass</em>.</p>
<h3 id="bd-1-demo-interaction" data-id="1-demo-interaction">4.1. Demo Interaction</h3>
<div class="bd-anchor" id="1-demo-interaction"></div>
<p>From the <em>Assets</em> menu, let&#8217;s choose <em>Create</em>, select <em>Laptop</em>, and enter <em>LT-001</em>. Causeway immediately renders the new object using its title, properties, field sets, and actions:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/causeway-available-asset.png"><img decoding="async" class="alignnone size-full wp-image-256161" src="https://www.baeldung.com/wp-content/uploads/2026/07/causeway-available-asset.png" alt="An available asset with the actions derived from its domain model." /></a></p>
<p>When we open <em>Assign To</em> and submit an empty employee, the mandatory parameter prevents the invocation, and the generated prompt displays the validation error:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/causeway-assignment-validation.png"><img decoding="async" class="alignnone size-full wp-image-256166" src="https://www.baeldung.com/wp-content/uploads/2026/07/causeway-assignment-validation.png" alt="The generated action prompt rejects a missing employee." /></a></p>
<p>After entering <em>Alice</em>, the action changes the asset to <em>ASSIGNED</em>. The same page now enables <em>Return To Inventory</em> and disables <em>Assign To</em> and <em>Retire</em>, reflecting the supporting methods without page-specific conditional code:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/causeway-assigned-asset-rules.png"><img decoding="async" class="alignnone size-full wp-image-256173" src="https://www.baeldung.com/wp-content/uploads/2026/07/causeway-assigned-asset-rules.png" alt="Assigned asset - The generated UI reflects the asset's new state and available transitions." /></a></p>
<p><strong>No asset-specific controller, form, or HTML template was required for this generic UI</strong>.</p>
<p>The prototyping preset, in-memory H2 database, automatic schema creation, and <em>SimpleRealm</em> credentials are appropriate only for this local example. A production application should use durable persistence, schema migrations, production security, and a production-ready Causeway configuration.</p>
<h3 id="bd-2-generated-api" data-id="2-generated-api">4.2. Generated API</h3>
<div class="bd-anchor" id="2-generated-api"></div>
<p>The service marks its list operation as safe:</p>
<pre><code class="language-java">@Action(semantics = SemanticsOf.SAFE)
public List&lt;Asset&gt; listAll() {
    return assetRepository.findAllByOrderBySerialNumberAsc();
}</code></pre>
<p><strong>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://causeway.apache.org/vro/3.6.0/about.html">RESTful Objects viewer</a> maps this action to an authenticated <em>GET</em></strong>. With the application still running, we can invoke it using the demo account:</p>
<pre><code class="language-bash">$ curl -i -u admin:pass \
    -H 'Accept: application/json' \
    'http://localhost:8080/restful/services/assets.Assets/actions/listAll/invoke'</code></pre>
<p>The URL contains the logical service name <em>assets.Assets</em> and the action identifier <em>listAll</em>. The verified invocation returns <em>200 OK</em> and reaches the domain service rather than exposing <em>AssetRepository</em> directly.</p>
<p>The response isn&#8217;t a raw JSON array. It is a RESTful Objects <em>DomainObjectList</em> representation containing links, relation types, and media-type profiles that allow a client to navigate the domain.</p>
<p>This is useful for generic clients and integrations, but <strong>before treating it as a public, versioned API, we should deliberately restrict the exposed surface or define a client-specific representation</strong>.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, <strong>we began with the Causeway domain-first mental model and built an internal asset management application</strong>. Specifically, we mapped a JPA entity, expressed lifecycle behavior as actions, enforced valid transitions with supporting methods, and added a domain service for creation and queries.</p>
<p>After that, we ran the generated Wicket UI and invoked the RESTful Objects API. Causeway is a strong option when a behavior-rich domain and rapid delivery of consistent management screens outweigh the need for a fully custom interface.</p>
<p>The complete source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/apache-causeway">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-causeway">Introduction to Apache Causeway</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/963082190/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/963082190/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/963082190/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Boot-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/963082190/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/963082190/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/963082190/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/apache-causeway#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/apache-causeway/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/~/963082190/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-656</feedburner:origLink>
		<title>Java Weekly, Issue 656</title>
		<link>https://feeds.feedblitz.com/~/963067178/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/963067178/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sat, 25 Jul 2026 18:06:00 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204463</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>A Java movie? Cool. And Spring Cloud Contract has a new home.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/963067178/0/baeldung">Java Weekly, Issue 656</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/963067178/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/963067178/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/963067178/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/963067178/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/963067178/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-656#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-656/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://inside.java/2026/07/18/the-java-documentary/">&gt;&gt; Java &#8211; The Documentary</a></strong> [<span style="color: #993300;">inside.java</span>]</p>
<p>A cinematic look back at <strong>how Java got here</strong>, from Sun, through the strategic pivots that could easily have gone the other way, to a platform running on billions of devices.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/in-house-llm-serving-at-netflix-a5a8e799ea2c">&gt;&gt; In-House LLM Serving at Netflix</a></strong> [<span style="color: #993300;">netflixtechblog.com</span>]</p>
<p>Rather than consuming LLMs through hosted APIs, <strong>Netflix runs the whole stack in-house</strong>, and this write-up walks through the four decisions that shaped it. Not easily reproducible of course but still an interesting read.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/security-baked-into-jvm/2/" target="_blank" rel="noopener"><strong>Security Baked Into the JVM: the Safe Codebase Audit Pipeline</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/first-test-of-java-on-banana-pi-arm-and-risc-v-plus-a-blinking-led-with-pi4j/" target="_blank" rel="noopener"><strong>First Test of Java on Banana Pi (ARM and RISC-V), Plus a Blinking LED with Pi4J</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/toward-a-durable-spring-petclinic/" target="_blank" rel="noopener"><strong>Toward a Durable Spring PetClinic</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/how-we-beat-hotspot-performance-by-cheating-but-not-like-that/" target="_blank" rel="noopener"><strong>How We Beat HotSpot Performance (By Cheating, But Not Like That)</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/warm-up-fast-run-lean-vertical-scaling-for-java-on-kubernetes-with-azul-prime-and-kedify/" target="_blank" rel="noopener"><strong>Warm Up Fast, Run Lean: Vertical Scaling for Java on Kubernetes with Azul Prime and Kedify</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/20/quality-heads-up/" target="_blank" rel="noopener"><strong>Quality Outreach Heads-up &#8211; JDK 27: Default G1 in All Environments</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/16/identify-jdk-value-classes-candidates/" target="_blank" rel="noopener"><strong>Identifying JDK Value Class Candidates</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blogs.oracle.com/java/transitioning-java-to-more-frequent-security-updates" target="_blank" rel="noopener"><strong>Transitioning Java to more frequent security updates</strong></a> [<span style="color: #800000;">oracle.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/fairness-in-benchmarking/" target="_blank" rel="noopener"><strong>Improving a Competitor&#8217;s Numbers (On Purpose)</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-shim/" target="_blank" rel="noopener"><strong>Patch Java classes at build time with Quarkus Shim</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://nurkiewicz.com/2026/07/simplest-interpreter-write-yourself-a-compiler-part-i.html" target="_blank" rel="noopener"><strong>The simplest interpreter: Write yourself a compiler, Part I</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://nurkiewicz.com/2026/07/arithmetic-interpreter-write-yourself-a-compiler.html" target="_blank" rel="noopener"><strong>Arithmetic interpreter: Part II</strong></a> [<span style="color: #800000;">nurkiewicz.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.vojtechruzicka.com/owasp-top-10-2025/" target="_blank" rel="noopener"><strong>OWASP Top 10 2025</strong></a> [<span style="color: #800000;">vojtechruzicka.com</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/07/19/podcast-062/" target="_blank" rel="noopener"><strong>Episode 62 “JDK 27 + Valhalla, Now! + Hackathon” [IJN]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/07/16/a-bootiful-podcast-russ-miles/" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Russ Miles on Safer, More Productive Interactions with AI</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.infoq.com/presentations/otel-slm-ai/" target="_blank" rel="noopener"><strong>From OTEL to SLMs: Distilling Frontier Model Behaviour from Production Telemetry</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://foojay.io/today/boxlang-aws-azure-and-google-secrets-manager-module-released/" target="_blank" rel="noopener"><strong>BoxLang AWS, Azure, and Google Secrets Manager Module Released</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://github.com/quarkusio/quarkus/releases/tag/3.38.0" target="_blank" rel="noopener"><strong>Quarkus 3.38.0</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.27.5" target="_blank" rel="noopener"><strong>3.27.5</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/4.5.31" target="_blank" rel="noopener"><strong>Vert.x 4.5.31</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.4.4" target="_blank" rel="noopener"><strong>Elasticsearch 9.4.4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.3.8" target="_blank" rel="noopener"><strong>9.3.8</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v8.19.19" target="_blank" rel="noopener"><strong>8.19.19</strong></a> [<span style="color: #800000;">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.1" target="_blank" rel="noopener"><strong>Zuul 4.1.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.0" target="_blank" rel="noopener"><strong>4.1.0</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.10" target="_blank" rel="noopener"><strong>Micronaut Core 5.1.10</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.9" target="_blank" rel="noopener"><strong>5.1.9</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.8" target="_blank" rel="noopener"><strong>5.1.8</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.7" target="_blank" rel="noopener"><strong>5.1.7</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.1" target="_blank" rel="noopener"><strong>Helidon 4.5.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/3.2.19" target="_blank" rel="noopener"><strong>3.2.19</strong></a> [<span style="color: #800000;">github.com/helidon-io</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">2.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/slashdata-fs-9lzq">&gt;&gt; The New Survey from SlashData is Out</a></strong></p>
<p>And yes, as always, there&#8217;s a prize <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;" /></p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-656">Java Weekly, Issue 656</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/963067178/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/963067178/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/963067178/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/963067178/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/963067178/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/963067178/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-656#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-656/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/~/963067178/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-generate-diagrams-uml</feedburner:origLink>
		<title>Generating Diagrams From Java Code</title>
		<link>https://feeds.feedblitz.com/~/962183936/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/962183936/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Emanuel Trandafir]]></dc:creator>
		<pubDate>Thu, 23 Jul 2026 15:40:59 +0000</pubDate>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[OpenAPI]]></category>
		<category><![CDATA[popular]]></category>
		<category><![CDATA[Static Analysis]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-generate-diagrams-uml</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to generate UML and architecture diagrams from Java code using static analysis, Spring Modulith, and AI-powered workflows to improve code reviews and documentation.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/962183936/0/baeldung">Generating Diagrams From Java Code</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/962183936/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/962183936/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/962183936/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/962183936/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/962183936/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-generate-diagrams-uml#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-generate-diagrams-uml/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>While design tools and development aren&#8217;t always compatible, being able to programmatically generate schematic images and diagrams can be invaluable.</p>
<p>In this tutorial, we&#8217;ll explore a few approaches for generating UML diagrams from Java code. First, we&#8217;ll start with static analysis tools that scan source or bytecode to produce class and dependency diagrams automatically. Then, we&#8217;ll look at tools and frameworks that help us expose architectural boundaries in one way or another. Finally, we&#8217;ll explore an agent-based approach that uses an LLM to turn a code diff into a diagram and a human-readable summary.</p>
<p>To facilitate and expand the overall understanding, we&#8217;ll use these various approaches to generate different types of diagrams for a demo application. It&#8217;s built with a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-vertical-slice-architecture">vertical slice architecture</a>, modeling a domain that simulates a blogging platform like Baeldung itself.</p>
<h2 id="bd-why-uml-diagrams" data-id="why-uml-diagrams">2. Why UML Diagrams</h2>
<div class="bd-anchor" id="why-uml-diagrams"></div>
<p>Pull requests have been getting larger. AI-assisted coding tools make it easy to touch a dozen classes across several packages in a single commit. New interfaces extracted, dependencies rewired, responsibilities moved between layers — all generated in minutes rather than hours.</p>
<p>As a result, <strong>code review is increasingly becoming the bottleneck</strong>. Mainly, that&#8217;s because reviewers are expected to reconstruct the shape of a change from the diff alone. However, a diff that&#8217;s readable line by line doesn&#8217;t necessarily make the change easy to understand. Scrolling through additions and deletions gives little sense of which classes now depend on which. Also, components can often quietly cross a boundary they shouldn&#8217;t.</p>
<p>This is exactly the kind of problem diagrams are designed to solve. <strong>A good diagram can provide the reviewer with a structural summary before they read a single line of the methods section</strong>. It shows which components were added, which relationships changed, and where the blast radius of the change actually is. Generating that diagram from the before-and-after state of a change — or straight from the diff — turns a wall of text into something you can take in at a glance.</p>
<h2 id="bd-static-code-analysis" data-id="static-code-analysis">3. Static Code Analysis</h2>
<div class="bd-anchor" id="static-code-analysis"></div>
<p>The most deterministic way to get a diagram is to generate it directly from the code — no manual modeling, no LLM in the loop, just a tool that walks the class files or source tree and emits a diagram description. These tools work by parsing imports, package structure, and class relationships, then rendering the result as PlantUML or Mermaid.</p>
<p><strong>IntelliJ IDEA ships with built-in diagram support that can visualize a package, a module, or the classes touched by a selected diff</strong>.</p>
<p>Let&#8217;s see the steps to generate a Java class diagram directly from a classic IDE:</p>
<ol>
<li>Right-click the root directory of the project, or a Java package</li>
<li>Select <em>Diagrams</em> &gt; <em>Show Diagrams</em></li>
<li>Select <em>Java Classes</em> as the type of diagram we want to generate</li>
</ol>
<p>This is all done in a single subwindow:</p>
<p><img decoding="async" class="aligncenter size-full wp-image-255513" src="https://www.baeldung.com/wp-content/uploads/2026/07/intellij_diagrams.png" alt="" /></p>
<p>IntelliJ should now render the class diagram in a separate tab. The diagram is interactive rather than exported by default, but it can be saved as a .<em>png</em>, <em>.svg</em>, or <em>.puml</em> file via the toolbar&#8217;s export button:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/intellij_class_diagram.png"><img decoding="async" class="aligncenter size-full wp-image-255512" src="https://www.baeldung.com/wp-content/uploads/2026/07/intellij_class_diagram.png" alt="" /></a></p>
<p>Alternatively, <strong>we can use an IDE plugin such as PlantUML Parser</strong>, which lets us right-click any Java file or directory, select <em>PlantUML Parser</em>, and generate a <em>.puml</em> file directly:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/puml_parser.png"><img decoding="async" class="aligncenter size-full wp-image-255514" src="https://www.baeldung.com/wp-content/uploads/2026/07/puml_parser.png" alt="" /></a></p>
<p>Needless to say, there are plenty of other options too. Teams still relying on Eclipse have their own options too, with older plugins like AmaterasUML solving the same problem within that IDE.</p>
<p>Outside the IDE, <strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/juanmf/Java2PlantUML">Java2PlantUML</a> takes a similar approach from the command line, scanning compiled bytecode or source and producing a <em>.puml</em> file</strong>, which makes it a good fit for CI pipelines.</p>
<h2 id="bd-framework-level-architecture-sources" data-id="framework-level-architecture-sources">4. Framework-Level Architecture Sources</h2>
<div class="bd-anchor" id="framework-level-architecture-sources"></div>
<p>Static analysis tools have no notion of intent — they see classes and packages, not architectural boundaries. Some frameworks, however, already know exactly where those boundaries are, because we declared them explicitly.</p>
<p>Rather than reverse-engineering structure from bytecode, <strong>we can treat these framework-level declarations as ground truth for diagrams</strong>. Some of these frameworks go a step further too, letting us highlight and even enforce those boundaries as part of the build itself.</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-modulith">Spring Modulith</a> is a good example of this. Specifically, <strong>Spring Modulith lets us organize a Spring Boot application into explicit modules</strong>, one per top-level package, and verifies at build time that modules only interact through their public API:</p>
<pre><code class="language-java">class ModularityTests {
    ApplicationModules modules = ApplicationModules.of(Application.class);
    @Test
    void verifiesModularStructure() {
        modules.verify();
    }
    @Test
    void createModuleDocumentation() {
        new Documenter(modules).writeDocumentation();
    }
}</code></pre>
<p>As we can see, apart from just verifying the structure of modules, we can also generate documentation directly from it. Running <em>createModuleDocumentation()</em>, produces a set of PlantUML diagrams in <em>target/spring-modulith-docs</em>:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/spring_modulith_diagram.png"><img decoding="async" class="aligncenter size-full wp-image-255518" src="https://www.baeldung.com/wp-content/uploads/2026/07/spring_modulith_diagram.png" alt="" /></a>
<br class="yoast-text-mark" /><strong>Other frameworks such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-cloud-stream">Spring Cloud Stream</a> enable us to declaratively define the flow of data through the application in a single, centralized configuration file</strong>. Since these bindings live in one place rather than scattered across annotations, we can easily read that file and generate a sequence or flow diagram in Mermaid or PlantUML to show how data moves between services.</p>
<p>While these frameworks help us document what&#8217;s happening inside a single service, they don&#8217;t tell us much about how that service talks to the outside world — that&#8217;s where contracts come in. Scanning contract files can be a good way to understand how different services connect. This includes <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-rest-openapi-documentation">OpenAPI</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-spring-doc-asyncapi-springwolf">AsyncAPI</a> docs, or Spring Cloud Contract and Pact contracts, if we implement contract testing.</p>
<h2 dir="auto" id="bd-agent-in-the-loop-generation" data-id="agent-in-the-loop-generation">5. Agent-in-the-Loop Generation</h2>
<div class="bd-anchor" id="agent-in-the-loop-generation"></div>
<p>Static analysis and framework-level sources both have limitations. In essence, they can only describe structure that already exists in a machine-readable form. Neither can tell us why a change was made, or summarize it in plain language for the reviewer.</p>
<p>This is where an LLM-based agent fits in. <strong>Rather than parsing code deterministically, we can trigger a lightweight agent</strong> like Claude Haiku. It reads a diff directly and produces both a diagram and a short, human-readable summary of what changed. There are two natural places to wire this in.</p>
<p>The first is as a step in a CI pipeline, e.g.,<strong> a GitHub Actions or GitLab CI job that runs on every pull request, diffs against the target branch, and posts the generated diagram and summary as a PR comment</strong>:</p>
<pre><code class="language-yaml">- name: Generate PR diagram and summary
  run: |
    git diff origin/main...HEAD &gt; diff.patch
    claude -p "Given this diff, generate a Mermaid diagram of the
    affected components and a 2-3 sentence summary of the change." \
    diff.patch &gt; pr-summary.md</code></pre>
<p>On the other hand, <strong>we can also use a pre-commit hook that uses a local AI harness like Claude Code or the Copilot CLI, so the documentation is generated and committed alongside the change itself</strong>.</p>
<p>Let&#8217;s see a simplified version of such a hook, which we can save as <em>.githooks/pre-commit</em>:</p>
<pre><code class="language-bash">#!/bin/bash
DOCS_FILE="docs/$(git branch --show-current | tr '/' '-').md"
DIFF=$(git diff --cached | head -c 4000)
[ -z "$DIFF" ] &amp;&amp; exit 0
PROMPT="Generate a brief Markdown doc for this commit: a Summary,
Modified Components list, and a Mermaid Component Diagram.
Changes:
${DIFF}"
claude --model claude-haiku-4-5-20251001 -p "$PROMPT" --output-format text &gt; "$DOCS_FILE" \
  &amp;&amp; git add "$DOCS_FILE"</code></pre>
<p>On every commit that touches the project, the hook performs several actions:</p>
<ul>
<li>diffs the staged changes</li>
<li>asks Claude Haiku for a short summary and a Mermaid component diagram</li>
<li>stages the resulting Markdown file alongside the commit</li>
</ul>
<p>Let&#8217;s commit a few local changes to the codebase and see the pre-commit hook in action:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/ai_generated_diagram.png"><img decoding="async" class="aligncenter size-full wp-image-255523" src="https://www.baeldung.com/wp-content/uploads/2026/07/ai_generated_diagram.png" alt="" /></a></p>
<p>As expected, <strong>the model summarizes the changes and generates a simple diagram of the key components affected by the recent commit</strong>, making the reviewer&#8217;s job easier.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored a few ways to generate diagrams from Java code to make code review easier as diffs grow larger and harder to reason about from raw code alone. Initially, we started with static code analysis, using tools like the built-in IntelliJ diagram support, the PlantUML Parser plugin, and Java2 PlantUML to generate diagrams directly from source or bytecode.</p>
<p>After that, we looked at framework-level architecture sources, where tools like Spring Modulith and Spring Cloud Stream let us treat already-declared boundaries and data flows as ground truth for diagrams. Then, we also discussed how contracts like OpenAPI, AsyncAPI, or Pact can document the interactions between services.</p>
<p>Finally, we explored an agent-in-the-loop approach, triggering a lightweight AI agent from a CI pipeline or a pre-commit hook to turn a diff into a diagram and a human-readable summary.</p>
<p>As always, the code for this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/patterns-modules/vertical-slice-architecture">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-generate-diagrams-uml">Generating Diagrams From Java Code</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/962183936/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/962183936/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/962183936/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/962183936/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/962183936/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/962183936/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-generate-diagrams-uml#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-generate-diagrams-uml/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/~/962183936/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/spring-integrating-local-llms-spring-ai-lm-studio</feedburner:origLink>
		<title>Integrating Local LLMs with Spring AI Using LM Studio</title>
		<link>https://feeds.feedblitz.com/~/961624313/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/961624313/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Manfred Ng]]></dc:creator>
		<pubDate>Wed, 22 Jul 2026 11:24:54 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[popular]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-integrating-local-llms-spring-ai-lm-studio</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 to use a locally hosted chat model and an embedding model with Spring AI in LM Studio.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/961624313/0/baeldung">Integrating Local LLMs with Spring AI Using LM Studio</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/961624313/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/961624313/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/961624313/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/961624313/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/961624313/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-integrating-local-llms-spring-ai-lm-studio#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-integrating-local-llms-spring-ai-lm-studio/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-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>Large language models (LLMs) have emerged as a key component of modern applications. While the most capable models are proprietary and hosted in a cloud environment. Accessing these models requires an internet connection and sending our application data to external providers.</p>
<p>For applications that process privacy-sensitive information or need to operate offline, these requirements may not be acceptable. As a result, there is a growing demand for integrating local LLMs into applications.</p>
<p>In this tutorial, we&#8217;ll learn how to integrate <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai">Spring AI</a> with LM Studio and configure Spring AI to communicate with locally hosted chat and embedding models.</p>
<h2 id="bd-lm-studio" data-id="lm-studio">2. LM Studio</h2>
<div class="bd-anchor" id="lm-studio"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://lmstudio.ai/">LM Studio</a> is an application that can download, manage, and host LLMs and embedding models in our local environment.</strong> Its graphical interface allows us to browse and download models from public repositories with ease:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/lm-studio-select-model.jpg"><img decoding="async" class="alignnone size-full wp-image-254407" src="https://www.baeldung.com/wp-content/uploads/2026/07/lm-studio-select-model.jpg" alt="" /></a></p>
<p><strong>Besides model management, it provides an API server that exposes the loaded models via REST-based APIs.</strong> This allows AI frameworks such as Spring AI to interact with local models.</p>
<p>There are other applications, such as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-ollama-chatgpt-like-chatbot">Ollama</a>, providing similar capabilities, but with different focuses:</p>
<table class="table-styled" style="border-collapse: collapse;width: 80%;height: 168px">
<tbody>
<tr style="height: 24px">
<th style="width: 19.9557%;height: 24px"></th>
<th style="width: 39.9113%;height: 24px">LM Studio</th>
<th style="width: 39.9113%;height: 24px">Ollama</th>
</tr>
<tr style="height: 24px">
<td style="width: 19.9557%;height: 24px">User Interface</td>
<td style="width: 39.9113%;height: 24px">Graphical desktop application</td>
<td style="width: 39.9113%;height: 24px">Primarily command-line interface (CLI)</td>
</tr>
<tr style="height: 48px">
<td style="width: 19.9557%;height: 48px">Model Discovery</td>
<td style="width: 39.9113%;height: 48px">Browse and download models from the GUI</td>
<td style="width: 39.9113%;height: 48px">Download models via CLI, without an interface to browse models.</td>
</tr>
<tr style="height: 24px">
<td style="width: 19.9557%;height: 24px">Supported APIs</td>
<td style="width: 39.9113%;height: 24px">OpenAI-compatible and Anthropic-compatible</td>
<td style="width: 39.9113%;height: 24px"><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-ollama-hugging-face-models">Native API, </a>OpenAI-compatible, and Anthropic-compatible</td>
</tr>
<tr style="height: 48px">
<td style="width: 19.9557%;height: 48px">Best fit</td>
<td style="width: 39.9113%;height: 48px">Non-technical users without CLI experience, model experimentation</td>
<td style="width: 39.9113%;height: 48px">Technical users, application integration</td>
</tr>
</tbody>
</table>
<p>In general, LM Studio offers a more user-friendly experience for exploring and experimenting with local models, while Ollama is optimized for command-line workflows.</p>
<h2 id="bd-maven-dependency" data-id="maven-dependency">3. Maven Dependency</h2>
<div class="bd-anchor" id="maven-dependency"></div>
<p><strong>LM Studio provides both OpenAI-compatible and Anthropic-compatible API endpoints.</strong></p>
<p>To integrate with Spring AI, we could either pick the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-openai">Spring AI OpenAI</a> dependency or the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-anthropic">Spring AI Anthropic</a> dependency. However, Spring AI Anthropic does not support embedding models.</p>
<p>Therefore, we&#8217;ll adopt the Spring AI OpenAI dependency in this tutorial:</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;</code></pre>
<p>Note that Spring AI 2.0.0 is built on Spring Framework 7 and is intended for Spring Boot 4.</p>
<h2 id="bd-configuration" data-id="configuration">4. Configuration</h2>
<div class="bd-anchor" id="configuration"></div>
<p>Let&#8217;s configure our <em>application.yml</em> to adopt the local models in LM Studio:</p>
<pre><code class="language-yaml">spring:
  ai:
    openai:
      api-key: dummy
      base-url: http://localhost:1234/v1
      chat:
        model: google/gemma-4-e4b
      embedding:
        model: text-embedding-embeddinggemma-300m</code></pre>
<p>There are several properties worth mentioning:</p>
<p><strong>api-key:</strong> We don&#8217;t really need a real API key when connecting to the local model. Thus, we just need to fill in an arbitrary value.</p>
<p><strong>base-url:</strong> We could find the base URL when we start the local server in LM Studio. If we use the Spring AI OpenAI dependency, remember to append <em>/v1</em> to the base URL.</p>
<p><strong>model:</strong> We only need to specify the model when multiple models are loaded in LM Studio. Otherwise, we can omit this model configuration.</p>
<h2 id="bd-sample-endpoints" data-id="sample-endpoints">5. Sample Endpoints</h2>
<div class="bd-anchor" id="sample-endpoints"></div>
<p><strong>Now, let&#8217;s create two endpoints: One for the chat model and one for the embedding model.</strong></p>
<p>The controller basically delegates the work to 2 services that work with the models:</p>
<pre><code class="language-java">@RequestMapping("/lm-studio")
public class LmStudioController {
    private final ChatService chatService;
    private final EmbeddingService embeddingService;
    public LmStudioController(ChatService chatService, EmbeddingService embeddingService) {
        this.chatService = chatService;
        this.embeddingService = embeddingService;
    }
    @PostMapping("/chat")
    @ResponseStatus(HttpStatus.OK)
    public String chat(@RequestBody String prompt) {
        return chatService.chat(prompt);
    }
    @PostMapping("/embeddings")
    @ResponseStatus(HttpStatus.OK)
    public EmbeddingResponse getEmbeddings(@RequestBody String text) {
        return embeddingService.getEmbeddings(text);
    }
}</code></pre>
<p>The <em>ChatService</em> uses the <em>ChatClient</em> to send a prompt to the configured chat model:</p>
<pre><code class="language-java">@Service
public class ChatService {
    private final ChatClient chatClient;
    public ChatService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    public String chat(String prompt) {
        return chatClient.prompt()
          .user(userMessage -&gt; userMessage.text(prompt))
          .call()
          .content();
    }
}</code></pre>
<p>Likewise, the <em>EmbeddingService</em> sends the text to the configured embedding model:</p>
<pre><code class="language-java">@Service
public class EmbeddingService {
    private final EmbeddingModel embeddingModel;
    public EmbeddingService(EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
    }
    public EmbeddingResponse getEmbeddings(String text) {
        EmbeddingRequest request = new EmbeddingRequest(List.of(text), null);
        return embeddingModel.call(request);
    }
}</code></pre>
<h2 id="bd-test-run" data-id="test-run">6. Test Run</h2>
<div class="bd-anchor" id="test-run"></div>
<p><strong>Before starting our Spring Boot application, make sure the LM Studio local server is running with both the chat model and embedding model loaded:</strong></p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/07/lm-studio-server.jpg"><img loading="lazy" decoding="async" class="alignnone wp-image-254409" src="https://www.baeldung.com/wp-content/uploads/2026/07/lm-studio-server.jpg" alt="" width="1067" height="337" /></a></p>
<p>Now, let&#8217;s verify both the chat and embedding endpoints by initiating HTTP requests via <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/curl-rest">curl</a>.</p>
<p>First, we send a request to the chat endpoint:</p>
<pre><code class="language-bash">curl -X POST http://localhost:8080/lm-studio/chat 
  -d "Tell me who you are"</code></pre>
<p>The request is processed by the chat model currently loaded in LM Studio. In this example, we&#8217;ve loaded Google&#8217;s Gemma 4 model, so the endpoint returns the following response:</p>
<pre><code class="language-plaintext">I am Gemma 4, a Large Language Model developed by Google DeepMind.</code></pre>
<p>Next, we use the chat model response as the request payload for the embedding endpoint:</p>
<pre><code class="language-bash">curl -X POST http://localhost:8080/lm-studio/embeddings 
  -d "I am Gemma 4, a Large Language Model developed by Google DeepMind."</code></pre>
<p>The endpoint returns the embedding response. Since the full response is large, we&#8217;ll only show the model name and the first few elements:</p>
<pre><code class="language-json">{
  "metadata": {
    "model": "text-embedding-embeddinggemma-300m:2"
  },
  "result": {
    "index": 0,
    "output": [
      -0.07699633,
      -0.030751443,
      0.019093497,
      ...
    ]
  }
}</code></pre>
<p>These results confirm that Spring AI successfully invokes the locally hosted chat model and the embedding model via LM Studio&#8217;s OpenAI-compatible API.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we learned how to integrate Spring AI with LM Studio to use a locally hosted chat model and embedding model. We configured Spring AI to work with the open-source models via LM Studio&#8217;s OpenAI-compatible API.</p>
<p>LM Studio is a perfect choice for running AI models without relying on a cloud provider. This approach keeps your data within the local environment and enables it to operate without an internet connection. These make it ideal for privacy-sensitive applications.</p>
<p>As always, the complete code examples are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-lm-studio">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-integrating-local-llms-spring-ai-lm-studio">Integrating Local LLMs with Spring AI Using LM Studio</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/961624313/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/961624313/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/961624313/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/961624313/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/961624313/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/961624313/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-integrating-local-llms-spring-ai-lm-studio#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-integrating-local-llms-spring-ai-lm-studio/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/~/961624313/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-mysql-sqlexception-incorrect-string-value</feedburner:origLink>
		<title>How to Fix MySQL java.sql.SQLException: Incorrect string value</title>
		<link>https://feeds.feedblitz.com/~/961624316/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/961624316/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Hiks Gerganov]]></dc:creator>
		<pubDate>Wed, 22 Jul 2026 11:19:39 +0000</pubDate>
				<category><![CDATA[Persistence]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-mysql-sqlexception-incorrect-string-value</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to diagnose and fix the java.sql.SQLException: Incorrect string value error in MySQL.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/961624316/0/baeldung">How to Fix MySQL <em>java.sql.SQLException: Incorrect string value</em></a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/961624316/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/961624316/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-03-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/961624316/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/961624316/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/961624316/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-mysql-sqlexception-incorrect-string-value#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-mysql-sqlexception-incorrect-string-value/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-03-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>MySQL is one of the most common relational database systems. Consequently, integrating it with Java applications via the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-connect-mysql">MySQL Connector/J JDBC</a> driver is a fairly standard task. This enables working with the database directly from Java. Yet, even when handling pure text data, we might encounter errors such as <em>java.sql.SQLException: Incorrect string value</em>.</p>
<p>In this tutorial, <strong>we explain how to fix the <em>Incorrect string value</em> error in Java applications using MySQL</strong>. First, we explore what the exception means and why it occurs. Then, we examine configuration changes at different levels as a means to work around the problem. Finally, we provide other practical solutions such as MySQL schema updates, JDBC configuration, and server settings.</p>
<h2 id="bd-understanding-the-error" data-id="understanding-the-error">2. Understanding the Error</h2>
<div class="bd-anchor" id="understanding-the-error"></div>
<p>Before attempting to fix <em>java.sql.SQLException: Incorrect string value</em>, let&#8217;s understand how and when the error appears.</p>
<h3 id="bd-1-general-description" data-id="1-general-description">2.1. General Description</h3>
<div class="bd-anchor" id="1-general-description"></div>
<p>Initially, we can explore an example error text. To that end, let&#8217;s execute an INSERT or UPDATE operation with problematic characters:</p>
<pre><code class="language-java">java.sql.SQLException: Incorrect string value: '\x00\x00\x27\x4C' for column 'textcol'</code></pre>
<p>In this case, we see that the <em>textcol</em> column can&#8217;t handle the string value <em>\x00\x00\x27\x4C</em>. In short, MySQL rejected the input. One of the main reasons for this is the fact that <strong>the column character set can&#8217;t represent the provided data</strong>.</p>
<h3 id="bd-2-character-encoding-mismatch" data-id="2-character-encoding-mismatch">2.2. Character Encoding Mismatch</h3>
<div class="bd-anchor" id="2-character-encoding-mismatch"></div>
<p>So, <strong>the way that Java handles Unicode isn&#8217;t entirely compatible with the default or current MySQL <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-char-encoding">character set</a></strong>.</p>
<p>Specifically, Java uses full UTF-8 encoding, where more than three bytes can represent a single character. On the other hand, <strong>the default <em>utf8</em> character set in MySQL supports a maximum of 3 bytes per character</strong>. In this situation, if we try to store a 4-byte character, such as an emoji, within the database, we might encounter the exception in question.</p>
<p>For instance, let&#8217;s consider inserting a string containing an emoji into a table as a common action:</p>
<pre><code class="language-sql">INSERT INTO messages (text) VALUES ('&#x274c;');</code></pre>
<p>If the column uses <em>utf8</em>, MySQL rejects the value because of its inability to store 4-byte characters.</p>
<h2 id="bd-mysql-character-set-configuration-and-conversion" data-id="mysql-character-set-configuration-and-conversion">3. MySQL Character Set Configuration and Conversion</h2>
<div class="bd-anchor" id="mysql-character-set-configuration-and-conversion"></div>
<p>In short, the most reliable solution is often to configure MySQL, the database, and its components for full Unicode support.</p>
<h3 id="bd-1-change-utf8mb4-to-utf8" data-id="1-change-utf8mb4-to-utf8">3.1. Change <em>utf8mb4</em> to <em>utf8</em></h3>
<div class="bd-anchor" id="1-change-utf8mb4-to-utf8"></div>
<p>MySQL provides the <em>utf8mb4</em> character set, which supports all Unicode characters, including emojis and similar:</p>
<pre><code class="language-sql">ALTER DATABASE mydb 
CHARACTER SET = utf8mb4 
COLLATE = utf8mb4_unicode_ci;</code></pre>
<p>This command ensures the database itself can store 4-byte characters correctly.</p>
<h3 id="bd-2-convert-tables" data-id="2-convert-tables">3.2. Convert Tables</h3>
<div class="bd-anchor" id="2-convert-tables"></div>
<p><strong>Existing tables may still use incompatible encodings</strong>, even after updating the database configuration:</p>
<pre><code class="language-sql">ALTER TABLE xtab 
CONVERT TO CHARACTER SET utf8mb4 
COLLATE utf8mb4_unicode_ci;</code></pre>
<p>Thus, we should update all tables with the new encoding.</p>
<h3 id="bd-3-update-columns" data-id="3-update-columns">3.3. Update Columns</h3>
<div class="bd-anchor" id="3-update-columns"></div>
<p>In some cases, we might need to modify individual columns:</p>
<pre><code class="language-sql">ALTER TABLE xtab 
MODIFY text_column TEXT 
CHARACTER SET utf8mb4;</code></pre>
<p>To reiterate, <strong>the database, all tables, and each column must all use <em>utf8mb4</em> exclusively</strong>.</p>
<h2 id="bd-jdbc-configuration-issues" data-id="jdbc-configuration-issues">4. JDBC Configuration Issues</h2>
<div class="bd-anchor" id="jdbc-configuration-issues"></div>
<p>Even after configuring MySQL correctly, we should set up the JDBC connection, so it also handles Unicode properly.</p>
<h3 id="bd-1-configure-connection-url" data-id="1-configure-connection-url">4.1. Configure Connection URL</h3>
<div class="bd-anchor" id="1-configure-connection-url"></div>
<p>The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jdbc-url-format">JDBC URL</a> can include parameters to enforce Unicode behavior:</p>
<pre><code class="language-java">jdbc:mysql://localhost:3306/mydb
  ?useUnicode=true
  &amp;characterEncoding=utf8</code></pre>
<p>These options ensure that <strong>Java correctly encodes and transmits string data to MySQL</strong>.</p>
<h3 id="bd-2-connectorj-behavior" data-id="2-connectorj-behavior">4.2. Connector/J Behavior</h3>
<div class="bd-anchor" id="2-connectorj-behavior"></div>
<p>Modern versions of Connector/J automatically negotiate character sets with the MySQL server. However, <strong>using older drivers or misconfigurations may still lead to encoding mismatches</strong>.</p>
<p>Therefore, explicitly setting encoding or upgrading the driver can help avoid inconsistencies.</p>
<h2 id="bd-verifying-mysql-server-settings" data-id="verifying-mysql-server-settings">5. Verifying MySQL Server Settings</h2>
<div class="bd-anchor" id="verifying-mysql-server-settings"></div>
<p>In addition to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/sql/check-table-existence-particular-schema">schema</a> and connection settings, server-level configuration plays a role in encoding behavior.</p>
<h3 id="bd-1-check-character-set-variables" data-id="1-check-character-set-variables">5.1. Check Character Set Variables</h3>
<div class="bd-anchor" id="1-check-character-set-variables"></div>
<p>Let&#8217;s inspect the current MySQL character set configuration:</p>
<pre><code class="language-sql">SHOW VARIABLES LIKE 'character_set%';</code></pre>
<p>Ideally, the server, database, and connection should all use <em>utf8mb4</em> to ensure compatibility.</p>
<h3 id="bd-2-update-mysql-configuration" data-id="2-update-mysql-configuration">5.2. Update MySQL Configuration</h3>
<div class="bd-anchor" id="2-update-mysql-configuration"></div>
<p>If necessary, <strong>we might need to update the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/linux/mysql-find-my-cnf-command-line">MySQL configuration file</a> to enforce the needed defaults</strong>:</p>
<pre><code class="language-ini">[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci</code></pre>
<p>After making changes, we should restart the MySQL service for them to take effect. At this point, the server should only employ <em>utf8mb4</em> as its encoding.</p>
<h2 id="bd-common-pitfalls" data-id="common-pitfalls">6. Common Pitfalls</h2>
<div class="bd-anchor" id="common-pitfalls"></div>
<p>Even after applying fixes, certain common mistakes can still trigger the <em>java.sql.SQLException: Incorrect string value</em> error.</p>
<h3 id="bd-1-partial-configuration" data-id="1-partial-configuration">6.1. Partial Configuration</h3>
<div class="bd-anchor" id="1-partial-configuration"></div>
<p>Updating only one component can result in inconsistent behavior.</p>
<p>For example, fixing the database but not the tables may still cause failures. Even single columns can result in problems. Thus, <strong>all components must conform to the same encoding</strong>.</p>
<p>This process can be tedious, but it is required, as even a single misaligned component can break the character set negotiation.</p>
<h3 id="bd-2-new-database-components" data-id="2-new-database-components">6.2. New Database Components</h3>
<div class="bd-anchor" id="2-new-database-components"></div>
<p>Any new database component with the default encoding <em>utf8</em> can break character support.</p>
<p>Thus, <strong>we should only create new tables and columns with the <em>utf8mb4</em> encoding</strong>.</p>
<h3 id="bd-3-outdated-jdbc-driver" data-id="3-outdated-jdbc-driver">6.3. Outdated JDBC Driver</h3>
<div class="bd-anchor" id="3-outdated-jdbc-driver"></div>
<p>Older JDBC drivers may not fully support modern MySQL encoding behavior:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;mysql&lt;/groupId&gt;
    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
    &lt;version&gt;8.0.33&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>That&#8217;s because the above Maven artifact (with its <em>groupId</em> and <em>artifactId</em>) is outdated and superseded:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.mysql&lt;/groupId&gt;
    &lt;artifactId&gt;mysql-connector-j&lt;/artifactId&gt;
    &lt;version&gt;9.7.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p><strong>Keeping dependencies up to date is one of the best ways to improve compatibility and stability</strong>. To find the latest artifacts, we can always go to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.mysql/mysql-connector-j">MySQL Connector/J on the Maven Repository</a>.</p>
<h2 id="bd-summary" data-id="summary">7. Summary</h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we explored how to diagnose and fix the <em>java.sql.SQLException: Incorrect string value</em> error in MySQL.</p>
<p>In summary, the problem is caused by character encoding mismatches, most commonly due to the limited <em>utf8</em> implementation of MySQL. By switching to <em>utf8mb4</em>, aligning JDBC configuration, verifying server settings, and ensuring consistency across all layers, we can fully support Unicode and often eliminate this error.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-mysql-sqlexception-incorrect-string-value">How to Fix MySQL <em>java.sql.SQLException: Incorrect string value</em></a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/961624316/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/961624316/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/961624316/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-03-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/961624316/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/961624316/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/961624316/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-mysql-sqlexception-incorrect-string-value#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-mysql-sqlexception-incorrect-string-value/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/~/961624316/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-150x150.jpg</webfeeds:featuredImage></item>
</channel></rss>

