<?xml version="1.0" encoding="UTF-8"?><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/"
	>

<channel>
	<title>JVM Advent</title>
	<atom:link href="https://www.javaadvent.com/feed" rel="self" type="application/rss+xml" />
	<link>https://www.javaadvent.com/</link>
	<description>The JVM Programming Advent Calendar</description>
	<lastBuildDate>Wed, 24 Dec 2025 05:01:50 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.5</generator>

<image>
	<url>https://i0.wp.com/www.javaadvent.com/content/uploads/2017/12/DukeFavicon.png?fit=32%2C32&#038;ssl=1</url>
	<title>JVM Advent</title>
	<link>https://www.javaadvent.com/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">101550570</site>	<item>
		<title>Santa&#8217;s Python Pitfalls: A Java Developer&#8217;s Guide to Staying Safe This Christmas</title>
		<link>https://www.javaadvent.com/2025/12/santas-python-pitfalls-a-java-developers-guide-to-staying-safe-this-christmas.html</link>
					<comments>https://www.javaadvent.com/2025/12/santas-python-pitfalls-a-java-developers-guide-to-staying-safe-this-christmas.html#respond</comments>
		
		<dc:creator><![CDATA[Steve Poole]]></dc:creator>
		<pubDate>Wed, 24 Dec 2025 03:03:09 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5916</guid>

					<description><![CDATA[<p>Just like that it happened. You, a disciplined Java developer, are now installing Python. Like everything in 2025, it just arrived. One day, you were running a tidy mvn install, the next, you&#8217;re learning about virtual environments and fighting an unfriendly pip install that won&#8217;t explain what it just pulled from the internet. Good news: [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fsantas-python-pitfalls-a-java-developers-guide-to-staying-safe-this-christmas.html&amp;action_name=Santa%26%238217%3Bs%20Python%20Pitfalls%3A%20A%20Java%20Developer%26%238217%3Bs%20Guide%20to%20Staying%20Safe%20This%20Christmas&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/santas-python-pitfalls-a-java-developers-guide-to-staying-safe-this-christmas.html">Santa&#8217;s Python Pitfalls: A Java Developer&#8217;s Guide to Staying Safe This Christmas</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><span style="font-weight: 400">Just like that it happened. You, a disciplined Java developer, are now installing Python. Like everything in 2025, it just arrived. One day, you were running a tidy mvn install, the next, you&#8217;re learning about virtual environments and fighting an unfriendly pip install that won&#8217;t explain what it just pulled from the internet.</span></p>
<p><span style="font-weight: 400">Good news: staying safe in Python’s world isn’t complicated. It&#8217;s just not </span><b>obvious</b><span style="font-weight: 400">. Think of this as a friendly reminder to check the extension cable before plugging in another set of lights.</span></p>
<p>&nbsp;</p>
<h3><b>Pip Install vs. Mvn Install: A World of Difference</b></h3>
<p><span style="font-weight: 400">Java’s packaging has its flaws, but </span><b>predictability</b><span style="font-weight: 400"> isn&#8217;t one of them. </span><b>Maven Central</b><span style="font-weight: 400"> is curated. </span><b>Group IDs</b><span style="font-weight: 400"> enforce a namespace. When you fetch </span><em><span style="font-weight: 400">com.fasterxml.jackson.core:jackson-databind</span></em><span style="font-weight: 400"><em>,</em> you get that specific artefact from that particular organisation.</span></p>
<p><span style="font-weight: 400">Python is radically different:</span></p>
<ul>
<li style="font-weight: 400"><b>A Flat Namespace:</b><span style="font-weight: 400"> Package names are first-come, first-served on PyPI. </span><b>Name collisions</b><span style="font-weight: 400"> and </span><b>typosquatting</b><span style="font-weight: 400"> are primary attack vectors.</span></li>
<li style="font-weight: 400"><b>A Trusting Resolver:</b><span style="font-weight: 400"> The resolver is extremely trusting. It will often prioritise a higher version number from </span><i><span style="font-weight: 400">any</span></i><span style="font-weight: 400"> available index, which is the core of </span><b>dependency confusion</b><span style="font-weight: 400"> attacks.</span></li>
</ul>
<p><span style="font-weight: 400">If you’re used to Java&#8217;s rules, Python’s model will trip you up. Please treat it with extreme care.</span></p>
<h3><b>Be Suspicious of &#8220;pip install &#8230;&#8221;</b></h3>
<p><span style="font-weight: 400">Java developers might fall for an occasional fake install script; Python encourages them. The internet is full of <em>&#8220;copy this </em></span><em><span style="font-weight: 400">pip install</span></em><span style="font-weight: 400"><em> and trust me.&#8221;</em> You shouldn&#8217;t accept that for a Maven dependency. Don&#8217;t accept it here.</span></p>
<p><span style="font-weight: 400">If you see </span><em><span style="font-weight: 400">pip install my-cool-tool</span></em><span style="font-weight: 400">, ask the same questions you would for a new Maven dependency:</span></p>
<ol>
<li style="font-weight: 400"><b>Who owns this package?</b><span style="font-weight: 400"> (Check PyPI, GitHub activity, and developer reputation).</span></li>
<li style="font-weight: 400"><b>Does the name look close to something legitimate?</b><span style="font-weight: 400"> (Guard against typosquatting: </span><span style="font-weight: 400">requests</span><span style="font-weight: 400"> vs. </span><span style="font-weight: 400">requessts</span><span style="font-weight: 400">).</span></li>
<li style="font-weight: 400"><b>Is this the actual source, or a mirror?</b></li>
</ol>
<p><b>Dependency Confusion</b><span style="font-weight: 400"> in Python is </span><i><span style="font-weight: 400">embarrassingly easy</span></i><span style="font-weight: 400">. A malicious public package with the same name can hijack a private internal package called </span><em><span style="font-weight: 400">company-utils</span></em><span style="font-weight: 400"> simply by having a higher version number. This is the default behaviour if not explicitly mitigated.</span></p>
<h4><b>Your Checklist:</b></h4>
<ul>
<li style="font-weight: 400"><b>Prefer explicit version pins.</b><span style="font-weight: 400"> (e.g., </span><span style="font-weight: 400">requests==2.28.1</span><span style="font-weight: 400">).</span></li>
<li style="font-weight: 400"><b>Prefer known sources.</b><span style="font-weight: 400"> Use a </span><b>private package registry</b><span style="font-weight: 400"> like Artifactory or Nexus to proxy and cache PyPI, allowing your organisation to blacklist known bad packages and prioritise your internal packages. (It may sound like &#8216;corporate&#8217;, but it&#8217;s essential)</span></li>
<li style="font-weight: 400"><b>Prefer tooling that uses a lock file.</b></li>
</ul>
<p>&nbsp;</p>
<h3><b>Take Five Minutes to Learn Virtual Environments</b></h3>
<p><span style="font-weight: 400">You&#8217;ve probably ignored Python’s advice about virtual environments already. Don&#8217;t. This is the difference between a clean setup and a machine that slowly accumulates mysterious, conflicting modules.</span></p>
<p><span style="font-weight: 400">A virtual environment gives you the </span><b>isolation</b><span style="font-weight: 400"> you take for granted in Java&#8217;s classpath model. Without it, you are installing every dependency into your system path.</span></p>
<p><b>Create one:</b></p>
<pre><em><span style="font-weight: 400">python3 -m venv .venv</span></em>

<em><span style="font-weight: 400">source .venv/bin/activate</span></em></pre>
<p><span style="font-weight: 400">Now every </span><span style="font-weight: 400">pip install</span><span style="font-weight: 400"> lives inside </span><span style="font-weight: 400">.venv</span><span style="font-weight: 400">, not your machine. This gives you </span><b>reproducibility</b><span style="font-weight: 400"> and enables cleaner </span><b>scanners</b><span style="font-weight: 400">, </span><b>policy checks</b><span style="font-weight: 400">, and </span><b>SBOM generation</b><span style="font-weight: 400">.  (In fact, once you start using Python in more depth, you&#8217;ll realise you can&#8217;t live without this approach &#8211; so do the right thing and you&#8217;re future 2026 self will thank you)</span></p>
<h3><b>Pin Your Dependencies. Really! Pin your </b><span style="margin: 0px;padding: 0px"><strong>dependencies</strong></span><b><i>.</i></b></h3>
<p><span style="font-weight: 400">In Java, you pin dependencies by default in your POM. Since transitive dependencies on Maven Central are immutable, you know the complete set upfront and permanently. In Python, if you don’t pin, you get </span><b>drift</b><span style="font-weight: 400">. Packages can silently update, pulling in new transitive dependencies.</span></p>
<p><span style="font-weight: 400">Most critically: </span><b>Malicious actors publish higher-numbered versions to trick the resolver.</b><span style="font-weight: 400"> If you use a loose constraint like </span><em><span style="font-weight: 400">package&gt;=1.0.0</span></em><span style="font-weight: 400">, an attacker publishing </span><em><span style="font-weight: 400">package==99.99.99</span></em><span style="font-weight: 400"> can compromise your build.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Use </span><b>pip-tools</b><span style="font-weight: 400">, </span><b>Poetry</b><span style="font-weight: 400">, or </span><b>uv</b><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><b>Generate a lock file</b><span style="font-weight: 400"> (your </span><span style="font-weight: 400">pom.xml</span><span style="font-weight: 400"> equivalent).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Add the lock file to source control.</span></li>
<li style="font-weight: 400"><b>Use Hashes:</b><span style="font-weight: 400"> Generate hashes for all dependencies. They are your final line of defence against tampered packages.</span></li>
</ul>
<h3><b>The Out-of-Support Trap: The Real Threat of Old Packages</b></h3>
<p><span style="font-weight: 400">Attackers don&#8217;t rely solely on brand-new exploits; they often compromise systems by manipulating developers (i.e., you) into installing </span><b>old, vulnerable packages that are frequently out of support</b><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">The workflow is:</span></p>
<ol>
<li style="font-weight: 400"><b>Bad Actor publishes an old, vulnerable version</b><span style="font-weight: 400"> (perhaps with malicious code added) or relies on an </span><b>unmaintained package</b><span style="font-weight: 400"> with a known CVE.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The vulnerable version is pulled in due to a </span><b>loose dependency constraint</b><span style="font-weight: 400"> (e.g., a sub-dependency requires </span><span style="font-weight: 400">old-library&lt;2.0.0</span><span style="font-weight: 400">).</span></li>
<li style="font-weight: 400"><span style="margin: 0px;padding: 0px">Your vulnerability scanner <em>may</em> catch the known CVE, but since the package is long out of support (its maintainers have moved on, or the version is too old), <strong>no patch exists.</strong></span></li>
</ol>
<p><b>The Cost:</b></p>
<ul>
<li style="font-weight: 400"><b>Vulnerability:</b><span style="font-weight: 400"> You have a known exploit in your stack.</span></li>
<li style="font-weight: 400"><b>Technical Debt:</b><span style="font-weight: 400"> You now have to either </span><b>fork and patch</b><span style="font-weight: 400"> the unmaintained library yourself (probably a massive undertaking) or </span><b>re-architect</b><span style="font-weight: 400"> your application to use a different, supported library. You&#8217;ve essentially built a stack that&#8217;s immediately due for a costly rebuild.</span></li>
</ul>
<h4><b>Mitigation:</b></h4>
<ul>
<li style="font-weight: 400"><b>Automated Scanners are Non-Negotiable:</b><span style="font-weight: 400"> Use tools like </span><b>Syft</b><span style="font-weight: 400">, </span><b>Safety</b><span style="font-weight: 400">, <strong>Sonatype</strong> or </span><b>Snyk</b><span style="font-weight: 400"> to generate an </span><b>SBOM</b><span style="font-weight: 400"> and scan for known CVEs </span><i><span style="font-weight: 400">on every build</span></i><span style="font-weight: 400">.  (Do all security companies start with &#8216;S&#8217;?)</span></li>
<li style="font-weight: 400"><b>Policy Checks:</b><span style="font-weight: 400"> Block builds that rely on packages with severe, unpatched CVEs, or that use versions marked as &#8220;end-of-life&#8221; by the maintainer. Check for third-party maintainers too.  Not all open-source is abandoned. Sometimes it gets picked up and brushed down.</span></li>
<li style="font-weight: 400"><b>Audit Your Transitives:</b><span style="font-weight: 400"> The </span><b>lock file</b><span style="font-weight: 400"> is key. It lets you see the </span><i><span style="font-weight: 400">entire</span></i><span style="font-weight: 400"> dependency graph, not just your direct requirements, and vet for ancient, risky components.</span></li>
<li><strong>Look for commercial support. </strong> Strangely there are companies who work to keep you safe.  End-of-life support (not really what it sounds like) can often be found for those pesky out-of-support open source components you&#8217;ve just installed and now discovered need a fix.</li>
</ul>
<h3><b>Know What &#8220;Local Install&#8221; Actually Means</b></h3>
<p><span style="font-weight: 400">Python encourages local installs with instructions like </span><span style="font-weight: 400">pip install -e .</span><span style="font-weight: 400">. This installs the package </span><b>&#8220;editable&#8221;</b><span style="font-weight: 400"> from your working directory. While convenient for development, it means the installed code </span><b>changes instantly</b><span style="font-weight: 400"> whenever someone edits files on disk.</span></p>
<ul>
<li style="font-weight: 400"><b>Avoid in Production:</b><span style="font-weight: 400"> This mutability is the last thing you want in a production flow. </span></li>
<li style="font-weight: 400"><b>Understand the Implications:</b><span style="font-weight: 400"> Use editable installs only for local, feature-branch development, where mutability is a feature, not a bug.</span></li>
</ul>
<p>If you don&#8217;t want to be on the leading edge, don&#8217;t do this!</p>
<p>&nbsp;</p>
<h3><b>Keep Your LLM Tools Contained</b></h3>
<p><span style="font-weight: 400">Many developers install Python only for LLMs. That’s fine, but </span><b>keep those tools contained</b><span style="font-weight: 400">. LLM agents and model servers often run their own complex Python interpreters and download additional files.</span></p>
<ul>
<li style="font-weight: 400"><b>Isolation:</b><span style="font-weight: 400"> Do not let your LLM workspace share a Python environment with your production scripts.</span></li>
<li style="font-weight: 400"><b>Sandboxing:</b><span style="font-weight: 400"> Do not run tools that download models or plugins without sandboxing (e.g., in a container or a dedicated VM).</span></li>
<li style="font-weight: 400"><b>No </b><b>curl | bash</b><b>:</b><span style="font-weight: 400"> Do not assume that &#8220;AI tool = harmless CLI.&#8221; Do not run shell-piped installers for model servers.  </span></li>
<li style="font-weight: 400"><strong>No curl | bash:  </strong>Hey, it&#8217;s the holidays, we can count things twice. Seriously, while<em> pip install requirements.txt </em>is a rich source of malware and compromised systems so is <em>curl | bash.  </em> Be very, very careful about using it to install software. Even if you think you trust the website or organisation involved.</li>
</ul>
<h3><b>Keep on the ‘nice’ list. </b></h3>
<p><span style="font-weight: 400">Python gives you rope. Your established Java discipline is your best defence :</span></p>
<ul>
<li style="font-weight: 400"><b>Trust Nothing:</b><span style="font-weight: 400"> Treat PyPI packages like third-party artefacts: verify their origin and security posture.</span></li>
<li style="font-weight: 400"><b>Pin Everything:</b><span style="font-weight: 400"> Go beyond version pinning to use lock files and dependency hashes.</span></li>
<li style="font-weight: 400"><b>Automate Scans:</b><span style="font-weight: 400"> Make SBOM generation and CVE scanning mandatory in your CI/CD pipeline.</span></li>
<li style="font-weight: 400"><b>Environments as Cattle:</b><span style="font-weight: 400"> Use virtual environments or containers, and frequently rebuild them to ensure a clean, reproducible state.</span></li>
</ul>
<p><span style="font-weight: 400">If you stick to these Java security principles, Python becomes far less chaotic and much more manageable.</span></p>
<h4><strong>Have a great holiday season and keep your software safe</strong></h4>
<img decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fsantas-python-pitfalls-a-java-developers-guide-to-staying-safe-this-christmas.html&amp;action_name=Santa%26%238217%3Bs%20Python%20Pitfalls%3A%20A%20Java%20Developer%26%238217%3Bs%20Guide%20to%20Staying%20Safe%20This%20Christmas&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/santas-python-pitfalls-a-java-developers-guide-to-staying-safe-this-christmas.html">Santa&#8217;s Python Pitfalls: A Java Developer&#8217;s Guide to Staying Safe This Christmas</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/santas-python-pitfalls-a-java-developers-guide-to-staying-safe-this-christmas.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5916</post-id>	</item>
		<item>
		<title>Bring WebAssembly to the JVM. How Chicory Is Powering a New Generation of Java Libraries</title>
		<link>https://www.javaadvent.com/2025/12/chicory-webassembly-on-the-jvm.html</link>
					<comments>https://www.javaadvent.com/2025/12/chicory-webassembly-on-the-jvm.html#respond</comments>
		
		<dc:creator><![CDATA[Andrea Peruffo]]></dc:creator>
		<pubDate>Tue, 23 Dec 2025 03:03:36 +0000</pubDate>
				<category><![CDATA[2017]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6289</guid>

					<description><![CDATA[<p>Introduction: the promise of “embed once, run anywhere” reimagined &#160; What if you could embed native‑level capabilities into your Java applications, think of database engines, scripting runtimes, policy interpreters, even compilers and still remain pure Java, with no JNI, no native binaries, no concerns about OS or architecture compatibility? That is the promise of combining [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fchicory-webassembly-on-the-jvm.html&amp;action_name=Bring%20WebAssembly%20to%20the%20JVM.%20How%20Chicory%20Is%20Powering%20a%20New%20Generation%20of%20Java%20Libraries&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/chicory-webassembly-on-the-jvm.html">Bring WebAssembly to the JVM. How Chicory Is Powering a New Generation of Java Libraries</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h3><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?ssl=1"><img data-recalc-dims="1" decoding="async" data-attachment-id="6293" data-permalink="https://www.javaadvent.com/2025/12/chicory-webassembly-on-the-jvm.html/chatgpt-image-dec-10-2025-05_29_26-pm" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?fit=1024%2C1024&amp;ssl=1" data-orig-size="1024,1024" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="ChatGPT Image Dec 10, 2025, 05_29_26 PM" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?fit=300%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?fit=600%2C600&amp;ssl=1" class="aligncenter wp-image-6293" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?resize=208%2C208&#038;ssl=1" alt="" width="208" height="208" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?w=1024&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?resize=300%2C300&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?resize=768%2C768&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/ChatGPT-Image-Dec-10-2025-05_29_26-PM.png?resize=508%2C508&amp;ssl=1 508w" sizes="(max-width: 208px) 100vw, 208px" /></a><b>Introduction: the promise of “embed once, run anywhere” reimagined</b></h3>
<p>&nbsp;</p>
<p><span style="font-weight: 400">What if you could embed native‑level capabilities into your Java applications, think of database engines, scripting runtimes, policy interpreters, even compilers and still remain pure Java, with no JNI, no native binaries, no concerns about OS or architecture compatibility? That is the promise of combining WebAssembly (Wasm) with the zero dependencies runtime </span><a href="https://github.com/dylibso/chicory"><span style="font-weight: 400">Chicory</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">The year 2025 feels like a turning point for this model. Chicory is no longer just an advanced exercise or a runtime prototype. It is powering a growing ecosystem of real‑world Java libraries. “Embed once, run anywhere” is no longer a slogan, it is becoming practical. In this post, we will explore why Chicory matters, what has been built upon it so far, and why you (as a Java developer or architect) should care today.</span></p>
<h3><b>What is Chicory and why it matters</b></h3>
<p><a href="https://github.com/dylibso/chicory"><span style="font-weight: 400">Chicory</span></a><span style="font-weight: 400"> is a JVM‑native WebAssembly runtime. It allows you to run Wasm modules with zero native dependencies or JNI. As long as the JVM runs, the Wasm‑derived code runs.</span></p>
<p><span style="font-weight: 400">Most existing Wasm runtimes (for example V8, Wasmtime, Wasmer, Wasmedge) are often written in C/C++/Rust. Embedding them in a Java application requires shipping native binaries for every supported platform, complicating distribution and deployment.</span></p>
<p><span style="font-weight: 400">By contrast, Chicory eliminates that burden. You just depend on a JAR. This design brings structural advantages:</span></p>
<ul>
<li style="font-weight: 400"><b><i>Portability</i></b><span style="font-weight: 400">: if the JVM can run, the same Wasm module can run everywhere. No need to build per OS or architecture.</span></li>
<li style="font-weight: 400"><b><i>Sandboxing and JVM integration</i></b><span style="font-weight: 400">: the Wasm module executes inside the JVM sandbox, on a managed isolated heap, preserving JVM memory safety, garbage collection, observability, tooling.</span></li>
<li style="font-weight: 400"><b><i>Simplicity</i></b><span style="font-weight: 400">: no external native dependencies, no packaging matrix across OS/architecture, no JNI trouble.</span></li>
</ul>
<p><span style="font-weight: 400">Chicory re-imagines the classic “write once, run anywhere” Java promise. It extends it beyond Java bytecode, enabling WebAssembly modules that let you embed seamlessly powerful native features, while staying inside the JVM.</span></p>
<h3><b>Chicory execution modes: flexibility for development and production</b></h3>
<p><span style="font-weight: 400">One of the strengths of Chicory is that it supports </span><a href="https://chicory.dev/docs/usage/execution_modes"><span style="font-weight: 400">multiple ways of executing</span></a><span style="font-weight: 400"> Wasm modules, letting you choose the trade‑off between portability and performance.</span></p>
<p><span style="font-weight: 400">Here is an overview:</span></p>
<table style="height: 451px" width="561">
<tbody>
<tr>
<td><b>Mode</b></td>
<td><b>Description</b></td>
<td><b>When to Use</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">Interpreter</span></td>
<td><span style="font-weight: 400">Default mode. Executes Wasm modules directly, without compilation. It requires no extra dependencies and is maximally portable.</span></td>
<td><span style="font-weight: 400">During development, dynamic loading, or when you want maximum flexibility.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Runtime Compilation</span></td>
<td><span style="font-weight: 400">On-the-fly compilation of Wasm modules into Java bytecode (in‑memory) and dynamic loading. This requires an additional dependency (ASM) but improves execution performance significantly.</span></td>
<td><span style="font-weight: 400">Useful when you need better performance while still allowing dynamic module loading.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Build‑time Compilation</span></td>
<td><span style="font-weight: 400">Compile Wasm modules into plain Java bytecode at build time (via Maven/Gradle plugin). You get standard class/JAR artifacts and avoid runtime compilation overhead altogether.</span></td>
<td><span style="font-weight: 400">For production deployments with static modules and where performance is important.</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">With the </span><a href="https://chicory.dev/blog/chicory-1.4.0/"><span style="font-weight: 400">release of Chicory 1.4.0</span></a><span style="font-weight: 400">, the compiler (both runtime and build‑time) and the annotations system became stable.</span></p>
<p><span style="font-weight: 400">More recently, </span><a href="https://chicory.dev/blog/chicory-1.6.0"><span style="font-weight: 400">Chicory 1.6.0</span></a><span style="font-weight: 400"> added support for the Java Platform Module System (JPMS), a directory‑backed runtime‑compiler cache (improving startup for repeated module loads), enhanced support for the Wasm “Threads” proposal(atomic fence instructions and atomic ops), increased Wasm spec conformance and verified compatibility with Java 25.</span></p>
<p><span style="font-weight: 400">Thanks to this flexibility, libraries and applications can choose the mode that fits their use case: from dynamic scripting in dev to high‑performance embedded Wasm in production.</span></p>
<h3><b>Real‑world “native‑free” tools built on Chicory</b></h3>
<p><span style="font-weight: 400">One of the strongest signals that the Wasm‑on‑JVM model is maturing is the growing number and quality of real libraries now using Chicory.</span></p>
<p><span style="font-weight: 400">Here are some of the most notable:</span></p>
<p><a href="https://github.com/roastedroot/quickjs4j"><b>QuickJs4</b></a><span style="font-weight: 400">j: a sandboxed JavaScript runtime for Java. QuickJS (originally a C engine) is compiled to WebAssembly, then Chicory compiles that Wasm into pure Java bytecode. The result is a small, self‑contained JAR runtime.</span></p>
<p><span style="font-weight: 400">QuickJs4J’s is already powering the </span><a href="https://microcks.io/documentation/explanations/dispatching/"><b>Microcks</b><span style="font-weight: 400"> JavaScript dispatcher</span></a><span style="font-weight: 400"> and </span><a href="https://www.apicur.io/blog/2025/10/27/custom-artifact-types"><b>Apicurio’s Registry</b><span style="font-weight: 400"> custom artifact types</span></a><span style="font-weight: 400">.</span></p>
<p><a href="https://github.com/dylibso/chicory"><b>SQLite4j</b></a><span style="font-weight: 400">: a pure‑Java SQLite JDBC driver. SQLite (originally written in C) is compiled to WebAssembly, then translated via Chicory to JVM bytecode. This allows embedding the full power of the most used lightweight SQL database inside Java without the need to ship native binaries.</span></p>
<p><a href="https://github.com/StyraInc/opa-java-wasm"><b>Opa‑java‑wasm</b></a><span style="font-weight: 400">: is the WebAssembly-powered version of the policy engine </span><b>Open Policy Agent (OPA)</b><span style="font-weight: 400">, delivered for Java via Chicory. It powers in-process OPA policy evaluation removing the network calls required by more traditional integrations and, again, no native dependencies.</span></p>
<p><span style="font-weight: 400">Beyond these, the </span><a href="https://github.com/dylibso/chicory?tab=readme-ov-file#who-uses-chicory"><span style="font-weight: 400">“Who uses Chicory?”</span></a><span style="font-weight: 400"> list includes many other use cases: user-defined functions for data engines, plugin systems, scripting inside data frameworks and more.</span></p>
<p><span style="font-weight: 400">These are not toy examples. They deliver widely relevant capabilities: a JavaScript runtime, an embedded database and a policy engine. They show that Wasm + JVM is not just a thought experiment; it is already powering production‑ready tools.</span></p>
<h3><b>Why this matters for Java developers and enterprises</b></h3>
<p><span style="font-weight: 400">For years, Java developers have accepted a painful tradeoff: whenever you needed to interact with libraries originally written in system programming languages, you ended up either rewriting the full thing or using JNI, FFI, native binaries, and OS/architecture‑specific builds. Packaging, deployment complexity, native-library hell became a recurring burden.</span></p>
<p><span style="font-weight: 400">Chicory changes that math. With Wasm + Chicory + appropriate libraries, you can embed powerful functionality without leaving the JVM or shipping native dependencies.</span><span style="font-weight: 400"><br />
</span><span style="font-weight: 400"><br />
</span><span style="font-weight: 400">New architectural patterns are emerging: plugin systems, embedded scripting engines, user-provided logic, sandboxed extensions, policy engines can now run within a JVM and with a controlled ABI surface.</span></p>
<p><span style="font-weight: 400">For enterprise and cloud‑native applications, this significantly reduces operational complexity, simplifies deployment across environments and improves maintainability enabling wider code re-use.</span></p>
<h3><b>Where things stand: momentum, community and next steps</b></h3>
<p><span style="font-weight: 400">The ecosystem around Chicory is gaining tangible momentum. With stable compiler support, modularity, improved performance, cache support, and a lot of emerging libraries and integrations, the year 2025 feels like the moment when this technology shifted from “experimental runtime” to a useful building block.</span></p>
<p><span style="font-weight: 400">Now is a great time to try things out!</span><span style="font-weight: 400"><br />
</span><span style="font-weight: 400">Try embedding a Wasm‑compiled module into your Java project using Chicory and help us shape the future of native‑free, polyglot tooling for the JVM.</span></p>
<h3><b>Conclusion: reimagining “write once, run anywhere” for modern needs</b></h3>
<p><span style="font-weight: 400">Chicory re-imagines Java’s classic “write once, run anywhere” promise:extending it beyond Java bytecode to WebAssembly modules compiled into JVM bytecode. With Chicory, you can embed powerful, native‑level capabilities inside Java applications, while retaining the safety, portability and simplicity of pure Java.</span></p>
<p><span style="font-weight: 400">If you are a Java developer or architect still wrestling with native dependencies or painfully shipping platform‑specific binaries perhaps it is time to look again. Chicory and the growing ecosystem of libraries around it shows that you can have your cake and eat it too.</span></p>
<p><span style="font-weight: 400">Give it a try now. The future of native‑free, polyglot-powered Java tooling may depend on it.</span></p>
<img decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fchicory-webassembly-on-the-jvm.html&amp;action_name=Bring%20WebAssembly%20to%20the%20JVM.%20How%20Chicory%20Is%20Powering%20a%20New%20Generation%20of%20Java%20Libraries&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/chicory-webassembly-on-the-jvm.html">Bring WebAssembly to the JVM. How Chicory Is Powering a New Generation of Java Libraries</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/chicory-webassembly-on-the-jvm.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6289</post-id>	</item>
		<item>
		<title>Delegating Java tasks to Supervised AI Dev Pipelines</title>
		<link>https://www.javaadvent.com/2025/12/delegating-java-tasks-to-supervised-ai-dev-pipelines.html</link>
					<comments>https://www.javaadvent.com/2025/12/delegating-java-tasks-to-supervised-ai-dev-pipelines.html#respond</comments>
		
		<dc:creator><![CDATA[Juan Antonio Breña Moral]]></dc:creator>
		<pubDate>Mon, 22 Dec 2025 03:03:48 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6278</guid>

					<description><![CDATA[<p>During the second part of this year, Anysphere, the company behind Cursor IDE, released 2 new products that could help you in 2026 increase the level of automation in your software operations. The names of both products are: Cursor Agent CLI and Cursor Cloud Agents. The article will explain the features that both products share [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fdelegating-java-tasks-to-supervised-ai-dev-pipelines.html&amp;action_name=Delegating%20Java%20tasks%20to%20Supervised%20AI%20Dev%20Pipelines&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/delegating-java-tasks-to-supervised-ai-dev-pipelines.html">Delegating Java tasks to Supervised AI Dev Pipelines</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p><span style="font-weight: 400">During the second part of this year, Anysphere, the company behind Cursor IDE, released 2 new products that could help you in 2026 increase the level of automation in your software operations. The names of both products are: Cursor Agent CLI and Cursor Cloud Agents. The article will explain the features that both products share and the unique capabilities of each. Finally, the article will share some insights for creating great supervised AI Dev pipelines.</span></p>
<h2><span style="font-weight: 400">What is Cursor Agent CLI in a Pipeline context?</span></h2>
<p><span style="font-weight: 400">In August 2025, Anysphere released Cursor Agent CLI, a new way to interact with frontier models but not coupled with a particular IDE. With this local development approach, the software engineer added a new way to enrich the development experience, but what happens if we use this product in a pipeline? In that case, we will add new capabilities.</span></p>
<p><span style="font-weight: 400">Let&#8217;s review the following pipeline to understand the concept:</span></p>
<pre><i><span style="font-weight: 400">name: Run Cursor Agent on Demand</span></i><br /><br /><i><span style="font-weight: 400">on:</span></i><br /><br /><i><span style="font-weight: 400">  workflow_dispatch:</span></i><br /><br /><i><span style="font-weight: 400">jobs:</span></i><br /><br /><i><span style="font-weight: 400">  agent-on-demand:</span></i><br /><br /><i><span style="font-weight: 400">    runs-on: ubuntu-latest<br />    timeout-minutes: 5</span></i><br /><br /><i><span style="font-weight: 400">    permissions:</span></i><br /><br /><i><span style="font-weight: 400">      contents: write</span></i><br /><br /><i><span style="font-weight: 400">      pull-requests: write</span></i><br /><br /><i><span style="font-weight: 400">    steps:</span></i><br /><br /><i><span style="font-weight: 400">      - name: Checkout repository</span></i><br /><br /><i><span style="font-weight: 400">        uses: actions/checkout@v6</span></i><br /><br /><i><span style="font-weight: 400">        with:</span></i><br /><br /><i><span style="font-weight: 400">          token: ${{ secrets.GITHUB_TOKEN }}</span></i><br /><br /><i><span style="font-weight: 400">          fetch-depth: 0</span></i><br /><br /><br /><i><span style="font-weight: 400">      - name: Install Cursor CLI</span></i><br /><br /><i><span style="font-weight: 400">        run: |</span></i><br /><br /><i><span style="font-weight: 400">          curl https://cursor.com/install -fsS | bash</span></i><br /><br /><i><span style="font-weight: 400">          echo "$HOME/.cursor/bin" &gt;&gt; $GITHUB_PATH</span></i><br /><br /><br /><i><span style="font-weight: 400">      - name: Run Cursor Agent</span></i><br /><br /><i><span style="font-weight: 400">        env:</span></i><br /><br /><i><span style="font-weight: 400">          CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}</span></i><br /><br /><i><span style="font-weight: 400">        run: |</span></i><br /><br /><i><span style="font-weight: 400">          echo "=== User Prompt:===";</span></i><br /><br /><i><span style="font-weight: 400">         PROMPT="Develop a classic Java class HelloWorld.java program that print Hello World in the console only"</span></i><br /><br /><i><span style="font-weight: 400">          echo "$PROMPT";</span></i><br /><br /><i><span style="font-weight: 400">          echo "=== Cursor Agent Execution:===";</span></i><br /><br /><i><span style="font-weight: 400">          echo "";</span></i><br /><br /><i><span style="font-weight: 400">          cursor-agent -p "$PROMPT" --model auto</span></i><br /><br /><br /><i><span style="font-weight: 400">      - name: Create PR with changes</span></i><br /><br /><i><span style="font-weight: 400">        env:</span></i><br /><br /><i><span style="font-weight: 400">          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}</span></i><br /><br /><i><span style="font-weight: 400">          PAT_TOKEN: ${{ secrets.PAT_TOKEN }}</span></i><br /><br /><i><span style="font-weight: 400">          GITHUB_REPOSITORY: ${{ github.repository }}</span></i><br /><br /><i><span style="font-weight: 400">          GITHUB_ACTOR: ${{ github.actor }}</span></i><br /><br /><i><span style="font-weight: 400">        run: |</span></i><br /><br /><i><span style="font-weight: 400">          chmod +x .github/scripts/create-pr.sh</span></i><br /><br /><i><span style="font-weight: 400">          .github/scripts/create-pr.sh</span></i></pre>
<p><span style="font-weight: 400">In a few lines of code, a pipeline is able to execute a task with the help of Frontier models and at the end of the process, submit a PR to be reviewed by the team.</span></p>
<p><span style="font-weight: 400">Once you have a clear idea about how to start working with this product, let&#8217;s jump to the second product released, Cursor Cloud Agent.</span></p>
<h2><span style="font-weight: 400">What are Cursor Cloud Agents?</span></h2>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">In October 2025, Cursor Cloud Agents was released, and it provides a collection of REST endpoints to handle the service. The different resources are organized into 3 categories:</p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1.5 [li_&amp;]:gap-1.5 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-2 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2">Agent Management (Launch, Follow up, Stop &amp; Delete)</li>
<li class="whitespace-normal break-words pl-2">Agent Information (Status, Conversation &amp; List of Agents)</li>
<li class="whitespace-normal break-words pl-2">General Information (Models, Repositories &amp; API keys)</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Using this service, you can delegate tasks to frontier models, but all operations run on Cursor cloud infrastructure, not in your pipelines like with Cursor Agent CLI.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">As the service provides different REST endpoints, it is important to understand the minimum concepts to orchestrate tasks with them.</p>
<h3><span style="font-weight: 400">Understanding the lifecycle of a Cursor Cloud Agent request</span></h3>
<h4><span style="font-weight: 400">Step 1: Launching a new AI Agent </span></h4>
<p><span style="font-weight: 400">When a user want to use this service, launch a HTTP POST request to provision a new cloud AI agent, the service will require the following information:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">A valid Github/Gitlab repository to operate with permissions</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">A user prompt with the clear goal to be achieved</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">An available frontier model to process your user prompt</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">A Cursor API Key to authenticate the request and validate if the user has permissions to operate with the required Git repository.</span></li>
</ul>
<p><b>Note:</b><span style="font-weight: 400"> In this article we will put focus on Prompts based on Text plain, not images.</span></p>
<p><span style="font-weight: 400">Once the User sends the request, the service will return a HTTP response with status code 201 indicating that the request was received and the service will be processed soon, an Agent-ID which is pretty useful to be used with other REST resources to track the progress and an Agent State, in this case, CREATING.</span></p>
<p><b>Note:</b><span style="font-weight: 400"> You could track the whole process here in a visual way: </span><a href="https://cursor.com/agents"><span style="font-weight: 400">https://cursor.com/agents</span></a><span style="font-weight: 400"> </span></p>
<p><b>What happens under the hood?</b></p>
<p><span style="font-weight: 400">Once the service receives the request, it will provision an EC2 instance running in AWS region us-east-1 with the following features:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">OS: Linux Distro</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Cores: 4 cores</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Memory: 16GB</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Disk: 126GB HDD</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Java: Java 21</span></li>
</ul>
<p><span style="font-weight: 400">Inside this Linux container, the service will perform a git checkout operation of the git repository described in the request, and after that, it will start working on the details described in the user prompt.</span></p>
<p><span style="font-weight: 400">As you can observe, the request receives a fast response, but the whole process is asynchronous. So how do you track the progress of your user prompt as it works on your repository?</span></p>
<h4><span style="font-weight: 400">Step 2: What is the status of my AI Agent?</span></h4>
<p><span style="font-weight: 400">An AI Agent has the following states:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">RUNNING</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">FINISHED</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">ERROR</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">CREATING</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">EXPIRED</span></li>
</ul>
<p><span style="font-weight: 400">If you remember from the first step, the AI Agent returned the state CREATING, and if everything goes well, the current state should now be RUNNING. But how do you know what the real status is? For that purpose, there exists a GET endpoint to receive the status from an Agent ID.</span></p>
<p><span style="font-weight: 400">By calling the status endpoint periodically, the user/process can know when the AI Agent has changed the state to FINISHED.</span></p>
<p><span style="font-weight: 400">Once the AI Agent is in a FINISHED state and the process has changed anything in the git repository, it will execute internally a git commit &amp; git push to a feature branch and will create a PR to be reviewed.</span></p>
<p><span style="font-weight: 400">Finally we have our lovely Hello World in the repository:</span></p>
<pre><span style="font-weight: 400">package info.jab.examples;</span><br /><br /><span style="font-weight: 400">public class HelloWorld {</span><br /><span style="font-weight: 400">    public static void main(String[] args) {</span><br /><span style="font-weight: 400">        System.out.println("Hello World");</span><br /><span style="font-weight: 400">    }</span><br /><span style="font-weight: 400">}</span></pre>



<h4><span style="font-weight: 400">Step 3: Review the pull request</span></h4>
<p><span style="font-weight: 400">Once Cursor Cloud Agent reaches the goal specified in the user prompt, the service will create a PR in the repository to be reviewed by your team, independent of your Git branch strategy like Trunk-Based Development, Gitflow, or similar.</span></p>
<h2><span style="font-weight: 400">When to choose Cursor Agent CLI and when to choose Cursor Cloud Agents?</span></h2>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Exploring new technologies always has a cost. Let&#8217;s list a few factors to help in your decision-making:</p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1.5 [li_&amp;]:gap-1.5 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-2 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2"><strong>Complexity of the scenario to automate:</strong> If the scenario to delegate is easy, like a simple task or a sequence of operations, Cursor Agent CLI could be the first option. On the other hand, if you need to model a process that behaves like a state machine or a directed graph, Cursor Cloud Agents offer better options because they provide more granular control of the execution.</li>
<li class="whitespace-normal break-words pl-2"><strong>Experience with AI agents in the team:</strong> If you are starting with AI Agents, Cursor Agent CLI could be the first option to run a pilot because it requires less effort to start and receive results.</li>
<li class="whitespace-normal break-words pl-2"><strong>Pipeline capacity:</strong> Depending on the pipeline capacity, you might consider whether to run the operations in your pipelines or outside.</li>
<li class="whitespace-normal break-words pl-2"><strong>Costs:</strong> Both products use the same subscription, so the cost structure is the same today.</li>
<li class="whitespace-normal break-words pl-2"><strong>Tooling:</strong> Frontier models use tools to interact in the environments where they operate. Currently, Cursor Agent CLI has support for MCP, but this feature is not available in Cursor Cloud Agents. An important question might be: Why do you need MCP for everything? A CLI interface might be enough.</li>
<li><strong>Observability:</strong> If observability is a critical factor, Cursor Cloud Agents provides a specific endpoint to retrieve the internal agent conversation. This approach is useful for analysis in case of errors.</li>
</ul>
<p>Until now, we have reviewed the way to execute user prompts by comparing 2 products, but in both cases, you can decouple the location of your prompts from the location of the execution. In the next sections, we will explore aspects of user prompts that will help you be more efficient and maintain them with less effort.</p>
<h2><span style="font-weight: 400">Developing great user prompts</span></h2>
<p><span style="font-weight: 400">Until now, we have only explained the output of the service—in the previous case, the creation of a Java class that writes to the terminal&#8217;s standard output. But how do you increase efficiency in the process? It&#8217;s simple: send a request with a user prompt that minimizes ambiguity to reach the defined goals.</span></p>
<h3><span style="font-weight: 400">An initial Hello World user prompt</span></h3>
<p><span style="font-weight: 400">You might think that a good user prompt could be:</span></p>
<pre><span style="font-weight: 400">Develop a classic Java class HelloWorld.java program</span><br /><span style="font-weight: 400">that prints "Hello World" in the console only.</span></pre>
<p><span style="font-weight: 400">And nothing more. But in practice, this idea—which apparently seems very easy—could be interpreted by frontier models in several ways, independent of which frontier model is used, because frontier models have non-deterministic behavior and may have doubts about:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The location of the Java class</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The approach to compile the class (javac or using a build system)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Whether they need to commit .class files</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Whether they need to use System.out.println or IO.println (Java 25+)</span></li>
</ul>
<p><span style="font-weight: 400">If you understand the potential problems on the frontier model side, let&#8217;s iterate on this user prompt.</span></p>
<h3><span style="font-weight: 400">Moving away from plain text user prompts</span></h3>
<p><span style="font-weight: 400">When you use modern IDEs with AI features and the frontier model doesn&#8217;t return the expected result, you continue the conversation, and after a few iterations, the result is as expected. But when using this kind of service running in your pipelines where you expect accurate results, you need to define restrictions and other details clearly to achieve your goals. So little by little, that user prompt will require some structure to operate accurately.</span></p>
<h3><span style="font-weight: 400">Encoding your User prompts in PML format</span></h3>
<p><span style="font-weight: 400">PML is the acronym for Prompt Markup Language, an XML Schema designed to help software engineers describe user prompts accurately.</span></p>
<p><span style="font-weight: 400">Take a look at the evolution from plain text to PML with the new sections:</span></p>
<p><b>Text plain:</b></p>
<pre><span style="font-weight: 400">Develop a classic Java class HelloWorld.java program</span><br /><span style="font-weight: 400">that prints "Hello World" in the console only.</span></pre>
<p><b>XML with PML Schema:</b></p>
<pre><span style="font-weight: 400">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span><br /><span style="font-weight: 400">&lt;prompt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   xsi:noNamespaceSchemaLocation="</span><a href="https://jabrena.github.io/pml/schemas/0.3.0/pml.xsd"><span style="font-weight: 400">https://jabrena.github.io/pml/schemas/0.3.0/pml.xsd</span></a><span style="font-weight: 400">"&gt;</span><br /><span style="font-weight: 400"><br />    &lt;role&gt;<br />        You are a Senior software engineer with extensive         experience in Java software development<br />    &lt;/role&gt;</span><br /><br /><span style="font-weight: 400">    &lt;goal&gt;</span><br /><span style="font-weight: 400">        Develop a classic Java class HelloWorld.java program</span><br /><span style="font-weight: 400">        that print "Hello World" in the console only</span><br /><span style="font-weight: 400">    &lt;/goal&gt;</span><br /><br /><span style="font-weight: 400">    &lt;constraints&gt;</span><br /><span style="font-weight: 400">        &lt;constraint-list&gt;</span><br /><span style="font-weight: 400">            &lt;constraint&gt;The develop the class in the Maven module `sandbox`&lt;/constraint&gt;</span><br /><span style="font-weight: 400">            &lt;constraint&gt;The develop the class in the package info.jab.examples&lt;/constraint&gt;</span><br /><span style="font-weight: 400">            &lt;constraint&gt;Do not create any test class&lt;/constraint&gt;</span><br /><span style="font-weight: 400">            &lt;constraint&gt;Do not touch the build file (pom.xml)&lt;/constraint&gt;</span><br /><span style="font-weight: 400">        &lt;/constraint-list&gt;</span><br /><span style="font-weight: 400">    &lt;/constraints&gt;</span><br /><br /><span style="font-weight: 400">    &lt;output-format&gt;</span><br /><span style="font-weight: 400">        &lt;output-format-list&gt;</span><br /><span style="font-weight: 400">            &lt;output-format-item&gt;Don not explain anything&lt;/output-format-item&gt;</span><br /><span style="font-weight: 400">        &lt;/output-format-list&gt;</span><br /><span style="font-weight: 400">    &lt;/output-format&gt;</span><br /><br /><span style="font-weight: 400">    &lt;safeguards&gt;</span><br /><span style="font-weight: 400">        &lt;safeguards-list&gt;</span><br /><span style="font-weight: 400">            &lt;safeguards-item&gt;Build the solution with Maven Only&lt;/safeguards-item&gt;</span><br /><span style="font-weight: 400">        &lt;/safeguards-list&gt;</span><br /><span style="font-weight: 400">    &lt;/safeguards&gt;</span><br /><br /><span style="font-weight: 400">    &lt;acceptance-criteria&gt;</span><br /><span style="font-weight: 400">        &lt;acceptance-criteria-list&gt;</span><br /><span style="font-weight: 400">            &lt;acceptance-criteria-item&gt;The solution is compiled successfully with `./mvnw clean compile -pl sandbox`&lt;/acceptance-criteria-item&gt;</span><br /><span style="font-weight: 400">            &lt;acceptance-criteria-item&gt;The solution only prints "Hello World" in the console&lt;/acceptance-criteria-item&gt;</span><br /><span style="font-weight: 400">            &lt;acceptance-criteria-item&gt;Only commit java sources only and push the changes to the branch to create the PR&lt;/acceptance-criteria-item&gt;</span><br /><span style="font-weight: 400">        &lt;/acceptance-criteria-list&gt;</span><br /><span style="font-weight: 400">    &lt;/acceptance-criteria&gt;</span><br /><span style="font-weight: 400">&lt;/prompt&gt;</span></pre>
<p>Although we have increased the number of lines, now the user prompt look robust and we have mitigated the ambiguity and now it has a better structure and it will be easier to maintain in the future with new refinements.</p>
<ul>
<li>User prompt
<ul>
<li>Role</li>
<li>Goal</li>
<li>Restrictions</li>
<li>Output format</li>
<li>Safeguards</li>
<li>Acceptance criteria</li>
</ul>
</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Once you have created the document, it can be validated with the XML Schema and later transformed to another format like Markdown.</p>
<p>Here is the result converted into Markdown:</p>
<pre><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">## Role</span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">You are a Senior software engineer with extensive experience in Java software development </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">## Goal </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">Develop a classic Java class HelloWorld.java program </span></span><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">that print "Hello World" in the console only </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">## Constraints </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- The develop the class in the Maven module `sandbox` </span></span><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- The develop the class in the package info.jab.examples </span></span><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- Do not invest time in planning </span></span><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- Do not create any test class </span></span><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- Do not touch the build file (pom.xml) </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">## Output Format </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- Don not explain anything </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">## Safeguards </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- Build the solution with Maven Only </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">## Acceptance Criteria </span></span><br /><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">The goal will be achieved if the following criteria are met: <br /></span></span><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- The solution is compiled successfully with `./mvnw clean compile -pl sandbox` </span></span><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- The solution only prints "Hello World" in the console </span></span><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- The solution is committed and pushed to the branch to create the PR </span></span><br /><span class="CheckStep-line-content d-inline-block flex-auto ml-3 js-check-line-content"><span class="">- Only commit java sources only and push the changes to the branch to create the PR</span></span></pre>
<p>&nbsp;</p>
<p>Using XML as the source format for your user prompts, you could use the composability features that XML includes. On the other hand, when creating or updating PML files, you always create files with the same syntax, so your prompts will be homogeneous at scale.</p>
<h2><span style="font-weight: 400">What happen if something goes wrong?</span></h2>
<p><span style="font-weight: 400">Don&#8217;t be naive—even the most complex systems in the world, like nuclear plants, have incidents in different ways, so why wouldn&#8217;t this kind of integration have them too? Let&#8217;s explore different types of issues that your threat model plan should cover in your projects using this kind of technology.</span></p>
<h3><span style="font-weight: 400">Scenario: using Cursor Agent cli from a Pipeline</span></h3>
<p><span style="font-weight: 400">Imagine the scenario where you delegate a task to Cursor Agent CLI in the execution of your pipeline. What issues could happen?</span></p>
<div id="attachment_6328" style="width: 610px" class="wp-caption alignnone"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6328" data-attachment-id="6328" data-permalink="https://www.javaadvent.com/2025/12/delegating-java-tasks-to-supervised-ai-dev-pipelines.html/image-20" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?fit=2438%2C894&amp;ssl=1" data-orig-size="2438,894" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?fit=300%2C110&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?fit=600%2C220&amp;ssl=1" class="wp-image-6328 size-large" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?resize=600%2C220&#038;ssl=1" alt="" width="600" height="220" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?resize=1024%2C375&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?resize=300%2C110&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?resize=768%2C282&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?resize=1536%2C563&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?resize=2048%2C751&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?resize=1240%2C455&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?resize=508%2C186&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-2.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a><p id="caption-attachment-6328" class="wp-caption-text">Scenario: Using Cursor Agent CLi from the pipeline</p></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>Issues at Pipeline Level</strong></p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1.5 [li_&amp;]:gap-1.5 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-2 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2"><strong>Third-party dependencies:</strong> If your pipeline is too complex, the global reliability may suffer. Review the chain of dependencies to simplify your pipeline and avoid runtime issues. Log runtime issues with third-party dependencies. Using a predefined Docker image could reduce the number of runtime issues.</li>
<li class="whitespace-normal break-words pl-2"><strong>Unexpected files included in the PR:</strong> When frontier models try to resolve the goals described in the user prompt, they sometimes need to create scripts, extra files, or simply store files for analysis and debugging. Refine your user prompts to specify exactly what files and file extensions are valid; another alternative is to combine this with .gitignore files.</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>Issues at Cursor Agent CLI Level</strong></p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1.5 [li_&amp;]:gap-1.5 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-2 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2"><strong>Cursor Agent CLI doesn&#8217;t make progress:</strong> This is rare, but you may not see execution progress due to different runtime issues. For such cases, it is important to define realistic timeouts for the pipeline step that involves this integration.</li>
<li class="whitespace-normal break-words pl-2"><strong>Cursor Agent CLI enters a loop:</strong> Sometimes, if you send an unclear or unrealistic user prompt, the process may enter a loop. To avoid this, review your user prompts, and if this happens, define clear timeouts to reduce costs and finish the process sooner.</li>
</ul>
<h3><span style="font-weight: 400">Scenario: Orchestrating Cursor Cloud Agent from a Pipeline</span></h3>
<p><span style="font-weight: 400">Imagine the scenario where you try to orchestrate an integration with the service Cursor Cloud Agent from a popular Pipeline. What issues could happen?</span></p>
<div id="attachment_6329" style="width: 610px" class="wp-caption alignnone"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6329" data-attachment-id="6329" data-permalink="https://www.javaadvent.com/2025/12/delegating-java-tasks-to-supervised-ai-dev-pipelines.html/image-21" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?fit=2076%2C870&amp;ssl=1" data-orig-size="2076,870" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?fit=300%2C126&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?fit=600%2C251&amp;ssl=1" class="wp-image-6329 size-large" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?resize=600%2C251&#038;ssl=1" alt="" width="600" height="251" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?resize=1024%2C429&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?resize=300%2C126&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?resize=768%2C322&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?resize=1536%2C644&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?resize=2048%2C858&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?resize=1240%2C520&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?resize=508%2C213&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-3.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a><p id="caption-attachment-6329" class="wp-caption-text">Scenario: Orchestrating Cursor Cloud Agents from the Pipeline</p></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>Issues at Pipeline level</strong></p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1.5 [li_&amp;]:gap-1.5 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-2 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2"><strong>Third-party dependencies:</strong> If your pipeline is too complex, the global reliability may suffer. Review the chain of dependencies to simplify your pipeline and avoid runtime issues. Log runtime issues with third-party dependencies. Using a predefined Docker image build could reduce the number of runtime issues.</li>
<li class="whitespace-normal break-words pl-2"><strong>The task related to Cursor Cloud Agent failed:</strong> It is not common, but the service can fail. In that case, I recommend logging the Agent ID returned from the launch operation (POST /v0/agents) and writing the internal conversation using the REST endpoint GET /v0/agents/{id}/conversation.</li>
<li class="whitespace-normal break-words pl-2"><strong>I am not able to create more Cursor Cloud Agents:</strong> If you use the service and submit the PR, at the end of the process, you should delete the agent to release resources at the Cursor level—the resources are not infinite. Use the endpoint DELETE /v0/agents.</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>Issues at Cursor Cloud Agent level</strong></p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1.5 [li_&amp;]:gap-1.5 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-2 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2"><strong>Cursor Cloud Agent finished with an ERROR state:</strong> Yes, it is not common, but sometimes it happens for different reasons. On the user side, for example, if you change a customized Cursor environment described in .cursor/environment.json and the associated Dockerfile, you could encounter this issue, but there could also be unknown runtime issues at the Cursor level. Logging the Agent ID and the conversation can be useful.</li>
<li class="whitespace-normal break-words pl-2"><strong>Cursor Cloud Agent doesn&#8217;t make progress:</strong> This is rare, but you may not see execution progress due to different runtime issues. For such cases, it is important to define realistic timeouts for the pipeline step that involves this integration.</li>
<li class="whitespace-normal break-words pl-2"><strong>Cursor Cloud Agent enters a loop:</strong> Sometimes, if you send an unclear or unrealistic user prompt, the process may enter a loop. To avoid this, review your user prompts, and if this happens, define clear timeouts to reduce costs and finish the process sooner.</li>
<li class="whitespace-normal break-words pl-2"><strong>Cursor Cloud Agent includes files not requested in the PR:</strong> When frontier models try to resolve the goals described in the user prompt, they sometimes need to create scripts, extra files, or simply store files for analysis and debugging. Refine your user prompts to specify exactly what files and file extensions are valid; another alternative is to combine this with .gitignore files.</li>
</ul>
<p>In general, it is a good practice to log the Agent ID for potential Cursor support and log the internal frontier model conversation for further analysis in order to improve the user prompt. Do not miss creating a threat model in your projects.</p>
<h2><span style="font-weight: 400">Real world scenarioS</span></h2>
<p><span style="font-weight: 400">If you have doubts about what scenarios could be used for this new cloud service, I&#8217;ll share a few scenarios that you might find interesting.</span></p>
<ul>
<li style="font-weight: 400"><b>Continuous documentation:</b><span style="font-weight: 400"> Not everyone loves documenting solutions, and sometimes the documentation is outdated over time. You could use this service to update the documentation at different levels for the team, externally, or simply to train people.</span></li>
<li style="font-weight: 400"><b>Continuous coding standard refactoring:</b><span style="font-weight: 400"> People come and go on your team, and everyone has a different programming style. In Java, you can establish format rules with plugins like Spotless or similar, but there is no tooling to unify the programming paradigm or style. Google and other companies have published Java guides—why not refactor your software using your style? If you have good tests, you&#8217;re safe.</span></li>
<li style="font-weight: 400"><b>Fix changes if a third party breaks the contract:</b><span style="font-weight: 400"> Oh my god, that team changed the contract again, and the product owner didn&#8217;t estimate the task in the sprint. Okay, let&#8217;s monitor the contract—if something changes, let&#8217;s delegate the action to evaluate the level of change and determine if it&#8217;s acceptable, then adapt the anticorruption layer to the new change.</span></li>
<li style="font-weight: 400"><b>Continuous Sonar cleanup:</b><span style="font-weight: 400"> Oh my god, the Sonar gate is blocked again, and the product owner doesn&#8217;t allow us to release the product. Okay, let&#8217;s run the pipeline that retrieves the failing security hotspots and issues with blocker &amp; high severity to be fixed today.</span></li>
<li style="font-weight: 400"><b>Continuous profiling:</b><span style="font-weight: 400"> Not everyone on the team has good skills to understand files like flamegraphs, thread dump files, GC logs, etc. But why not delegate that task to the service to discover new opportunities to improve your products?</span></li>
<li style="font-weight: 400"><b>Simplify complexity:</b><span style="font-weight: 400"> Periodically, you could run a pipeline that reviews the current implementation to simplify architecture, implementation, data types used, etc. Simple systems are maintained better.</span></li>
<li style="font-weight: 400"><b>Empower people based on team issues:</b><span style="font-weight: 400"> Periodically, issues reported in the ticket system or similar platforms could be a good source of ideas to train the squad and sharpen the axe.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">…</span></li>
<li>Solve the <strong><em>Advent of Code 2025</em></strong> with a scheduled pipeline every day.</li>
</ul>
<div id="attachment_6371" style="width: 610px" class="wp-caption alignnone"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6371" data-attachment-id="6371" data-permalink="https://www.javaadvent.com/2025/12/delegating-java-tasks-to-supervised-ai-dev-pipelines.html/image-22" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?fit=1164%2C1086&amp;ssl=1" data-orig-size="1164,1086" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?fit=300%2C280&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?fit=600%2C560&amp;ssl=1" class="wp-image-6371 size-large" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?resize=600%2C560&#038;ssl=1" alt="" width="600" height="560" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?resize=1024%2C955&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?resize=300%2C280&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?resize=768%2C717&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?resize=508%2C474&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-4.png?w=1164&amp;ssl=1 1164w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a><p id="caption-attachment-6371" class="wp-caption-text">https://adventofcode.com/2025</p></div>
<p><span style="font-weight: 400">Creativity and your monthly budget mark the limit.</span></p>
<h2><span style="font-weight: 400">LIMITATIONS</span></h2>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">This technology is awesome, but you should consider the following factors:</p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1.5 [li_&amp;]:gap-1.5 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-2 pl-8 mb-3">
<li class="whitespace-normal break-words pl-2"><strong>Cognitive load</strong>: When adding these new virtual hands to your squad, you need to ensure that everyone is able to review the new PRs with quality. Apart from using this technology for delivery, review the new opportunities to strengthen the team based on the issues identified as input.</li>
<li class="whitespace-normal break-words pl-2"><strong>Budget</strong>: This technology is not free. It is another input for your engineering manager in terms of cost and resources to maintain the prompts and pipelines.</li>
<li class="whitespace-normal break-words pl-2"><strong>Resources</strong>: You could create several pipelines, but you need to calibrate with the current cadence of PR reviews to avoid saturating the process.</li>
</ul>
<h2><span style="font-weight: 400">ExampleS in action</span></h2>
<h3><span style="font-weight: 400">using cursor agent cli in action: Orchestrating Cursor Cloud Agent from a Pipeline</span></h3>
<p>Review the following <a href="https://github.com/jabrena/cursor-agent-cli-demo/blob/main/.github/workflows/agent-on-demand.yml">step</a> to understand how to run a pipeline with Cursor Agent CLI using user prompts based on PML.</p>
<pre> - name: Run Cursor Agent<br />   env:<br />     CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}<br />   run: |<br />     echo "=== User Prompt:===";<br />     jbang trust add https://github.com/jabrena/<br />     PROMPT=$(jbang pml-to-md.0.4.0-SNAPSHOT@jabrena convert pml-hello-world-java.xml)<br />     echo "$PROMPT";<br />     echo "=== Cursor Agent Execution:===";<br />     echo "";<br />     cursor-agent -p "$PROMPT" --model auto</pre>
<p>In the previous example, the Cursor agent processes a user prompt in Markdown which was originally created in XML (using a PML schema).</p>
<h3><span style="font-weight: 400">Orchestrating Cursor Cloud Agent from a Pipeline</span></h3>
<p>A picture is worth a thousand words. You can see a service that monitors Cursor Cloud Agent runtime at the following address: <a href="https://jabrena.github.io/cursor-cloud-agent-rest-api-status/">https://jabrena.github.io/cursor-cloud-agent-rest-api-status/ </a></p>
<div id="attachment_6316" style="width: 610px" class="wp-caption alignnone"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6316" data-attachment-id="6316" data-permalink="https://www.javaadvent.com/2025/12/delegating-java-tasks-to-supervised-ai-dev-pipelines.html/image-19" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?fit=2148%2C1284&amp;ssl=1" data-orig-size="2148,1284" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?fit=300%2C179&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?fit=600%2C359&amp;ssl=1" class="wp-image-6316 size-large" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?resize=600%2C359&#038;ssl=1" alt="" width="600" height="359" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?resize=1024%2C612&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?resize=300%2C179&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?resize=768%2C459&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?resize=1536%2C918&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?resize=2048%2C1224&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?resize=1240%2C741&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?resize=508%2C304&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/image-1.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a><p id="caption-attachment-6316" class="wp-caption-text">Cursor Cloud Agent REST API Status</p></div>
<p>Every hour, the service tests the execution to verify different aspects of the solution. After a month of running the service, I can assert that latencies are stable, and this fact is important when designing AI solutions that don&#8217;t require near real-time feedback. Further information about the pipeline here: <a href="https://github.com/jabrena/cursor-cloud-agent-rest-api-status/blob/main/.github/workflows/scheduled-ping-agent.yaml">https://github.com/jabrena/cursor-cloud-agent-rest-api-status/blob/main/.github/workflows/scheduled-ping-agent.yaml</a></p>
<p><b>Note</b><span style="font-weight: 400">: The service has been running for more than 1 month (30 × 24 × 4 samples stored). Under the hood, the pipeline uses Churrera CLI, an Open source Java CLI tool designed to orchestrate Cursor Cloud Agents and measure latencies.</span></p>
<h2><span style="font-weight: 400">Takeaways</span></h2>
<ul>
<li>Cursor Agent CLI is a nice way to start using frontier models in pipelines.</li>
<li style="font-weight: 400">Cursor Cloud Agents is a nice way to use frontier models for parallel and complex scenarios because it offers an easy REST interface with fine-grained control over the process.</li>
<li style="font-weight: 400"><span style="font-weight: 400">Running a pilot in non-critical services could be a good way to understand the possibilities and train people.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">PML is a great way to write robust user prompts based on XML, which can be verified and transformed later to other hierarchical formats like Markdown.</span></li>
<li>Decouple User prompts from Agent systems. It will be considered as another IT asset in the future.</li>
<li>You can combine User prompts plus System prompts like Cursor rules or Claude Skills to enrich the final result.</li>
<li style="font-weight: 400"><span style="font-weight: 400">Both alternatives create pull requests, the team has the last word on accepting the new code added to the main/develop branch. This model is also compatible if you use Extreme Programming.</span></li>
<li>Review your engineering processes to include a threat modeling task for new operations that include AI.</li>
<li>Models can interact with the environment using CLI tools or MCP tools.</li>
<li>You can assign the operation of these pipelines to junior profiles because these problems were modeled with user prompts, so the risk is very limited. However, there exists a nice opportunity to improve the solutions with ideas like adding functional programming patterns, improving the OOP design, improving performance based on multiple factors, etc.</li>
</ul>
<h2><span style="font-weight: 400">References</span></h2>
<ul class="wp-block-list">
<li><a href="https://cursor.com/docs/cloud-agent">https://cursor.com/docs/cloud-agent</a></li>



<li><a href="https://cursor.com/docs/cloud-agent/api/endpoints">https://cursor.com/docs/cloud-agent/api/endpoints</a> </li>



<li><a href="https://editor.swagger.io/?url=https://cursor.com/docs-static/cloud-agents-openapi.yaml">https://editor.swagger.io/?url=https://cursor.com/docs-static/cloud-agents-openapi.yaml</a></li>
<li><a href="https://cursor.com/agents">https://cursor.com/agents</a></li>
<li><a href="https://github.com/jabrena/pml"><span style="font-weight: 400">https://github.com/jabrena/pml</span></a><span style="font-weight: 400"> </span></li>
<li><a href="https://github.com/jabrena/churrera-cli"><span style="font-weight: 400">https://github.com/jabrena/churrera-cli</span></a><span style="font-weight: 400"> </span></li>
<li><a href="https://github.com/jabrena/cursor-rules-java"><span style="font-weight: 400">https://github.com/jabrena/cursor-rules-java</span></a><span style="font-weight: 400"> </span></li>
<li><a href="https://github.com/jabrena/cursor-cloud-agent-rest-api-status"><span style="font-weight: 400">https://github.com/jabrena/cursor-cloud-agent-rest-api-status</span></a><span style="font-weight: 400"> </span></li>
<li><a href="https://contextmapper.org/docs/examples/"><span style="font-weight: 400">https://contextmapper.org/docs/examples/</span></a></li>
<li><a href="https://modelcontextprotocol.io/docs/getting-started/intro">https://modelcontextprotocol.io/docs/getting-started/intro</a></li>
</ul>
<p>&nbsp;</p>
<!-- /wp:post-content --><img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fdelegating-java-tasks-to-supervised-ai-dev-pipelines.html&amp;action_name=Delegating%20Java%20tasks%20to%20Supervised%20AI%20Dev%20Pipelines&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/delegating-java-tasks-to-supervised-ai-dev-pipelines.html">Delegating Java tasks to Supervised AI Dev Pipelines</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/delegating-java-tasks-to-supervised-ai-dev-pipelines.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6278</post-id>	</item>
		<item>
		<title>How to really measure LLMs for JVM Code? A Benchmarking guide for late 2025</title>
		<link>https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html</link>
					<comments>https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html#respond</comments>
		
		<dc:creator><![CDATA[Artur Skowronski]]></dc:creator>
		<pubDate>Sun, 21 Dec 2025 04:04:59 +0000</pubDate>
				<category><![CDATA[2017]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6343</guid>

					<description><![CDATA[<p>If you’re reading this, you’re probably already using some LLM for coding. Maybe it’s Copilot, maybe Claude Code, maybe Cursor with Gemini enabled (or Cursor’s own model). You know the drill. Do you truly expect the announcement &#8220;We are worst than competitors?&#8221; The problem is that when someone asks, “Which model is best for Java?”, [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fhow-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html&amp;action_name=How%20to%20really%20measure%20LLMs%20for%20JVM%20Code%3F%20A%20Benchmarking%20guide%20for%20late%202025&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html">How to really measure LLMs for JVM Code? A Benchmarking guide for late 2025</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="flex flex-col text-sm @w-xl/main:pt-header-height pb-25">
<article class="text-token-text-primary w-full focus:outline-none [--shadow-height:45px] has-data-writing-block:pointer-events-none has-data-writing-block:-mt-(--shadow-height) has-data-writing-block:pt-(--shadow-height) [&amp;:has([data-writing-block])&gt;*]:pointer-events-auto [content-visibility:auto] supports-[content-visibility:auto]:[contain-intrinsic-size:auto_100lvh] scroll-mt-[calc(var(--header-height)+min(200px,max(70px,20svh)))]" dir="auto" data-turn-id="e48d9eb8-9687-4ef0-ab55-a0324ba99ff5" data-testid="conversation-turn-2" data-scroll-anchor="true" data-turn="assistant">
<div class="text-base my-auto mx-auto pb-10 [--thread-content-margin:--spacing(4)] @w-sm/main:[--thread-content-margin:--spacing(6)] @w-lg/main:[--thread-content-margin:--spacing(16)] px-(--thread-content-margin)">
<div class="[--thread-content-max-width:40rem] @w-lg/main:[--thread-content-max-width:48rem] mx-auto max-w-(--thread-content-max-width) flex-1 group/turn-messages focus-visible:outline-hidden relative flex w-full min-w-0 flex-col agent-turn">
<div class="flex max-w-full flex-col grow">
<div class="min-h-8 text-message relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal [.text-message+&amp;]:mt-1" dir="auto" data-message-author-role="assistant" data-message-id="0b10ef34-ded9-4066-8efc-8b3dfcb900ca" data-message-model-slug="gpt-5-2">
<div class="flex w-full flex-col gap-1 empty:hidden first:pt-[1px]">
<div class="markdown prose dark:prose-invert w-full break-words dark markdown-new-styling">
<p data-start="127" data-end="597">If you’re reading this, you’re probably already using some LLM for coding. Maybe it’s Copilot, maybe Claude Code, maybe Cursor with Gemini enabled (or Cursor’s own model).</p>
<p data-start="127" data-end="597">You know the drill. Do you truly expect the announcement &#8220;We are worst than competitors?&#8221;</p>
<p data-start="127" data-end="597"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6351" data-permalink="https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html/cveag78ara1g1" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?fit=1214%2C1166&amp;ssl=1" data-orig-size="1214,1166" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="cveag78ara1g1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?fit=300%2C288&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?fit=600%2C577&amp;ssl=1" class="alignnone size-full wp-image-6351" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?resize=600%2C576&#038;ssl=1" alt="" width="600" height="576" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?w=1214&amp;ssl=1 1214w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?resize=300%2C288&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?resize=1024%2C984&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?resize=768%2C738&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cveag78ara1g1.webp?resize=508%2C488&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p data-start="127" data-end="597">The problem is that when someone asks, “Which model is best for Java?”, most answers are based either on a vibes check and opinions, or on re-published benchmarks that, frankly, hardly anyone truly understands &#8211; especially what they actually test and how that translates to everyday engineering work.</p>
<p data-start="599" data-end="869">Today, we’re changing that. We’ll walk through how LLM benchmarking for code actually works, which models are available on the market at the end of 2025, and &#8211; most importantly &#8211; how they perform in benchmarks that are specific to the JVM, including Java, Kotlin, and Scala.</p>
<h2 data-start="871" data-end="929">How Do You Even Measure an LLM’s Ability to Write Code?</h2>
<h3 data-start="931" data-end="999">The Evolution: from “Can You Code?” to “Can You Be an Engineer?”</h3>
<p data-start="1001" data-end="1168">The history of LLM coding benchmarks is a story of steadily rising expectations. It all started with a very basic question: can the model generate working code at all?</p>
<p data-start="1170" data-end="1477"><strong data-start="1170" data-end="1190"><a href="https://github.com/openai/human-eval">HumanEval</a></strong> described in the paper <a href="https://arxiv.org/abs/2107.03374" rel="nofollow">Evaluating Large Language Models Trained on Code</a> is the foundation of almost everything that came afterward. It consists of 164 hand-written Python problems, each with a set of unit tests. The model is given a function signature and a docstring and must generate the function body. Sounds simple? In 2021, even GPT-3 struggled with it.</p>
<p data-start="1479" data-end="1772">The key innovation of HumanEval was the <strong data-start="1519" data-end="1529">pass@k</strong> metric. Instead of measuring textual similarity (like BLEU score), we check whether the generated code actually passes the tests. This is a fundamental shift in thinking: we don’t care whether the code <em data-start="1732" data-end="1739">looks</em> good &#8211; we are interested whether it <em data-start="1764" data-end="1771">works</em>.</p>
<p data-start="1479" data-end="1772"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/1719300500107.jpeg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6347" data-permalink="https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html/attachment/1719300500107" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/1719300500107.jpeg?fit=888%2C499&amp;ssl=1" data-orig-size="888,499" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="1719300500107" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/1719300500107.jpeg?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/1719300500107.jpeg?fit=600%2C337&amp;ssl=1" class="alignnone size-full wp-image-6347" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/1719300500107.jpeg?resize=600%2C337&#038;ssl=1" alt="" width="600" height="337" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/1719300500107.jpeg?w=888&amp;ssl=1 888w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/1719300500107.jpeg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/1719300500107.jpeg?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/1719300500107.jpeg?resize=508%2C285&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p data-start="1774" data-end="2002">So what’s the problem with HumanEval? It’s too easy. By the end of 2024, top models were achieving 90%+ on this benchmark. When all the leading models cluster around 90%, it becomes very hard to say which one is actually better.</p>
<p data-start="2004" data-end="2160" data-is-last-node="" data-is-only-node="">A similar alternative to HumanEval is <strong data-start="2042" data-end="2081"><a href="https://github.com/google-research/google-research/tree/master/mbpp">MBPP (Mostly Basic Python Problems)</a> &#8211; </strong>974 entry-level problems. More data, but roughly the same level of difficulty.</p>
</div>
</div>
</div>
</div>
<div class="z-0 flex min-h-[46px] justify-start">
<div class="flex max-w-full flex-col grow">
<div class="min-h-8 text-message relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal [.text-message+&amp;]:mt-1" dir="auto" data-message-author-role="assistant" data-message-id="6906ef6a-9290-4f11-a5b6-01d029fb58a3" data-message-model-slug="gpt-5-2">
<div class="flex w-full flex-col gap-1 empty:hidden first:pt-[1px]">
<div class="markdown prose dark:prose-invert w-full break-words dark markdown-new-styling">
<h3 data-start="157" data-end="191">The Modern Standard: SWE-bench</h3>
<p data-start="193" data-end="643">The real revolution arrived with <a href="https://github.com/SWE-bench/SWE-bench"><strong data-start="226" data-end="239">SWE-bench</strong></a> (Software Engineering Benchmark). Instead of asking a model to fill in a missing function or solve an isolated coding puzzle, SWE-bench puts it in a situation that looks much closer to real work. The model is given the full source code of a real GitHub repository, along with the description of an issue taken directly from GitHub Issues, and is asked to produce a patch that actually fixes the problem.</p>
<p data-start="645" data-end="1228">At this point, we’re no longer testing whether a model can “code” in the narrow sense. We’re testing whether it can behave like a software engineer. To succeed, the model has to understand an existing codebase, navigate across multiple files, and reason about how different components interact. It needs to interpret the often messy, incomplete, or ambiguous business context hidden in an issue description, follow the project’s established conventions, and produce a change that solves the problem without breaking existing tests. This is software engineering, not algorithm trivia.</p>
<p data-start="1230" data-end="1536"><a href="https://openai.com/index/introducing-swe-bench-verified/"><strong data-start="1230" data-end="1252">SWE-bench Verified</strong></a> raises the bar even further. It is a curated subset of 500 tasks that have been manually verified by humans, and it has effectively become the industry’s gold standard. When vendors talk about their models’ real-world coding capabilities, this is the benchmark they usually point to.</p>
<p data-start="1538" data-end="2045" data-is-last-node="" data-is-only-node="">However, SWE-bench also has a fundamental limitation. It is exclusively focused on Python, which immediately makes it less useful for large parts of the industry. On top of that, it has been around long enough that there is a growing suspicion that some models may have been trained on it, at least partially. That doesn’t make the benchmark useless, but it does mean we should treat impressive scores with a healthy dose of skepticism &#8211; especially if we care about JVM languages like Java, Kotlin, and Scala.</p>
<p data-start="1538" data-end="2045" data-is-last-node="" data-is-only-node="">What&#8217;s particularly striking about SWE-bench is the scale of the solutions it expects. The mean lines of code per solution is just 11, with a median of only 4 lines. Amazon&#8217;s analysis found that over 77.6% of the solutions touch only one function. This tells us something important: SWE-bench is testing surgical precision on isolated problems, not the kind of sprawling, multi-component changes that often dominate real engineering work. Additionally, over 40% of the problems come from the Django repository alone, which introduces significant bias toward one project&#8217;s patterns and conventions.</p>
<div class="flex max-w-full flex-col grow">
<div class="min-h-8 text-message relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal [.text-message+&amp;]:mt-1" dir="auto" data-message-author-role="assistant" data-message-id="ceb4034b-8774-4d2e-b464-f31f12860342" data-message-model-slug="gpt-5-2">
<div class="flex w-full flex-col gap-1 empty:hidden first:pt-[1px]">
<div class="markdown prose dark:prose-invert w-full break-words dark markdown-new-styling">
<p data-start="0" data-end="163" data-is-last-node="" data-is-only-node="">Additionally, as it has become the most widely recognized, it regularly appears in new model announcements and marketing materials. The results are… interesting <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f601.png" alt="😁" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</div>
</div>
</div>
</div>
<p data-start="1538" data-end="2045" data-is-last-node="" data-is-only-node=""><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/13-Artificial-Non-Intelligence.jpeg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6348" data-permalink="https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html/13-artificial-non-intelligence" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/13-Artificial-Non-Intelligence.jpeg?fit=518%2C600&amp;ssl=1" data-orig-size="518,600" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="13-Artificial-Non-Intelligence" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/13-Artificial-Non-Intelligence.jpeg?fit=259%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/13-Artificial-Non-Intelligence.jpeg?fit=518%2C600&amp;ssl=1" class="alignnone size-full wp-image-6348" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/13-Artificial-Non-Intelligence.jpeg?resize=518%2C600&#038;ssl=1" alt="" width="518" height="600" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/13-Artificial-Non-Intelligence.jpeg?w=518&amp;ssl=1 518w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/13-Artificial-Non-Intelligence.jpeg?resize=259%2C300&amp;ssl=1 259w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/13-Artificial-Non-Intelligence.jpeg?resize=508%2C588&amp;ssl=1 508w" sizes="auto, (max-width: 518px) 100vw, 518px" /></a></p>
<p>Scale AI has attempted to address these limitations with <a href="https://scale.com/leaderboard/swe_bench_pro_public">SWE-bench Pro</a>, a significantly improved successor.</p>
<p>Instead of 500 Python-only problems, it offers 1,865 tasks drawn from 41 repositories across Python, Go, JavaScript, and TypeScript. The solutions are substantially larger &#8211; averaging 107 lines of code with a median of 55 lines, typically spanning 4 files. The benchmark also covers a more diverse range of software types: consumer applications with complex UI logic, B2B platforms with intricate business rules, and developer tools with sophisticated APIs. Crucially, humans rewrote the problem descriptions based on issues, commits, and PRs to ensure no missing information, and they added explicit requirements grounded in the unit tests used for validation. All environments are dockerized with dependencies pre-installed, so the benchmark explicitly does not test repository setup &#8211; just the engineering work itself.</p>
<h3 data-start="121" data-end="171">New Benchmarks: BigCodeBench and LiveCodeBench</h3>
<p data-start="173" data-end="766"><a href="https://github.com/bigcode-project/bigcodebench"><strong data-start="173" data-end="189">BigCodeBench</strong></a> emerged as a response to the growing criticism that existing coding benchmarks had simply become too easy. Instead of testing toy problems, it raises the bar by introducing 1,140 tasks that require real interaction with 139 different libraries. On average, each task comes with 5.6 tests and achieves 99% branch coverage. At this level, knowing the syntax is no longer enough. The model needs to understand how to actually <em data-start="613" data-end="618">use</em> libraries like pandas, numpy, requests, and dozens of others in realistic ways—exactly the kind of knowledge developers rely on in day-to-day work.</p>
<p data-start="768" data-end="1388" data-is-last-node="" data-is-only-node=""><a href="https://livecodebench.github.io/"><strong data-start="768" data-end="785">LiveCodeBench</strong></a>, on the other hand, tackles a completely different but equally important problem: data contamination. Its tasks are sourced from weekly programming contests on platforms like LeetCode, AtCoder, and Codeforces, and each task is tied to a specific publication date. This allows evaluators to check whether a model could realistically have seen the problem during training. If a model was trained before a given date, it simply couldn’t have memorized that task. In practice, this makes LiveCodeBench one of the most credible attempts so far to measure genuine generalization rather than benchmark recall.</p>
</div>
</div>
</div>
</div>
<div class="mt-3 w-full empty:hidden">
<div class="text-center">
<div class="mx-auto">
<div class="inline-flex rounded-xl border border-gray-100 dark:border-gray-700">
<div class="me-12 flex items-center px-4 py-3 text-start">
<div class="ms-4">
<h2 data-start="99" data-end="133">Multilinguality: Where Is Java?</h2>
<p data-start="135" data-end="376">And this is where we get to the heart of the problem. A review of 24 major coding benchmarks reveals a rather uncomfortable statistic. An overwhelming 95.8% of existing benchmarks focus on Python, while only five of them include Java at all.</p>
<p data-start="378" data-end="767" data-is-last-node="" data-is-only-node="">This imbalance isn’t accidental. Python dominates machine learning research, so benchmarks are naturally designed around the language researchers themselves use every day. The result is a benchmarking ecosystem that tells us a lot about how well models perform in Python, but surprisingly little about their real capabilities in languages like Java &#8211; or, by extension, the broader JVM world.</p>
<div class="flex max-w-full flex-col grow">
<div class="min-h-8 text-message relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal [.text-message+&amp;]:mt-1" dir="auto" data-message-author-role="assistant" data-message-id="687edbb3-93cd-42fe-ab38-b55d10cf39b1" data-message-model-slug="gpt-5-2">
<div class="flex w-full flex-col gap-1 empty:hidden first:pt-[1px]">
<div class="markdown prose dark:prose-invert w-full break-words dark markdown-new-styling">
<p data-start="129" data-end="740"><a href="https://github.com/nuprl/MultiPL-E"><strong data-start="129" data-end="142">MultiPL-E</strong></a> is an effort to address this imbalance by translating the HumanEval and MBPP benchmarks into 18 additional programming languages, including <strong data-start="283" data-end="310">Java, Kotlin, and Scala</strong>.</p>
<p data-start="129" data-end="740">On paper, this sounds like exactly what the ecosystem needs. In practice, however, there’s a catch. Automatic translation doesn’t always capture the idioms of a given language. A Java test mechanically translated from Python may compile and run, but it often fails to exercise what actually matters in an object-oriented context. Instead of testing real JVM-style design, it may still be implicitly testing Pythonic assumptions.</p>
<p data-start="760" data-end="1376" data-is-last-node="" data-is-only-node=""><a href="https://github.com/floatai/HumanEval-XL"><strong data-start="760" data-end="776">HumanEval-XL</strong></a> takes a slightly different approach by expanding the benchmark to cover 12 programming languages, including Python, Java, Go, Kotlin, PHP, Ruby, Scala, JavaScript, C#, Perl, Swift, and TypeScript. It also introduces 80 problems written in 23 natural languages, which makes it more diverse than earlier efforts. That said, while it is certainly better than nothing, it still falls short when it comes to evaluating realistic enterprise Java scenarios.</p>
<p data-start="760" data-end="1376" data-is-last-node="" data-is-only-node="">The problems remain small and isolated, far removed from the kinds of codebases and architectural concerns that dominate real-world JVM development.</p>
<p data-start="760" data-end="1376" data-is-last-node="" data-is-only-node=""><strong><a href="https://epoch.ai/benchmarks/aider-polyglot">Aider Polyglot</a> </strong>deserves special attention for JVM developers because it actually includes Java. The benchmark consists of 225 hard-level Exercism problems distributed across six languages: JavaScript (49), Java (47), Go (39), Python (34), Rust (30), and C++ (26). Solutions typically range from 30 to 200 lines of code and span at most 2 files. The evaluation allows one round of feedback before final assessment &#8211; mimicking a realistic back-and-forth with a coding assistant. While this is far from enterprise-scale Java work, it remains one of the few benchmarks that can tell us anything concrete about model performance on JVM languages in a standardized way.</p>
</div>
</div>
</div>
</div>
</div>
<div class="z-0 flex min-h-[46px] justify-start">
<h3 data-start="118" data-end="169">JavaBench: The First Benchmark Dedicated to OOP</h3>
<p data-start="171" data-end="578"><a href="https://github.com/java-bench/JavaBench"><strong data-start="171" data-end="184">JavaBench</strong></a> is a direct response to Python’s dominance in coding benchmarks. Instead of abstract problems or toy functions, it is built around four real Java projects, covering 389 methods across 106 classes, with an impressive 92% test coverage. The benchmark was additionally validated by 282 students, who achieved an average score of 90.93 out of 100, giving us a meaningful human baseline.</p>
<p data-start="580" data-end="904">What truly sets JavaBench apart is its focus on <strong data-start="628" data-end="668">object-oriented programming features</strong>. It explicitly evaluates concepts such as encapsulation, inheritance, and polymorphism &#8211; areas that benchmarks like HumanEval do not even attempt to measure. This makes it far more representative of how Java is actually used in practice.</p>
<p data-start="906" data-end="1342">JavaBench is nice try, but not the most active supported thing. We have better alternatives at the market.</p>
<h3 data-start="1344" data-end="1391">CoderUJB: Benchmarking Real-World Java Work</h3>
<p data-start="1393" data-end="1847"><a href="https://arxiv.org/abs/2403.19287"><strong data-start="1393" data-end="1405">CoderUJB</strong></a> pushes realism even further. It is built on 17 real open-source Java projects and contains 2,239 programming questions spanning multiple task types. These include not only code generation, but also test generation, bug fixing, and defect detection. The point here is no longer just to check whether a model can produce syntactically correct code, but whether it can perform the kinds of activities a real Java developer deals with every day.</p>
<h3 data-start="1849" data-end="1899">Brokk Power Ranking: A Java Benchmark for 2025</h3>
<p data-start="1901" data-end="2295">The <a href="https://blog.brokk.ai/introducing-the-brokk-power-ranking/"><strong data-start="1905" data-end="1928">Brokk Power Ranking</strong></a>, created by Jonathan Ellis, co-creator of Apache Cassandra, is one of the freshest additions to the benchmarking landscape and directly addresses some of the core weaknesses of SWE-bench. Unlike most existing benchmarks, it is not Python-only. Instead, it draws tasks from real Java repositories such as Brokk, JGit, LangChain4j, Apache Cassandra, and Apache Lucene.</p>
<p data-start="2297" data-end="2764" data-is-last-node="" data-is-only-node="">Equally important, it is genuinely fresh. The tasks are derived from commits made within the last six months, which significantly reduces the risk that models were trained on the benchmark data. It is also intentionally challenging, featuring 93 tasks with contexts reaching up to 108k tokens. Ellis positions it deliberately between AiderBench, which tends toward toy problems, and SWE-bench, which often throws entire repositories at the model and says “good luck.”</p>
<p data-start="2297" data-end="2764" data-is-last-node="" data-is-only-node=""><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6344" data-permalink="https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html/brokk" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?fit=1065%2C546&amp;ssl=1" data-orig-size="1065,546" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Brokk" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?fit=300%2C154&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?fit=600%2C308&amp;ssl=1" class="alignnone size-full wp-image-6344" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?resize=600%2C308&#038;ssl=1" alt="" width="600" height="308" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?w=1065&amp;ssl=1 1065w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?resize=300%2C154&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?resize=1024%2C525&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?resize=768%2C394&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Brokk.png?resize=508%2C260&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p data-start="116" data-end="780"><a href="https://brokk.ai/power-ranking"><strong data-start="116" data-end="147">Results as of November 2025</strong></a> paint a much clearer picture of how models actually perform in Java-centric, real-world scenarios. At the very top, the S tier is occupied by GPT-5.1 and Claude Opus 4.5, which clearly separate themselves from the rest of the field. Just below them, the A tier includes GPT-5, GPT-5 Mini, and Grok Code Fast 1, forming a strong upper-middle group with solid but slightly less consistent performance. The B tier is represented by Claude Sonnet 4.5 and GLM 4.6, while the C tier includes Grok 4.1 Fast, Gemini 2.5 Flash, Gemini 3 Pro (Preview), and DeepSeek-V3.2. At the bottom, in the D tier, we find Kimi K2 Thinking and MiniMax M2.</p>
<p data-start="782" data-end="1122">Several patterns stand out immediately. Chinese models such as DeepSeek-V3, Kimi K2, and Qwen3 Coder perform noticeably worse here than they do on benchmarks like SWE-bench or AiderBench. This suggests that their apparent strength on more generic or Python-heavy evaluations does not translate well to Java-heavy, object-oriented codebases.</p>
<p data-start="782" data-end="1122"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6349" data-permalink="https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html/dbb" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?fit=1314%2C1104&amp;ssl=1" data-orig-size="1314,1104" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="dbb" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?fit=300%2C252&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?fit=600%2C504&amp;ssl=1" class="alignnone size-full wp-image-6349" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?resize=600%2C504&#038;ssl=1" alt="" width="600" height="504" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?w=1314&amp;ssl=1 1314w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?resize=300%2C252&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?resize=1024%2C860&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?resize=768%2C645&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?resize=1240%2C1042&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/dbb.png?resize=508%2C427&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p data-start="1124" data-end="1420">Another clear takeaway is GPT-5’s dominance across every price tier. No matter whether you look at premium or more cost-conscious options, GPT-5-based models consistently lead in terms of raw capability. The trade-off, however, is speed: GPT-5 remains relatively slow compared to its competitors.</p>
<p data-start="1422" data-end="1764" data-is-last-node="" data-is-only-node="">Finally, Claude Sonnet 4 stands out for a very different reason. While it does not top the absolute performance charts, it is <em data-start="1548" data-end="1564">screaming fast &#8211; </em>faster than all models in tiers A and B. For workflows where latency matters as much as correctness, this makes it a surprisingly compelling choice despite not sitting at the very top of the ranking.</p>
<p data-start="883" data-end="1492">Kotlin is in interesting position in one important respect: specialization. <a href="https://huggingface.co/JetBrains/Mellum-4b-base"><strong data-start="975" data-end="985">Mellum</strong></a>, used inside JetBrains AI Assistant, is currently the only model with dedicated fine-tuning specifically for Kotlin, which gives it a noticeable edge in understanding Kotlin idioms and conventions. Beyond that, <strong data-start="1197" data-end="1207">Claude</strong> models tend to handle Kotlin surprisingly well, especially when it comes to expressive syntax and functional-style constructs.</p>
<p data-start="1494" data-end="2023">Scala, unfortunately, remains the most challenging case. No mainstream model has dedicated fine-tuning for Scala, which puts the language at a structural disadvantage. That said, the best reported results so far come from <strong data-start="1716" data-end="1735">Claude Opus 4.5</strong>, which benefits from a strong grasp of functional programming concepts, and <strong data-start="1812" data-end="1821">GPT-5</strong>, which shows solid performance on Scala tasks in benchmarks such as MultiPL-E. These models can be effective, but they still require more guidance and validation than their Java or Kotlin counterparts.</p>
<h2 data-start="2025" data-end="2047">Practical Takeaways</h2>
<p>Before diving into model recommendations, it is worth stepping back to consider what benchmarks actually measure &#8211; and what they do not. When we say an agent scores 25% on SWE-bench Pro, we are saying: in a problem set of well-defined issues with explicit requirements and specified interfaces, 25% of the agent&#8217;s solutions pass the relevant unit tests. This is useful for tracking progress, but it is not software engineering as most practitioners understand it. The high-leverage parts of real SWE work &#8211; collaborating with stakeholders to develop specifications, translating ambiguous requirements into clean interfaces, writing secure and maintainable code &#8211; remain entirely unmeasured. We know the code passes tests; we have no idea if it is maintainable, secure, or well-crafted. The UTBoost paper goes further, demonstrating that many SWE-bench solutions pass unit tests without actually resolving the underlying issues. Keep this gap in mind when interpreting any benchmark results.</p>
<p data-start="2049" data-end="2548">When choosing a model, the most important rule is not to trust any single benchmark blindly. SWE-bench, while influential, is Python-only, and HumanEval is simply too trivial to say much about real-world engineering in 2025. Benchmarks that span multiple languages- such as Aider Polyglot or the Brokk Power Ranking &#8211; are far more informative for JVM developers. Even then, no benchmark can replace testing a model directly on your own codebase, with your own architectural constraints and conventions.</p>
<p data-start="2550" data-end="3338">Looking at broader trends for 2025, Java is finally starting to receive more focused attention. The emergence of JavaBench, CoderUJB, and the Brokk Power Ranking is a clear signal that the ecosystem is moving beyond Python-centric evaluation. At the same time, specialized models are becoming more prominent, with Mellum for Kotlin and tools like Codestral for code completion pointing toward a future of narrower but deeper optimization. Another clear pattern is that reasoning increasingly matters: models with explicit “thinking” or extended reasoning modes tend to perform better on complex, multi-step tasks. Context size also plays a critical role, with models like Gemini 3 Pro &#8211; offering up to one million tokens &#8211; making it feasible to work with entire codebases in a single session.</p>
<p data-start="3340" data-end="3897">There are also some pitfalls worth actively avoiding. “Benchmark gaming” is becoming more visible, particularly when models show suspiciously strong results on a single benchmark like SWE-bench but fail to replicate that performance elsewhere. Relying on outdated benchmarks is equally misleading—HumanEval from 2021 tells us very little about model capabilities in 2025. Finally, one-off tests are unreliable by nature, since LLM performance is probabilistic. Meaningful evaluation requires repeated runs and consistent patterns, not a single lucky output.</p>
<p data-start="3911" data-end="4298">So, the state of LLM benchmarking for JVM languages in 2025 is&#8230;  complicated. Python still dominates research and evaluation, but genuinely JVM-focused benchmarks are finally emerging, especially for Java, with Brokk Power Ranking leading the way. Kotlin benefits from an steward in JetBrains and experiments like in Mellum, while Scala is still waiting for truly dedicated tooling.</p>
<p data-start="3911" data-end="4298"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6346" data-permalink="https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html/cost" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?fit=1064%2C481&amp;ssl=1" data-orig-size="1064,481" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="cost" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?fit=300%2C136&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?fit=600%2C271&amp;ssl=1" class="alignnone size-full wp-image-6346" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?resize=600%2C271&#038;ssl=1" alt="" width="600" height="271" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?w=1064&amp;ssl=1 1064w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?resize=300%2C136&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?resize=1024%2C463&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?resize=768%2C347&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/cost.png?resize=508%2C230&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p data-start="4300" data-end="4636" data-is-last-node="" data-is-only-node="">For everyday development work, <strong data-start="374" data-end="395">Claude Sonnet 4.5</strong> and <strong data-start="400" data-end="409">GPT-5</strong> are both safe, well-rounded options that handle the vast majority of typical tasks reliably. When the work shifts toward long, complex debugging sessions &#8211; where reasoning across many files and iterations really matters &#8211; <strong data-start="628" data-end="647">Claude Opus 4.5</strong> clearly pulls ahead. On the other end of the spectrum, if speed and cost efficiency are the primary concerns, lighter models such as <strong data-start="781" data-end="801">Gemini 2.5 Flash</strong> or <strong data-start="805" data-end="819">GPT-5 Mini</strong> offer a reasonable trade-off between performance and latency.</p>
<p>Note for the end: The difficulty of designing good benchmarks&#8230; actually makes me optimistic about coding agents. Current state-of-the-art benchmarks fall woefully short of capturing the nuance and messiness of real engineering work &#8211; yet the agents we have are already remarkably capable. There is substantial low-hanging fruit in benchmark design: validating with property-based testing instead of unit tests, using formal methods where possible, starting from product-level documents like PRDs and technical specifications, and creating benchmarks that test information acquisition and clarification skills rather than assuming perfect problem statements.</p>
<p>As these improvements arrive, we should expect corresponding improvements in agent capabilities through better training signals.</p>
<p>Please remember, the current solutions are the worst we will ever get <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</article>
</div>
<p><img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fhow-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html&amp;action_name=How%20to%20really%20measure%20LLMs%20for%20JVM%20Code%3F%20A%20Benchmarking%20guide%20for%20late%202025&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html">How to really measure LLMs for JVM Code? A Benchmarking guide for late 2025</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/how-to-really-measure-llms-for-jvm-code-a-benchmarking-guide-for-late-2025.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6343</post-id>	</item>
		<item>
		<title>Out with the Old, In with the New: A Guide to Application Upkeep</title>
		<link>https://www.javaadvent.com/2025/12/out-with-the-old-in-with-the-new.html</link>
					<comments>https://www.javaadvent.com/2025/12/out-with-the-old-in-with-the-new.html#respond</comments>
		
		<dc:creator><![CDATA[Andres Sacco]]></dc:creator>
		<pubDate>Sat, 20 Dec 2025 03:08:59 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[automatic]]></category>
		<category><![CDATA[legacy application]]></category>
		<category><![CDATA[rewrite]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5926</guid>

					<description><![CDATA[<p>Migrating an existing application to a new version of Java or a framework such as Spring Boot involves much more than simply updating a version number in a file. Each new release of a library or language brings new features, deprecations, behavioral changes, and sometimes complete API redesigns. When legacy code is involved, these upgrades [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fout-with-the-old-in-with-the-new.html&amp;action_name=Out%20with%20the%20Old%2C%20In%20with%20the%20New%3A%20A%20Guide%20to%20Application%20Upkeep&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/out-with-the-old-in-with-the-new.html">Out with the Old, In with the New: A Guide to Application Upkeep</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Migrating an existing application to a new version of Java or a framework such as Spring Boot involves much more than simply updating a version number in a file. Each new release of a library or language brings new features, deprecations, behavioral changes, and sometimes complete API redesigns. When legacy code is involved, these upgrades may require revisiting old implementations and correcting patterns that no longer work. Even the slightest change can trigger a cascading effect, modifying other parts of the application in unexpected ways.</p>
<p>These migration challenges require significant effort even for a single application. The difficulty grows exponentially when a company needs to upgrade dozens of systems at once. And once the migration is complete, a new question arises: how can we keep applications consistently up to date without repeating the same painful process?</p>

<h2 class="wp-block-heading">CONTEXT OF THE SITUATION</h2>
<p>Imagine a team responsible for several microservices that use different versions of Java or Spring Boot. This happens because not all microservices change frequently; at some point, a problem occurs with a library they all use, and when someone tries to update the version, another problem arises, such as the new version not supporting all versions of Java.</p>
<p>To see this situation graphically, check the following table, which represents a possible scenario that could occur in any company:</p>
<table style="height: 300px" width="600">
<tbody>
<tr>
<td><b>API</b></td>
<td><b>Compilation</b></td>
<td><b>Code Style</b></td>
<td><b>Framework</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">api-a</span></td>
<td><span style="font-weight: 400">8</span></td>
<td><span style="font-weight: 400">8</span></td>
<td><span style="font-weight: 400">Spring Boot 1.5.7</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">api-b</span></td>
<td><span style="font-weight: 400">11</span></td>
<td><span style="font-weight: 400">8</span></td>
<td><span style="font-weight: 400">Spring Boot 2.1.4</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">api-c</span></td>
<td><span style="font-weight: 400">17</span></td>
<td><span style="font-weight: 400">17</span></td>
<td><span style="font-weight: 400">Spring Boot 3.0.0</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">api-d</span></td>
<td><span style="font-weight: 400">14</span></td>
<td><span style="font-weight: 400">11</span></td>
<td><span style="font-weight: 400">Spring Boot 2.3.3</span></td>
</tr>
</tbody>
</table>
<p>With this problem in mind, the only alternative is to create a custom solution for each application that does not use the minimum version of Java that supports this library, but this could have many implications, such as performance issues and more time to fix the problem across all the microservices.</p>



<h3><strong>The Problems with Legacy Code</strong></h3>
<p data-start="475" data-end="726">Legacy code does not always need to be updated. For example, a desktop application that operates in isolation and does not interact with external services may continue to function without requiring significant changes. In these scenarios, updates are optional.</p>
<p data-start="728" data-end="1022">However, in many other cases, applications <strong data-start="771" data-end="779">must</strong> be updated for several reasons, mainly when they rely on outdated Java versions or obsolete dependencies. Running outdated software introduces risks and limitations that can directly affect stability, security, and maintainability.</p>
<p data-start="728" data-end="1022">Some of the risks associated with outdated code are:</p>
<ul>
<li data-start="1055" data-end="1135">
<p data-start="1057" data-end="1135"><strong data-start="1057" data-end="1085">Security vulnerabilities</strong>: Older versions often contain unpatched exploits.</p>
</li>
<li data-start="1136" data-end="1251">
<p data-start="1138" data-end="1251"><strong data-start="1138" data-end="1162">Compatibility issues</strong>: Modern libraries, tools, and operating systems may no longer support obsolete versions.</p>
</li>
<li data-start="1252" data-end="1357">
<p data-start="1254" data-end="1357"><strong data-start="1254" data-end="1284">Performance inefficiencies</strong>: Newer versions frequently include optimizations that older releases lack.</p>
</li>
</ul>
<h3>HOW TO SOLVE THESE PROBLEMS?</h3>
<p>The problem could be split into two situations: one is an application that contains legacy code and needs to be updated, and the other is an application that uses a relatively recent version of a language or framework but has some dependencies on older versions.</p>
<p>There is a set of tools or libraries to solve these problems, the most popular are:</p>
<ul>
<li data-start="57" data-end="152">
<p data-start="59" data-end="152"><strong data-start="59" data-end="72">Renovate:</strong> Automates dependency updates and creates pull requests across many languages.</p>
</li>
<li data-start="153" data-end="256">
<p data-start="155" data-end="256"><strong data-start="155" data-end="171">OpenRewrite:</strong> Automatically refactors source code to migrate between Java versions or frameworks.</p>
</li>
<li data-start="257" data-end="367">
<p data-start="259" data-end="367"><strong data-start="259" data-end="285">Maven Versions Plugin:</strong> Detects and updates dependency versions in Maven projects without code changes.</p>
</li>
<li data-start="368" data-end="462" data-is-last-node="">
<p data-start="370" data-end="462" data-is-last-node=""><strong data-start="370" data-end="385">Dependabot:</strong> GitHub’s built-in tool that creates pull requests for outdated dependencies.</p>
</li>
</ul>
<p>Let&#8217;s see in the following table a brief comparison of all of them:</p>
<table style="height: 300px" width="650">
<tbody>
<tr>
<td><b>Main differences</b></td>
<td><b>Reno vate</b></td>
<td>
<p><b>Open Rewrite</b></p>
</td>
<td><b>Maven plugin</b></td>
<td>
<p><b>Dependa Bot</b></p>
</td>
</tr>
<tr>
<td> It has good documentation.</td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
<tr>
<td>Support multiple languages.</td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
<tr>
<td>
<p>Refactoring the source code to a specific version.</p>
</td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
<tr>
<td>Automates dependency updates.</td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
<tr>
<td>
<p>It&#8217;s possible to generate pull/merge requests.</p>
</td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
<tr>
<td>It has a large user community.</td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
</tbody>
</table>
<p>In this comparison, the best option for addressing migration problems is OpenRewrite. If you need to keep dependencies up to date, the winners could be Dependabot or Renovate. However, given the cost of these tools, the winner is the Maven Plugin.</p>
<p>This article uses a source from a <a href="https://github.com/andres-sacco/out-with-the-old">GitHub repository</a>; feel free to clone it and use it to learn about how to keep the application updated.</p>
<h2><strong>Solution #1: Leveraging OpenRewrite</strong></h2>
<p>OpenRewrite provides a comprehensive solution to code migration challenges with automated, reliable refactoring tools that streamline the process. The platform effectively highlights necessary code changes, implements consistent transformations, and significantly minimizes the manual effort required for application updates. By automating repetitive tasks and guiding developers through essential modifications, OpenRewrite improves the manageability of large-scale upgrades while reducing the risk of errors.</p>
<p>The migration process with OpenRewrite follows a precise, structured flow, offering a series of recipes that include the steps and resources needed at each stage. This tool is not limited to migrating Spring Boot or Java; it offers a comprehensive catalog of recipes for different languages and frameworks.</p>
<p>Let&#8217;s see how the process of migrating an application to Java 21 and Spring Boot 3.4 is. To do that, add the OpenRewrite plugin or dependency to your build system, along with the recipes to be used, as shown in the following code block.</p>
<pre><span style="font-weight: 400">&lt;plugin&gt;</span><br /><br /><span style="font-weight: 400">     &lt;groupId&gt;org.openrewrite.maven&lt;/groupId&gt;</span><br /><br /><span style="font-weight: 400">     &lt;artifactId&gt;rewrite-maven-plugin&lt;/artifactId&gt;</span><br /><br /><span style="font-weight: 400">     &lt;version&gt;6.24.0&lt;/version&gt;</span><br /><br /><span style="font-weight: 400">     &lt;configuration&gt;</span><br /><br /><span style="font-weight: 400">         &lt;activeRecipes&gt;</span><br /><br /><span style="font-weight: 400">             &lt;recipe&gt;org.openrewrite.java.OrderImports&lt;/recipe&gt;</span><br /><br /><span style="font-weight: 400">       &lt;recipe&gt;org.openrewrite.java.migrate.UpgradeToJava21&lt;/recipe&gt;</span><br /><br /><span style="font-weight: 400">   &lt;recipe&gt;org.openrewrite.java.spring.boot3.SpringBootProperties_3_4&lt;/recipe&gt;</span><br /><br /><span style="font-weight: 400">         &lt;/activeRecipes&gt;</span><br /><br /><span style="font-weight: 400">     &lt;/configuration&gt;</span><br /><br /><span style="font-weight: 400">     &lt;dependencies&gt;</span><br /><br /><span style="font-weight: 400">         &lt;dependency&gt;</span><br /><br /><span style="font-weight: 400">             &lt;groupId&gt;org.openrewrite.recipe&lt;/groupId&gt;</span><br /><br /><span style="font-weight: 400">             &lt;artifactId&gt;rewrite-spring&lt;/artifactId&gt;</span><br /><br /><span style="font-weight: 400">             &lt;version&gt;6.19.0&lt;/version&gt;</span><br /><br /><span style="font-weight: 400">         &lt;/dependency&gt;</span><br /><br /><span style="font-weight: 400">         &lt;dependency&gt;</span><br /><br /><span style="font-weight: 400">             &lt;groupId&gt;org.openrewrite.recipe&lt;/groupId&gt;</span><br /><br /><span style="font-weight: 400">             &lt;artifactId&gt;rewrite-migrate-java&lt;/artifactId&gt;</span><br /><br /><span style="font-weight: 400">             &lt;version&gt;3.9.0&lt;/version&gt;</span><br /><br /><span style="font-weight: 400">         &lt;/dependency&gt;</span><br /><br /><span style="font-weight: 400">    &lt;/dependencies&gt;</span><br /><br /><span style="font-weight: 400">&lt;/plugin&gt;</span></pre>
<p>As a recommendation, check the latest version of this plugin on the official webpage or a repository like <a href="https://mvnrepository.com/artifact/org.openrewrite.maven/rewrite-maven-plugin/6.24.0" target="_blank" rel="noopener">this regularly</a>.</p>
<p>The plugin only contains the core logic to execute the receipts that are necessary to include as external dependencies, as shown in the previous code block. Also, it&#8217;s possible to create custom receipts that are not part of the official library.</p>
<p>The next step after the modifications on the project is to execute the changes using the following command:</p>
<pre>$ mvn rewrite:run<br /><br />[INFO] Using active recipe(s) [org.openrewrite.java.OrderImports, org.openrewrite.java.migrate.UpgradeToJava21, org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_4]<br />[INFO] Using active styles(s) []<br />[INFO] Validating active recipes...<br />[INFO] Project [api-reservations] Resolving Poms...<br />[INFO] Project [api-reservations] Parsing source files<br />[WARNING] locking FileBasedConfig[/home/asacco/.config/jgit/config] failed after 5 retries<br />[INFO] Running recipe(s)...<br />[WARNING] Changes have been made to api-reservations/pom.xml by:<br />[WARNING] org.openrewrite.java.migrate.UpgradeToJava21<br />[WARNING] org.openrewrite.java.migrate.UpgradeBuildToJava21<br />[WARNING] org.openrewrite.java.migrate.UpgradeJavaVersion: {version=21}<br />[WARNING] org.openrewrite.maven.UpdateMavenProjectPropertyJavaVersion: {version=21}<br />[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_4<br />[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3<br />[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2<br />[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1<br />[WARNING] org.openrewrite.java.dependencies.UpgradeDependencyVersion: {groupId=org.springframework.boot, artifactId=*, newVersion=3.1.x, overrideManagedVersion=false}<br />[WARNING] org.openrewrite.java.dependencies.UpgradeDependencyVersion: {groupId=org.springdoc, artifactId=*, newVersion=2.2.x}<br />[WARNING] org.openrewrite.java.testing.mockito.Mockito4to5Only<br />[WARNING] org.openrewrite.java.dependencies.UpgradeDependencyVersion: {groupId=org.mockito, artifactId=*, newVersion=5.x}<br />[WARNING] org.openrewrite.java.dependencies.UpgradeDependencyVersion: {groupId=org.springframework.boot, artifactId=*, newVersion=3.2.x, overrideManagedVersion=false}<br />[WARNING] org.openrewrite.java.dependencies.UpgradeDependencyVersion: {groupId=org.springdoc, artifactId=*, newVersion=2.5.x}<br />[WARNING] org.openrewrite.java.dependencies.UpgradeDependencyVersion: {groupId=org.springframework.boot, artifactId=*, newVersion=3.3.x, overrideManagedVersion=false}<br />[WARNING] org.openrewrite.java.dependencies.UpgradeDependencyVersion: {groupId=org.springdoc, artifactId=*, newVersion=2.6.x}<br />[WARNING] org.openrewrite.java.dependencies.UpgradeDependencyVersion: {groupId=org.springframework.boot, artifactId=*, newVersion=3.4.x, overrideManagedVersion=false}<br />[WARNING] org.openrewrite.java.dependencies.UpgradeDependencyVersion: {groupId=org.springdoc, artifactId=*, newVersion=2.8.x}<br />[WARNING] Changes have been made to api-reservations/src/main/java/com/twa/reservations/connector/CatalogConnector.java by:<br />.....</pre>
<p>If everything works as expected and the migration was successful, the changes to the POM file that add the openRewrite plugin will be removed.<br /><br />Suppose it&#8217;s necessary to understand and see all the changes that could affect the application before doing so. In that case, another command shows that information by simulating execution and displaying the results.</p>
<pre>$ mvn rewrite:dryRun<br />....<br />[INFO] Using active recipe(s) [org.openrewrite.java.OrderImports, org.openrewrite.java.migrate.UpgradeToJava21, org.openrewrite.java.spring.boot3.SpringBootProperties_3_4]<br />[INFO] Using active styles(s) []<br />[INFO] Validating active recipes...<br />[INFO] Project [api-reservations] Resolving Poms...<br />[INFO] Project [api-reservations] Parsing source files<br />....<br />[WARNING] These recipes would make changes to api-reservations/src/main/java/com/twa/reservations/controller/ReservationController.java:<br />[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_4<br />[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3<br />[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2<br />[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1<br />[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0<br />[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7<br />[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6<br />[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5<br />[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4<br />[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_3<br />[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_2<br />[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_1<br />[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_0<br />[WARNING] org.openrewrite.java.spring.boot2.SpringBoot2BestPractices<br />[WARNING] org.openrewrite.java.spring.NoAutowiredOnConstructor<br />[WARNING] Patch file available:<br />[WARNING] /home/asacco/Code/Talks/out-with-the-old/api-reservations/target/rewrite/rewrite.patch<br />[WARNING] Estimate time saved: 20m<br />[WARNING] Run 'mvn rewrite:run' to apply the recipes.<br />[INFO] ------------------------------------------------------------------------<br />[INFO] BUILD SUCCESS<br />[INFO] ------------------------------------------------------------------------<br />[INFO] Total time: 20.671 s<br />[INFO] Finished at: 2025-12-01T15:22:34-03:00<br />[INFO] ------------------------------------------------------------------------</pre>
<p>On the file <strong>rewrite.patch</strong> that was created in the target folder, all the changes that will be executed in the application will appear.</p>
<h2>Solution #2: Dependency &#8211; Updates Versions</h2>
<p>The <strong data-start="84" data-end="109">versions-maven-plugin</strong> is a powerful Maven tool designed to help developers keep project dependencies, plugins, and parent versions up to date. With simple commands, it can identify outdated components, suggest the latest compatible versions, and even update itself <code data-start="350" data-end="359">pom.xml</code> automatically. This reduces the manual effort required to track version changes and helps ensure applications remain secure, stable, and aligned with the latest improvements in their ecosystems.</p>
<p>The first step to use this plugin is to include it on the pom file like appears on the following block:</p>
<div>
<pre>&lt;plugin&gt;<br />    &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;<br />    &lt;artifactId&gt;versions-maven-plugin&lt;/artifactId&gt;<br />    &lt;version&gt;2.18.0&lt;/version&gt;<br />&lt;/plugin&gt;</pre>
</div>
<p>As a recommendation, check the latest version of this plugin on the official webpage or a repository like <a href="https://mvnrepository.com/artifact/org.openrewrite.maven/rewrite-maven-plugin/6.24.0" target="_blank" rel="noopener">this regularly</a>.</p>
<p>The next and last step is to run a command that checks all dependencies and the project and suggests which are outdated. The command and the output looks like the following:</p>
<pre>$ mvn versions:display-dependency-updates<br />...<br />[INFO] --- versions:2.18.0:display-dependency-updates (default-cli) @ api-reservations ---<br />[INFO] The following dependencies in Dependency Management have newer versions:<br />[INFO] biz.aQute.bnd:biz.aQute.bnd.annotation ................ 7.0.0 -&gt; 7.1.0<br />[INFO] co.elastic.clients:elasticsearch-java ................ 8.15.5 -&gt; 9.2.1<br />[INFO] com.couchbase.client:java-client ..................... 3.7.9 -&gt; 3.10.0<br />[INFO] com.datastax.oss:native-protocol ...................... 1.5.1 -&gt; 1.5.2<br />[INFO] com.fasterxml.jackson.core:jackson-annotations ..... 2.18.5 -&gt; 3.0-rc5<br />[INFO] com.fasterxml.jackson.core:jackson-core ............. 2.18.5 -&gt; 2.20.1<br />[INFO] com.fasterxml.jackson.core:jackson-databind ......... 2.18.5 -&gt; 2.20.1<br />[INFO] com.fasterxml.jackson.dataformat:jackson-dataformat-avro ...<br />[INFO] <br />[INFO] ------------------------------------------------------------------------<br />[INFO] BUILD SUCCESS<br />[INFO] ------------------------------------------------------------------------<br />[INFO] Total time: 0.970 s<br />[INFO] Finished at: 2025-12-01T16:43:32-03:00<br />[INFO] ------------------------------------------------------------------------</pre>
<p>Consider that with this command, all the dependencies related to Spring Boot will appear; for example, they could be updated by simply incrementing the framework&#8217;s version. This scenario is a good candidate to use another approach which only show the version of the dependencies that are declared on the pom file. The command is quite similar to the previous one, but the output is entirely different, as shown in the following block.</p>
<pre>$ mvn versions:display-property-updates<br />...<br />[INFO] The following version properties are referencing the newest available version:<br />[INFO] ${maven-failsafe-plugin.version} .............................. 3.5.4<br />[INFO] ${mockito.version} ........................................... 5.20.0<br />[INFO] The following version property updates are available:<br />[INFO] ${datafaker.version} ................................. 2.3.0 -&gt; 2.5.3<br />[INFO] ${formatter-maven-plugin.version} .................. 2.23.0 -&gt; 2.29.0<br />[INFO] ${instancio-junit.version} ........................... 5.2.1 -&gt; 5.5.1<br />[INFO] ${junit-platform-launcher.version} ................ 1.8.2 -&gt; 6.1.0-M1<br />[INFO] ${junit.version} ................................. 5.10.1 -&gt; 6.1.0-M1<br />[INFO] ${mapstruct.version} ........................... 1.5.5.Final -&gt; 1.6.3<br />[INFO] ${maven-compiler-plugin.version} ............. 3.14.1 -&gt; 4.0.0-beta-3<br />[INFO] ${maven-enforcer-plugin.version} ..................... 3.4.1 -&gt; 3.6.2<br />[INFO] ${maven-surefire-plugin.version} ..................... 3.1.2 -&gt; 3.5.4<br />[INFO] ${spring-boot-starter.version} ...................... 3.4.12 -&gt; 4.0.0<br />[INFO] ${springdoc-openapi-starter-webmvc-ui.version} ...... 2.8.14 -&gt; 3.0.0<br />[INFO] <br />[INFO] ------------------------------------------------------------------------<br />[INFO] BUILD SUCCESS<br />[INFO] ------------------------------------------------------------------------<br />[INFO] Total time: 0.970 s<br />[INFO] Finished at: 2025-12-01T16:43:32-03:00<br />[INFO] ------------------------------------------------------------------------</pre>
<p>This plugin offers a series of other commands to check specific parts of the POM file, such as plugins, and many others, so it&#8217;s recommended to use the appropriate one depending on the context.</p>



<h2 class="wp-block-heading">WHAT’S NEXT?</h2>



<p>There are many resources for the evolution of a platform or application. The following is just a short list of resources:</p>
<ul>
<li><a href="https://www.amazon.com/Modernizing-Enterprise-Java-Concise-Developers/dp/1098102142">Modernizing Enterprise Java: A Concise Cloud Native Guide for Developers</a> by Markus Eisele</li>
<li><a href="https://www.amazon.com/-/es/Building-Evolutionary-Architectures-Automated-Governance/dp/1492097543/ref=sr_1_1?crid=3KOISMK7J4LNA&amp;dib=eyJ2IjoiMSJ9.IP8w7960vhkT6IFoEuyn1FfuEribnqJr8KtEsO2aLzcD5GfT61m1GzkE0-YXane6rV9yfsdsCazIUQ56Xijouk1r20AdbWq7j5t0w23Z6R_6TY-92aS5zapNW5HdOxmm3gWwv1qMGf8NZr1XAfKOATh9md-5UdPIa6b65Lefbx-53mYnHjI9UtIiAjLkh4u7gcppYY7XGoryCGnIS_WuIF_YDGR4n0d0OWnaRCGsWEc.moJcrwMO8W8zUxzB61OByfGf7_uZBkPeNZIdrGRlAGY&amp;dib_tag=se&amp;keywords=building+evolutionary+architectures&amp;qid=1764593484&amp;s=books&amp;sprefix=building+e%2Cstripbooks-intl-ship%2C270&amp;sr=1-1">Building Evolutionary Architectures: Automated Software Governance</a> by Neal Ford</li>
</ul>
<p>Other resources could be great for understanding some concepts related to the evolution of a platform in depth:</p>
<ul>
<li><a href="https://www.amazon.com/-/es/Software-Architecture-Trade-Off-Distributed-Architectures/dp/1492086894/ref=sr_1_1?crid=3RYLIS9PB96CH&amp;dib=eyJ2IjoiMSJ9.nqwcn_XNPa_qE3lv1ItCHibAVFRNIWPtpnuz2WDrYHYDLu-EWCFxY0tqVNC3OFM0mDM6TY-BuHX67X47n-EFKpOj43OpqKZZk5CC2YyHSCcBxqmP1UjPoPnlep1Rwd48dSfHS42zPDYa5iLgyAFHXccSaDOz6JtUvSE03_I75H6tvGTrEXrKIvdFDBPh1XOFftlYNdSMAUAGRPj5tYn-2a9vF-Rxh6Lswtvie3qB2qM.V7LiiVGxtEr-z1CIe3lBVR4ntiM0yfz7dfbGhTaIZVw&amp;dib_tag=se&amp;keywords=software+architecture+the+hard+parts&amp;qid=1764593665&amp;s=books&amp;sprefix=software+architecture+hard%2Cstripbooks-intl-ship%2C247&amp;sr=1-1">Software Architecture: The Hard Parts: Modern Trade-Off Analyses for Distributed Architectures</a> by Neal Ford</li>
<li><a href="https://www.amazon.com/-/es/Refactoring-Improving-Existing-Addison-Wesley-Signature/dp/0134757599/ref=sr_1_1?crid=3D5L14LWKC4B7&amp;dib=eyJ2IjoiMSJ9.d5ydIv1vhzDGghLAj8wLktME7tL2Xr-w9gn938RnbUbwDDkLGozo4P8dqQpvx-HmceR6KIQx4ixDe--6Eu-6TNkDAzaOVbVKbjIrilBt9cJZ5WlJMK140J99KX2DGfG9TEq64oUYgL60I6ByXMtcLSAb3z9p8phuog8LXQqrjFmStfvnKwtmeAN2I2EPm-qhsfTOEn00PqYxytqvLF4afm4Jw-GBTJa1_YLYqWW7vzA.ZMht9uifJLkEMwBE7WeQ6UZGXfBvzxqPSvDyaC66Xxk&amp;dib_tag=se&amp;keywords=refactoring+martin+fowler&amp;qid=1764593747&amp;s=books&amp;sprefix=refactoring+fo%2Cstripbooks-intl-ship%2C286&amp;sr=1-1">Refactoring: Improving the Design of Existing Code (2nd Edition)</a> by Martin Fowler</li>
<li><a href="https://developertoarchitect.com/">Software Developer To Software Architect</a> by Mark Richards</li>
</ul>
<p>Consider this just a small list of available resources. If something is unclear, find another video or resource.</p>



<h2 class="wp-block-heading">CONCLUSION</h2>
<p>There is no silver bullet for keeping an application permanently up to date, but combining the proper practices with the right tools can dramatically simplify the process. Tools such as OpenRewrite or the Maven Versions Plugin automate much of the Java and Spring Boot migration process. Still, they cannot fix everything, especially when a library does not support newer Java versions. For example, Orika, a widely used mapping library, does not support Java 17 or later, so developers must manually migrate to an alternative before they can benefit from automation.</p>
<p>Because of these limitations, maintaining a clean codebase, adopting a strong testing culture, and updating dependencies regularly are essential. By combining these practices with the tools discussed in this article, development teams can reduce migration risks and ensure that future upgrades become far less painful.</p>
<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fout-with-the-old-in-with-the-new.html&amp;action_name=Out%20with%20the%20Old%2C%20In%20with%20the%20New%3A%20A%20Guide%20to%20Application%20Upkeep&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/out-with-the-old-in-with-the-new.html">Out with the Old, In with the New: A Guide to Application Upkeep</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/out-with-the-old-in-with-the-new.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5926</post-id>	</item>
		<item>
		<title>Getting Started with SpringAI</title>
		<link>https://www.javaadvent.com/2025/12/getting-started-with-springai.html</link>
					<comments>https://www.javaadvent.com/2025/12/getting-started-with-springai.html#respond</comments>
		
		<dc:creator><![CDATA[Samuel Lissner]]></dc:creator>
		<pubDate>Fri, 19 Dec 2025 03:03:39 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6211</guid>

					<description><![CDATA[<p>A Hands-On Guide to Text Summarization Over the past year, the Java ecosystem has made significant strides in making Generative AI development enterprise-ready. For Spring developers, SpringAI has emerged as the go-to toolkit for seamlessly integrating enterprise data and APIs with AI models. Are you curious in developing enterprise grade AI applications with Spring AI? [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fgetting-started-with-springai.html&amp;action_name=Getting%20Started%20with%20SpringAI&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/getting-started-with-springai.html">Getting Started with SpringAI</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2 id="a-hands-on-guide-to-text-summarization">A Hands-On Guide to Text Summarization</h2>
<p>Over the past year, the Java ecosystem has made significant strides in making Generative AI development enterprise-ready.</p>
<p>For Spring developers, SpringAI has emerged as the go-to toolkit for seamlessly integrating enterprise data and APIs with AI models.</p>
<p>Are you curious in developing enterprise grade AI applications with Spring AI? Then read on.</p>
<h2 id="setting-up-springai">Setting up SpringAI</h2>
<p>Loading documents and evaluating them with Generative AI is a fundamental use case that you will encounter when working with Large Language Models (LLMs) in the industry.</p>
<p>Therefore, to get started with SpringAI, we are using the practical example of summarizing Wikipedia articles with LLMs. We are going to create a <a title="https://github.com/slissner/springai-wikipedia-demo" href="https://github.com/slissner/springai-wikipedia-demo" data-from-md="">springai-wikipedia-demo</a> project that you can find on GitHub.</p>
<p>The project has been build with Spring Boot v3.5.8 running on Java 21. As key ingredients we are using the <a href="https://www.anthropic.com/">Anthropic</a> API integration that uses the Claude models. To extract text from PDF documents we are using the <a href="https://tika.apache.org/">Apache Tika</a> document reader.</p>
<p>The key dependencies are:</p>
<div>
<pre>implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.ai:spring-ai-starter-model-anthropic")
implementation("org.springframework.ai:spring-ai-tika-document-reader")
</pre>
</div>
<p>Note that we have added a dependency on <code>spring-webflux</code>. For SpringAI to work, we need the <a title="https://netty.io/" href="https://netty.io/" data-from-md="">Netty Client</a> on the classpath to carry out synchronous and reactive HTTP requests (using its <code>RestClient</code> and <code>WebClient</code>). So even if you only run SpringAI from the CLI, you also need to include a dependency to <code>spring-webflux</code>.</p>
<p>As said, we use <a title="https://claude.ai/" href="https://claude.ai/" data-from-md="">Claude</a> from Anthropic as LLM for demo purpose, but you can of course use any of your choice.</p>
<p>Note that you need to provide an Anthropic API key for the <a title="https://console.anthropic.com/" href="https://console.anthropic.com/" data-from-md="">Claude Console</a> as an environment variable to authenticate your requests to the model. See the <code>application.properties</code> file:</p>
<div>
<pre>spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY:missing}</pre>
</div>
<h2 id="the-document-reader-infrastructure">The Document reader infrastructure</h2>
<p>One of the key abstractions when processing media content in SpringAI is the <code>org.springframework.ai.document.Document</code> interface. A <code>Document</code> contains the plain content and metadata about the document. The content can be textual, or optionally audio or video. The most important interface methods are:</p>
<div>
<pre>String getText();
Media getMedia();
Map&lt;String,Object&gt; getMetadata();</pre>
</div>
<p>The <code>Document</code> abstraction is closely tied to the context of <a title="https://docs.spring.io/spring-ai/reference/api/etl-pipeline.html" href="https://docs.spring.io/spring-ai/reference/api/etl-pipeline.html" data-from-md="">Extract, Transform, Load (ETL)</a> processes for <a title="https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html" href="https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html" data-from-md="">Retrieval Augmented Generation (RAG)</a>. ETL is a three-step data integration process to collect data from various sources, clean and reshape it into a usable format, here a <code>Document</code>.</p>
<p>In simple terms, RAG is needed to feed your own private data to the LLM in order to take it into account when answering prompts. In the enterprise, this is of very high value. For privacy purposes, proprietary and open source LLMs won&#8217;t be trained on your specific company data.</p>
<p>In our example case, we are going to load <a title="https://en.wikipedia.org/wiki/Wikipedia:Random" href="https://en.wikipedia.org/wiki/Wikipedia:Random" data-from-md="">five random articles of Wikipedia</a> that have been saved as PDF. The articles have been placed in the <code>resources</code> folder:</p>
<div>
<pre>├── application.properties
└── articles
    ├── 2023_Asia_Contents_Awards_&amp;_Global_OTT_Awards.pdf
    ├── Anthony_Wonke.pdf
    ├── Chang_Tzi-chin.pdf
    ├── Indera_SC.pdf
    └── Neant-sur-Yvel.pdf</pre>
</div>
<p>In the real world, you can imagine this content being Confluence docs, JIRA tickets, internal reports, literature and other kinds of publications that you want to provide to your LLM. You might want to let coworkers ask questions about your internal documentation. Or you want provide customers with answers about your products taking into account your own knowledge base.</p>
<p>To use the Tika document reader, we introduce a simple <code>DocumentReader</code> component; I am going to skip the import declarations in my examples:</p>
<div>
<pre>package com.slissner.springai.infrastructure.document;

@Component
public class DocumentReader {
  public List&lt;Document&gt; loadText(final Resource resource) {
    final TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(resource);
    return tikaDocumentReader.read();
  }
}</pre>
</div>
<p>As you can see, SpringAI has nice abstractions. You just throw in some <code>Resource</code> reference, be it TXT, HTML, PDF, XLSX and so on, and the Tika Reader will answer with the appropriate <code>Document</code>.</p>
<p>Next, we are defining a repository reading the articles:</p>
<div>
<pre>@Repository
public class ArticleRepository {

  private final DocumentReader documentReader;

  public ArticleRepository(final DocumentReader documentReader) {
    this.documentReader = documentReader;
  }

  private static final List&lt;String&gt; DOCUMENT_PATHS =
      Stream.of(
              "2023_Asia_Contents_Awards_&amp;_Global_OTT_Awards.pdf",
              "Anthony_Wonke.pdf",
              "Chang_Tzi-chin.pdf",
              "Indera_SC.pdf",
              "Neant-sur-Yvel.pdf")
          .map(path -&gt; "/articles/" + path)
          .toList();

  public List&lt;Document&gt; getAll() {
    return DOCUMENT_PATHS.stream()
        .map(ClassPathResource::new)
        .map(documentReader::loadText)
        .flatMap(Collection::stream)
        .toList();
  }
}</pre>
</div>
<p>We simply load all articles with the <code>List&lt;Document&gt; getAll()</code> method, by first loading the classpath <code>Resource</code> and then sending it to the Tika reader.</p>
<h2 id="processing-documents-with-the-chatclient">Processing Documents with the ChatClient</h2>
<p>As we can now load <code>List&lt;Document&gt;</code> from the PDFs, we can carry out a first prompt to let the LLM summarize the content of the first article in the list <a title="https://github.com/slissner/springai-wikipedia-demo/blob/main/src/main/resources/articles/2023_Asia_Contents_Awards_%26_Global_OTT_Awards.pdf" href="https://github.com/slissner/springai-wikipedia-demo/blob/main/src/main/resources/articles/2023_Asia_Contents_Awards_%26_Global_OTT_Awards.pdf" data-from-md="">2023_Asia_Contents_Awards_&amp;_Global_OTT_Awards.pdf</a>.</p>
<p>For this purpose, we have introduced the <code>ArticleService</code> application service. Note that we have not introduced yet a separate <code>infrastructure</code> class for the <code>ChatClient</code>. Our use case is so simple that we did not want to introduce a separate class for it.</p>
<div>
<pre>package com.slissner.springai.application;

@Service
public class ArticleService {

  private final ChatClient chatClient;
  private final ArticleRepository articleRepository;

  public ArticleService(
      final ArticleRepository articleRepository, final ChatClient.Builder chatClientBuilder) {
    this.articleRepository = articleRepository;
    this.chatClient = chatClientBuilder.build();
  }

  public String summarizeArticles() {
    final List&lt;Document&gt; articles = articleRepository.getAll();

    // Use the first article as an example
    final Document articleContent = articles.getFirst();

    return chatClient
        .prompt()
        .user("Provide a summary of the following article:\n\n" + articleContent)
        .call()
        .content();
  }
}</pre>
</div>
<p>We can now run the <code>String summarizeArticles()</code> application service method on the command line, with the following <code>CommandLineRunner</code>:</p>
<div>
<pre>  @Bean
  public CommandLineRunner commandLineRunner(final ArticleService articleService) {
    return args -&gt; {
      log.info("Okay, I am going to summarize articles...");

      final String summary = articleService.summarizeArticles();

      log.info("The summary is:");
      log.info(summary);
      log.info("Done!");
    };
  }</pre>
</div>
<p>Great, that worked! Here is the answer from the LLM:</p>
<div>
<pre>2025-11-28T16:26:02.457+01:00  INFO 4936 --- [springAI] [           main] c.slissner.springai.SpringAiApplication  : The summary is:
2025-11-28T16:26:02.457+01:00  INFO 4936 --- [springAI] [           main] c.slissner.springai.SpringAiApplication  : # 2023 Asia Contents Awards &amp; Global OTT Awards Summary

The 2023 Asia Contents Awards &amp; Global OTT Awards was held on October 8, 2023, at the BIFF Theater in Busan Cinema Center, South Korea. This event represents a rebranding and expansion of the previous Asia Contents Awards, now including global OTT (Over-The-Top) content and services.

[...]</pre>
</div>
<p>The <code>ChatClient</code> offers a fluent API when communicating with the AI models.</p>
<div>
<pre>chatClient
        .prompt()
        .user("Provide a summary of the following article:\n\n" + articleContent)
        .call()
        .content();</pre>
</div>
<p>You declare to send a <code>.prompt()</code> to the model and its <code>.user()</code> input.</p>
<p>Note that we are passing the prompt here as a <code>String text</code>. You can also pass a <code>Resource text</code> handle. However, if you need full control over the user prompt, you can pass a well-defined <code>Prompt</code> to the <code>ChatClientRequestSpec prompt(Prompt prompt)</code> method. A <code>Prompt</code> gives you full control over the <code>ChatOption</code> such as the concrete model, max tokens or the temperature of the model.</p>
<p>Ultimately, the chat model can be synchronously called with the <code>.call()</code> method. There exists also a <code>.stream()</code> method that offers a reactive <code>Flux&lt;String&gt;</code> via calling the <code>.content()</code> method.</p>
<h2 id="memorizing-chat-history-with-advisors">Memorizing Chat History with Advisors</h2>
<p>So far, we have only sent a single prompt. What if we want to send a sequence of prompts, memorize the model answers and then run a final prompt on the memory?</p>
<p>For this purpose, SpringAI introduced the <code>Advisors</code> API. Think of the <a title="https://docs.spring.io/spring-ai/reference/api/chatclient.html#_advisors" href="https://docs.spring.io/spring-ai/reference/api/chatclient.html#_advisors" data-from-md="">Advisors API</a> as a plugin system for your AI calls.</p>
<p>Each advisor can intercept a request, add context, or modify the prompt before it reaches the model. Advisors are small middleware that automatically add missing context, such as previous messages or app-specific data, so the AI model always has what it needs.</p>
<p>The most common use cases are to add your own data to the conversation (see RAG); or to to add conversational history to the otherwise stateless chat model API.</p>
<p>Note that the SpringAI team warns that the order in which advisors are added to the advisor chain is crucial, like for other middleware. An advisor that has been added before another advisor to the advisor chain is executed first.</p>
<p>Interestingly, if you are having a look into the <code>DefaultChatClient</code> implementation, you can see that <em>calling</em> or <em>streaming</em> the chat model is realized bz just two Advisors that have been added at the end of the chain:</p>
<div>
<pre>private BaseAdvisorChain buildAdvisorChain() {
    // At the stack bottom add the model call advisors.
    // They play the role of the last advisors in the advisor chain.
    this.advisors.add(ChatModelCallAdvisor.builder().chatModel(this.chatModel).build());
    this.advisors.add(ChatModelStreamAdvisor.builder().chatModel(this.chatModel).build());

    return DefaultAroundAdvisorChain.builder(this.observationRegistry)
        .observationConvention(this.advisorObservationConvention)
        .pushAll(this.advisors)
        .build();
}</pre>
</div>
<p>Now, let&#8217;s enhance our current <code>ArticleService</code> implementation with the standard <code>MessageChatMemoryAdvisor</code>. First we need to inject a <code>ChatMemory</code> into our <code>ArticleService</code>:</p>
<div>
<pre>@Service
public class ArticleService {

  private static final Logger log = LoggerFactory.getLogger(ArticleService.class);

    
  private final ArticleRepository articleRepository;
  private final ChatClient chatClient;
  private final ChatMemory chatMemory;

  public ArticleService(
      final ArticleRepository articleRepository,
      final ChatClient.Builder chatClientBuilder,
      final ChatMemory chatMemory) {
    this.articleRepository = articleRepository;
    this.chatClient = chatClientBuilder.build();
    this.chatMemory = chatMemory;
  }</pre>
</div>
<p>As we have not declared any other bean, SpringAI will bind it with the default <code>InMemoryChatMemoryRepository</code>. There exists other <code>ChatMemoryRepository</code> implementations. For example, you can store your <code>ChatMemory</code> to a relational database with the help of a <code>JdbcChatMemoryRepository</code>.</p>
<p>This <code>ChatMemory</code> instance we are going to pass to our <code>MessageChatMemoryAdvisor</code> middleware:</p>
<div>
<pre>  public String summarizeArticles() {
    final MessageChatMemoryAdvisor chatMemoryAdvisor =
        MessageChatMemoryAdvisor.builder(chatMemory).build();

    final List&lt;Document&gt; articles = articleRepository.getAll();

    articles.stream()
        .filter(article -&gt; StringUtils.isNotBlank(article.getText()))
        // Max length of 8000 characters to avoid API limits
        .map(abbreviateArticleContent())
        .forEach(
            articleContent -&gt; {
              try {
                log.info("Calling AI API with article. [id={}]", articleContent.id());

                chatClient
                    .prompt()
                    .advisors(chatMemoryAdvisor)
                    .user("Provide a summary of the following article:\n\n" + articleContent.text())
                    .call()
                    // We need to call .content() in order to actually retrieve the answer and store
                    // it
                    // in the chat memory.
                    .content();

                log.info(
                    "Successfully summarized article content with AI model. Sleeping now... [id={}]",
                    articleContent.id());

                // Sleep for 60 seconds after each API call to avoid rate limiting
                Thread.sleep(60000);

              } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Thread was interrupted during sleep", e);
              }
            });</pre>
</div>
<p>It is important that you call the <code>.content()</code> method and not only the <code>.call()</code> method.</p>
<p>Neither the <code>.call()</code> nor the <code>.stream()</code> method do actually trigger the AI model execution. Instead, they only instruct Spring AI whether to use synchronous or streaming calls. The actual terminal operations are <code>.content()</code>, <code>.chatResponse()</code>, and <code>.responseEntity()</code>. In our example, only by calling <code>.content()</code> method thus, we are actually storing the answers to the <code>MessageChatMemoryAdvisor</code>.</p>
<p>Furthermore, note the ugly <code>Thread.sleep()</code> call. The Anthropic API may return with a HTTP 429 error because of a 20,000 input tokens per minute rate limit. We circumvent this limit here with the help of the sleep. However, this gives you a first glimpse of the difficulties to scale AI model usage in a high data volume context, such as for enterprise applications.</p>
<p>With a final prompt, we will ask the AI to work with the previous answers. Let&#8217;s assume we build an editorial agent for a travel magazine. We want it to evaluate whether we can recommend arbitrary news articles as a travel destination.</p>
<div>
<pre>    return chatClient
        .prompt()
        .advisors(chatMemoryAdvisor)
        .user(
            "Imagine you are a journalist in a news outlet. You work for a travel magazine that is based in Europe, "
                + "and thus its readers are European travelers. A colleague of yours has summarized five articles for "
                + "you. Now it is your turn to pick a subject out of these five articles and write a short travel "
                + "recommendation. The idea is that you write a single paragraph that praises a destination or activity "
                + "that you want to recommend to your readers."
                + "\n\n"
                + "Given the summaries provided, what is the most interesting topic?")
        .call()
        .content();</pre>
</div>
<p>The idea here is that the LLM correctly picks the article about <a title="https://en.wikipedia.org/wiki/N%C3%A9ant-sur-Yvel" href="https://en.wikipedia.org/wiki/N%C3%A9ant-sur-Yvel" data-from-md="">Néant-sur-Yvel</a>, a village of a thousand inhabitants in Brittany, France.</p>
<p>Let&#8217;s run it through the model&#8230;</p>
<div>
<pre>  @Bean
  public CommandLineRunner commandLineRunner(final ArticleService articleService) {
    return args -&gt; {
      log.info("Okay, I am going to summarize articles and provide travel recommendations...");

      final String recommendation = articleService.summarizeArticles();

      log.info("The travel recommendation is:");
      log.info(recommendation);
      log.info("Done!");
    };
  }</pre>
</div>
<p>And here comes its answer:</p>
<div>
<pre>Tucked away in the enchanting landscape of Brittany, the commune of Néant-sur-Yvel offers discerning travelers a perfect escape from the well-trodden tourist paths of France. This picturesque village, nestled along the banks of the Yvel river, embodies authentic rural French charm that has remained largely undiscovered by mass tourism. With its medieval architecture, verdant countryside perfect for cycling and hiking, and proximity to the legendary Brocéliande Forest—steeped in Arthurian legends—Néant-sur-Yvel provides a genuine glimpse into traditional Breton life. The village makes an ideal base for exploring the wider Morbihan department, with its megalithic monuments and stunning coastline just a scenic drive away. Visit in late spring when the countryside bursts with wildflowers, and don't miss sampling local Breton specialties like galettes and cider in the village's unassuming but delightful eateries. For travelers seeking to experience the France that exists beyond the postcard views of Paris, Néant-sur-Yvel delivers an authentic slice of Brittany that will leave you enchanted.</pre>
</div>
<p>With such a beautiful answer, who wouldn&#8217;t love to travel to Néant-sur-Yvel now? <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<h2 id="final-word">Final word</h2>
<p>The example has shown that Spring AI is a strong choice for enterprise AI applications. It offers a modular, modern, and well‑integrated feature set. However, scaling AI requires careful planning: token limits, API rate limits, and operational costs must all be considered. This is were the true challenge lies.<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fgetting-started-with-springai.html&amp;action_name=Getting%20Started%20with%20SpringAI&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/getting-started-with-springai.html">Getting Started with SpringAI</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/getting-started-with-springai.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6211</post-id>	</item>
		<item>
		<title>Making Java a first-class AI citizen with Langchain4j</title>
		<link>https://www.javaadvent.com/2025/12/making-java-a-first-class-ai-citizen-with-langchain4j.html</link>
					<comments>https://www.javaadvent.com/2025/12/making-java-a-first-class-ai-citizen-with-langchain4j.html#respond</comments>
		
		<dc:creator><![CDATA[deors]]></dc:creator>
		<pubDate>Thu, 18 Dec 2025 03:03:33 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6271</guid>

					<description><![CDATA[<p>Introduction When the first technical solutions based on Artificial Intelligence began to be created, Python was the language and runtime platform of choice. It was not a total surprise as Python was the choice of the great majority of data scientists to analyse data, perform experiments and create AI models, so it was kind of [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fmaking-java-a-first-class-ai-citizen-with-langchain4j.html&amp;action_name=Making%20Java%20a%20first-class%20AI%20citizen%20with%20Langchain4j&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/making-java-a-first-class-ai-citizen-with-langchain4j.html">Making Java a first-class AI citizen with Langchain4j</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Introduction</h1>
<p>When the first technical solutions based on Artificial Intelligence began to be created, Python was the language and runtime platform of choice. It was not a total surprise as Python was the choice of the great majority of data scientists to analyse data, perform experiments and create AI models, so it was kind of natural that AI solutions were also based on Python and its rich ecosystem for data-intensive applications.</p>
<p>However, with the rise of Generative AI, organisations worldwide reconsider that approach given that the vast majority of GenAI-based solutions are just leveraging existing Large Language Models consumed &#8220;as a service&#8221; via web APIs. For doing that, Python does not have the same lead over the others. We must balance other key aspects of enterprise-grade solutions, such as resilience, scalability, observability, as well as the leverage of existing skills in the organisation. That means that other key languages and platforms can play a leading role in delivering and running GenAI-based solutions. Platforms such as Node.js, Go, and, of course, Java.</p>
<p>In Java we already have multiple valid approaches to be considered.In this article I will cover Langchain4j, one of the most popular choices in the Java ecosystem for building and running AI solutions at scale.</p>
<p>Why Langchain4j? Key outsdanding aspects are:</p>
<ol>
<li>Framework-agnostic: As a library it imposes no constraints about how you design and build your solutions, so it can be easily integrated with any existing solution, either Spring-based, Jakarta-based, Quarkus-based, Micronaut-based, or with no framework at all.</li>
<li>Simple yet powerful API: Based on well-known patterns such as the Builder pattern, and with simple API constructs so its learning curve is simple and rewarding:</li>
<li>It works with both cloud-based &#8220;as a service&#8221; models such as OpenAI or Google Vertex, and with local/owned models via Ollama.</li>
</ol>
<p>NOTE: The following examples are based on Langchain4j 0.36.2.</p>
<h1>The first Langchain4j program</h1>
<p>To demonstrate these concepts, let&#8217;s look at a &#8220;hello world&#8221; Langchain4j program.</p>
<p>The main interface that we need to learn about is <strong>ChatLanguageModel</strong> (from package <strong>dev.langchain4j.model.chat</strong>). This interface has the simple API that we need to send messages to an LLM and get its response. To instantiate specific models to interact with them we need the specific implementation depending on who provides them:</p>
<ul>
<li>For OpenAI, we leverage <strong>OpenAiChatModel</strong> from package <strong>dev.langchain4j.model.openai.OpenAiChatModel</strong>.</li>
<li>For Vertex, we leverage <strong>VertexAiGeminiChatModel</strong> from package <strong>dev.langchain4j.model.vertexai</strong>.</li>
<li>For Ollama, we leverage <strong>OllamaChatModel</strong> from package <strong>dev.langchain4j.model.ollama.OllamaChatModel</strong>.</li>
</ul>
<p>As well as others. Every implementation of <strong>ChatLanguageModel</strong> has its own builder pattern to be able to add any specific configuration setting that is needed. Let&#8217;s see three brief examples of how this looks once we put it together:</p>
<h2>OpenAI Hello World</h2>
<pre>import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;

class OpenAIHelloWorld {

    void main() {
        // OpenAI model
        ChatLanguageModel model = OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4o")
            .build();

        // the first prompt
        String message = "Hello world!";
        System.out.println("\n&gt;&gt;&gt; " + message);

        String answer = model.generate(message);
        System.out.println(answer);
    }
}</pre>
<p>As can be seen above, to connect with OpenAI services you must provide your own API key. You can also add explicitly the model that you want to be used.</p>
<h2>Vertex Hello World</h2>
<div>
<pre>import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;

class VertexAIHelloWorld {
    void main() {
        // Vertex AI model
        ChatLanguageModel model = VertexAiGeminiChatModel.builder()
            .project(System.getenv("VERTEXAI_PROJECT_ID"))
            .location("us-central1")
            .modelName("gemini-2.5-flash")
            .build();

        // the first prompt
        String message = "Hello world!";
        System.out.println("\n&gt;&gt;&gt; " + message);

        String answer = model.generate(message);
        System.out.println(answer);
    }
}</pre>
</div>
<p>The pattern is similar to the previous but the settings that must be provided are different: the project id in Vertex, the cloud region and the model name.</p>
<h2>Ollama Hello World</h2>
<pre>import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.ollama.OllamaChatModel;

class OllamaGptOssHelloWorld {
    void main() {
        // gpt-oss:20b model running locally with Ollama
        ChatLanguageModel model = OllamaChatModel.builder()
            .baseUrl("http://localhost:11434")
            .modelName("gpt-oss:20b")
            .build();

        // the first prompt
        String message = "Hello world!";
        System.out.println("\n&gt;&gt;&gt; " + message);

        String answer = model.generate(message);
        System.out.println(answer);

    }
}</pre>
<p>Again, the pattern is similar. In this case we are running Ollama in the local computer and using the<strong> gpt-oss:20b</strong> model, quite competent and runnable in many personal computers.</p>
<h1>Managing the context (a.k.a. short-term chat memory)</h1>
<p>While the previous examples work, they lack a critical feature that any GenAI solution would need. The first important concept that we need to understand is how to manage the context of the conversation with the LLMs, also known as the short-term memory.</p>
<p>In essence, what we must do is to track the whole conversation with the LLM (a.k.a. &#8220;the chat&#8221;). After every interaction, the pair question and answer are saved to be sent with the next request payload, typically models expect that under a specific history entry in the request body in Json format. Fortunately, Langchain4j deals with those details and we just focus on keeping track of the conversation. The simplest way to do that is to use a in-memory store, as we can see in the following example:</p>
<div>
<pre>ChatLanguageModelmodel = OllamaChatModel.builder()
    .baseUrl(baseUrl)
    .modelName(modelName)
    .timeout(Duration.ofSeconds(300))
    .temperature(0.0)
    .build();

// define context window
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);

// initial prompt with name and what I'm doing
String message = "Hello world! My name is Jorge and I'm writing this for Java Advent 2025.";
chatMemory.add(userMessage(message));

AiMessage answer = model.generate(chatMemory.messages()).content();
System.out.println(answer.text());
chatMemory.add(answer);

// ask for the name
message = "What is my name?";
chatMemory.add(userMessage(message));

answer = model.generate(chatMemory.messages()).content();
System.out.println(answer.text());
chatMemory.add(answer);</pre>
</div>
<div></div>
<div><strong>ChatMemory</strong> (from <strong>package dev.langchain4j.memory</strong>) is the interface that abstracts the different implementations of short-term memory. In this example, we just use the implementation provided by <strong>MessageWindowChatMemory</strong> (from package <strong>dev.langchain4j.memory.chat</strong>) with a maximum capacity of 10 messages. Other implementations may have other ways to set the maximum capacity, e.g., using the token count.</div>
<p>As can be seen in the example, the whole chat memory is passed to the model. As we keep adding every message and answer into the memory, the LLM will leverage the whole conversation (up to the limits of the memory or its own internal context window, whatever comes first) to come up with the best possible answer.</p>
<p>The static function <strong>userMessage</strong> from class <strong>dev.langchain4j.data.message.UserMessage</strong> helps to simplify keeping the history up to date by putting the user prompt in the right place.</p>
<h1>Enriching answers with Retrieval Augmented Generation (RAG)</h1>
<p>No matter how big is the LLM we use for our solutions it lacks something key: the business-specific data. Facts and figures, business processes, knowledge bases&#8230; Every bit and piece of internal information of the organisation that is therefore not part of the public data sets used to train LLMs.</p>
<p>If we want to create really useful GenAI-based solutions we need them to be aware of the potential user context: what do they need, what they must know.</p>
<div>
<div>RAG is a very simple and time- and cost-effective pattern to have our AI agents better prepared for their assigned tasks, as compared with training your own models or fine-tuning existing ones.</div>
<p>To augment the LLM answer the RAG pattern enriches the context with pieces of information that are connected to the user&#8217;s problem or request. This is done by querying a vector search database or a graph database and obtain the documents or document portions that seem to be related to the user&#8217;s prompt.</p>
<p>RAG can be seen as a form of long-term memory: we prepare the knowledge bases or graphs before an agent is first released into the public, and is suitable for continuous improvement as knowledge base sources are not read-only. RAG pattern also plays well with continuous feedback, as users report about our agents performance (e.g., correctness, completeness, relevance of results, etc.) and that feedback leads to refining prompts and the content in knowledge bases.</p>
<p>RAG is a two part process, then:</p>
<ol>
<li>The existing know-how is processed: parse, tokenization, vectorization, store in KB store. This process can be done periodically or even continuously if needed.</li>
<li>When the user asks for something the prompt is used to search for the relevant information in the KB store, the best ranked results are used to augment the prompt, and the whole set of data is sent to the LLM to get the final response.</li>
</ol>
<p>It is important to note that as the relevant pieces of information go within the context, and are subject to context limits, it is important to balance the quantity of data that is retrieved and ranked, as we cannot simply add every piece of the KB into the context.</p>
<p>RAG can be seen graphically in this diagram:</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/rag.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6273" data-permalink="https://www.javaadvent.com/2025/12/making-java-a-first-class-ai-citizen-with-langchain4j.html/rag" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/rag.jpg?fit=1024%2C559&amp;ssl=1" data-orig-size="1024,559" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="rag" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/rag.jpg?fit=300%2C164&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/rag.jpg?fit=600%2C328&amp;ssl=1" class="alignnone size-full wp-image-6273" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/rag.jpg?resize=600%2C328&#038;ssl=1" alt="" width="600" height="328" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/rag.jpg?w=1024&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/rag.jpg?resize=300%2C164&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/rag.jpg?resize=768%2C419&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/rag.jpg?resize=508%2C277&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<h2>Implementing RAG with Langchain4j</h2>
<p>In Langchain4j we can implement both parts of the pattern:</p>
<ol>
<li>Ingest documents containing the organisation knowledge to build up the knowledge base. For simple use cases, this KB can be maintained even in memory (e.g., for a bunch of PDF documents) and is pretty convenient to build many specialised agents with minimal dependencies (and investment).</li>
<li>Access the knowledge base when users asks for something to get the best possible results.</li>
</ol>
<p>Let&#8217;s see how this works in practice.</p>
<h2>Building the KNowledge Base</h2>
<p>To build the knowledge base with Langchain4j we need the following abstractions:</p>
<ul>
<li><strong>EmbeddingModel</strong> from package <strong>dev.langchain4j.model.embedding</strong>: This is responsible for converting text into embeddings, that is, numerical representations (vectors) of pieces of text (tokens). In the example below, that can also be used in simple use cases, we will leverage the popular <strong>MiniLM-L6-V2</strong> model.</li>
<li><strong>EmbeddingStore</strong> from package <strong>dev.langchain4j.store.embedding</strong>: This is responsible for abstracting the actual store, e.g. a vector search database. In the example below, that can also be used in simple use cases, we will leverage an in-memory store.</li>
<li><strong>DocumentSplitter</strong> from package <strong>dev.langchain4j.data.document</strong>: This is responsible for chunking the know-how documents. To parse documents in binary formats into text, in the example we leverage the popular <strong>Apache Tika</strong> library.</li>
<li>
<div>
<div><strong>EmbeddingStoreIngestor</strong> from package <strong>dev.langchain4j.store.embedding</strong>: This is responsible to ingest every parsed document into the embedding store with the provided document splitter and embedding model.</div>
</div>
</li>
</ul>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/embedding.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6275" data-permalink="https://www.javaadvent.com/2025/12/making-java-a-first-class-ai-citizen-with-langchain4j.html/embedding" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/embedding.png?fit=892%2C406&amp;ssl=1" data-orig-size="892,406" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="embedding" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/embedding.png?fit=300%2C137&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/embedding.png?fit=600%2C273&amp;ssl=1" class="alignnone size-full wp-image-6275" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/embedding.png?resize=600%2C273&#038;ssl=1" alt="" width="600" height="273" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/embedding.png?w=892&amp;ssl=1 892w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/embedding.png?resize=300%2C137&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/embedding.png?resize=768%2C350&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/embedding.png?resize=508%2C231&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p>A simple example code with Langchain4j would be like this:</p>
<div>
<div>
<pre>// an embedding model good for simple documents
EmbeddingModel embModel = new AllMiniLmL6V2EmbeddingModel();

// an in-memory embedding store
EmbeddingStore&lt;TextSegment&gt; embStore = new InMemoryEmbeddingStore&lt;&gt;();

// load a PDF file from the classpath
Path path = Path.of(ClassLoader.getSystemResource("acme-know-how.pdf").toURI());

Document document = FileSystemDocumentLoader.loadDocument(path, new ApacheTikaDocumentParser());

DocumentSplitter splitter = DocumentSplitters.recursive(256, 0);

// ingest the document into the embedding store
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
    .documentSplitter(splitter)
    .embeddingModel(embModel)
    .embeddingStore(embStore)
    .build();

ingestor.ingest(document);</pre>
</div>
</div>
</div>
<h2>Augmenting the Responses</h2>
<p>No matter if the knowledge base is created at runtime or it is a persistence enterprise-grade vector search database, the abstraction needed to augment the response during retrieval are the same:</p>
<ul>
<li><strong>ContentRetriever</strong> from
<div>
<div>package <strong>dev.langchain4j.rag.content.retriever</strong>: This is responsible to abstract the embedding store and model that will be used to look for the relevant data in the KB.</div>
</div>
</li>
<li><strong>AiServices</strong> from package <strong>dev.langchain4j.service.AiServices</strong>: This is a very convenient abstraction to create AI agents combining a given chat model and chat memory (as seen in the previous examples) with the content retriever.</li>
</ul>
<p>The retrieval example with Langchain4j is quite straightforward:</p>
<div>
<pre>// define the content retriever connecting everything together

ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
    .embeddingModel(embModel)
    .embeddingStore(embStore)
    .maxResults(1)
    .minScore(0.8)
    .build();
       
// llama3:8b model running locally with Ollama
ChatLanguageModel chatModel = OllamaChatModel.builder()
    .baseUrl("http://localhost:11434")
    .modelName("llama3:8b")
    .build();

// define context window
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(100);

Agent agent = AiServices.builder(Agent.class)
    .chatLanguageModel(chatModel)
    .chatMemory(chatMemory)
    .contentRetriever(retriever)
    .build();     

String message1 = "Could you summarize in 50 words the main concepts about the Java Platform?";

String answer1 = agent.answer(message1);</pre>
<p>The interface <strong>Agent</strong> is a simple abstraction of our agent and its system prompt:</p>
<pre>interface Agent {
    @SystemMessage("""
        You are an expert in information technologies
        and software engineering.
        """)
    String answer(String inputMessage);
}</pre>
<h1>Conclusions</h1>
<p data-path-to-node="3">The integration of Generative AI into enterprise software is no longer solely the domain of Python scripts or expensive cloud APIs. As we can see, Langchain4j offers a production-grade library for building agentic solutions at scale:</p>
<ul>
<li data-path-to-node="3">Decoupling: Langchain4j acts as a robust anti-corruption layer. By coding against interfaces like <strong>ChatLanguageModel</strong> and <strong>EmbeddingModel</strong>, applications can remain up to a certain degree agnostic to the underlying provider.</li>
<li data-path-to-node="3">Simplicity: The <strong>AiServices</strong> API brings the familiarity of aspect-oriented programming (similar to Spring Data) to AI. Complex orchestration involving RAG retrieval, history management, and prompt engineering is abstracted behind clean Java interfaces and annotations.</li>
<li data-path-to-node="3">Local Inference Viability: With the optimization of models (quantization) and the efficiency of modern hardware, running capable small-sized or medium-sized models augmented with the organization know-how on your own hardware is not just possible but practical for development cycles, CI/CD pipelines, privacy-sensitive edge deployments, and cost-effective deployments.</li>
</ul>
<h1>Knowing more</h1>
<p>If you want to know more and explore Langchain4j in deep, the following resources will be helpful:</p>
<ul>
<li>I created a step by step workshop with lots of examples here: <a href="https://github.com/deors/workshop-langchain4j">https://github.com/deors/workshop-langchain4j</a></li>
<li>The Langchain4j project tutorials: <a href="https://docs.langchain4j.dev/category/tutorials/">https://docs.langchain4j.dev/category/tutorials/</a></li>
</ul>
<p>&nbsp;</p>
</div>
<p><img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fmaking-java-a-first-class-ai-citizen-with-langchain4j.html&amp;action_name=Making%20Java%20a%20first-class%20AI%20citizen%20with%20Langchain4j&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/making-java-a-first-class-ai-citizen-with-langchain4j.html">Making Java a first-class AI citizen with Langchain4j</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/making-java-a-first-class-ai-citizen-with-langchain4j.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6271</post-id>	</item>
		<item>
		<title>Run Into the New Year with Java’s Ahead-of-Time Cache Features</title>
		<link>https://www.javaadvent.com/2025/12/run-java-aot-cache-optimizations.html</link>
					<comments>https://www.javaadvent.com/2025/12/run-java-aot-cache-optimizations.html#respond</comments>
		
		<dc:creator><![CDATA[Ana-Maria Mihalceanu]]></dc:creator>
		<pubDate>Wed, 17 Dec 2025 03:03:45 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[Java Advent]]></category>
		<category><![CDATA[openjdk]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[jdk25]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[Project Leyden]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6128</guid>

					<description><![CDATA[<p>As the year comes to a close, turn your focus to boosting your Java application performance by applying Ahead-of-Time (AOT) cache features in recent JDK releases. This article guides you through using AOT cache optimizations in your application, thereby minimizing startup time and achieving faster peak performance. What is the Ahead-of-time cache in the jdk [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Frun-java-aot-cache-optimizations.html&amp;action_name=Run%20Into%20the%20New%20Year%20with%20Java%E2%80%99s%20Ahead-of-Time%20Cache%20Features&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/run-java-aot-cache-optimizations.html">Run Into the New Year with Java’s Ahead-of-Time Cache Features</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p style="text-align: left">As the year comes to a close, turn your focus to boosting your Java application performance by applying Ahead-of-Time (AOT) cache features in recent JDK releases. This article guides you through using AOT cache optimizations in your application, thereby minimizing startup time and achieving faster peak performance.</p>
<h2>What is the Ahead-of-time cache in the jdk</h2>
<p>JDK 24 introduced the Ahead-Of-Time (AOT) cache, a HotSpot JVM feature that stores classes after they are read, parsed, loaded, and linked. Creating an AOT cache is specific to an application, and you can reuse it in subsequent runs of that application to improve the time to the first functional unit of work (startup time).</p>
<p>To generate an AOT cache, you need to perform two steps:</p>
<ol>
<li>Training by recording observations of the application in action. You can trigger a recording by setting an argument for the <code>-XX:AOTMode</code> option and giving a destination for the configuration file via <code>-XX:AOTConfiguration</code>:
<pre>java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf 
     -cp app.jar com.example.App ...</pre>
<p>This step aims to answer questions like &#8220;Which classes does the application load and initialize?&#8221;, &#8220;Which methods become hot?&#8221; and store the results in a configuration file (<code>app.aotconf</code>).</li>
<li>Assembly that converts the observations from the configuration file into an AOT cache (<code>app.aot</code>).
<pre>java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf 
     -XX:AOTCache=app.aot -cp app.jar</pre>
</li>
</ol>
<p>To benefit from a better startup time, run the application by pointing the -XX:AOTCache flag to the resulting AOT cache.</p>
<pre>java -XX:AOTCache=app.aot -cp app.jar com.example.App ...</pre>
<p>The improved startup time is the result of shifting work, usually done just-in-time when the program runs, earlier to the second step, which creates the cache. Thereafter, the program starts up faster in the third phase because its classes are available from the cache immediately.</p>
<p>The three-step workflow (train+assemble+run) became available starting with JDK 24, via <a href="https://openjdk.org/jeps/483">JEP 483: Ahead-of-Time Class Loading &amp; Linking</a>, the first feature merged from the research done by <a href="https://openjdk.org/projects/leyden/">Project Leyden</a>. A set of <a href="https://github.com/openjdk/leyden/blob/634547513c2a2b707ae43a735dc24fd1977da2ae/README.md#5-benchmarking">benchmarks</a> prove the effectiveness of this feature and other Leyden performance-related ones, as displayed by <em>Figure 1</em>.</p>
<p><div id="attachment_6191" style="width: 2570px" class="wp-caption aligncenter"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6191" data-attachment-id="6191" data-permalink="https://www.javaadvent.com/2025/12/run-java-aot-cache-optimizations.html/aot-cache-bench-jdk24" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?fit=2560%2C1440&amp;ssl=1" data-orig-size="2560,1440" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="aot-cache-bench-jdk24" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?fit=600%2C338&amp;ssl=1" class="wp-image-6191 size-full" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?resize=600%2C338&#038;ssl=1" alt="Figure 1: AOT Cache Benchmarks as of JDK 24" width="600" height="338" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?w=2560&amp;ssl=1 2560w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?resize=2048%2C1152&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?resize=1240%2C698&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?resize=508%2C286&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk24.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a><p id="caption-attachment-6191" class="wp-caption-text">Figure 1: AOT Cache Benchmarks as of JDK 24</p></div></p>
<p><span data-preserver-spaces="true">In JDK 25, the changes in </span><a class="editor-rtfLink" href="https://openjdk.org/jeps/515" target="_blank" rel="noopener"><span data-preserver-spaces="true">JEP 515 &#8211; Ahead-of-Time Method Profiling</span></a><span data-preserver-spaces="true"> enabled frequently executed method profiles to be part of the AOT cache. This addition improves application warm up by allowing the JIT to start generating native code immediately at application startup. The new AOT feature does not require you to add more constraints to your application execution; just use the existing AOT cache creation commands. Moreover, </span><a class="editor-rtfLink" href="https://github.com/openjdk/leyden/blob/634547513c2a2b707ae43a735dc24fd1977da2ae/README.md" target="_blank" rel="noopener"><span data-preserver-spaces="true">benchmarks</span></a><span data-preserver-spaces="true"> showed improved startup time too (</span><em><span data-preserver-spaces="true">Figure 2</span></em><span data-preserver-spaces="true">).</span></p>
<p><div id="attachment_6192" style="width: 2570px" class="wp-caption aligncenter"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6192" data-attachment-id="6192" data-permalink="https://www.javaadvent.com/2025/12/run-java-aot-cache-optimizations.html/aot-cache-bench-jdk25" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?fit=2560%2C1440&amp;ssl=1" data-orig-size="2560,1440" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="aot-cache-bench-jdk25" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?fit=600%2C338&amp;ssl=1" class="wp-image-6192 size-full" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?resize=600%2C338&#038;ssl=1" alt="Figure 2: AOT Cache Benchmarks as of JDK 25" width="600" height="338" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?w=2560&amp;ssl=1 2560w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?resize=2048%2C1152&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?resize=1240%2C698&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?resize=508%2C286&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/aot-cache-bench-jdk25.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /><p id="caption-attachment-6192" class="wp-caption-text">Figure 2: AOT Cache Benchmarks as of JDK 25</p></div></p>
<p>JDK 25 also simplified the process for generating an AOT cache by making it possible to do it in a single step, through setting the argument for <code>-XX:AOTCacheOutput</code> flag:</p>
<pre># Training Run + Assembly Phase
java -XX:AOTCacheOutput=app.aot \
     -cp app.jar com.example.App ...</pre>
<p>Upon passing <code>-XX:AOTCacheOutput=[cache<span class="token space"> </span>location]</code>, the JVM creates the cache on its shutdown. <a href="https://openjdk.org/jeps/514">JEP 514 &#8211; Ahead-of-Time Command-Line Ergonomics</a> introduced the two-step process for creating and using the AOT cache.</p>
<pre># Training Run + Assembly Phase
java -XX:AOTCacheOutput=app.aot \
     -cp app.jar com.example.App ...

# Deployment Run
java -XX:AOTCache=app.aot -cp app.jar com.example.App ...</pre>
<p>The two-step workflow may not work as expected in resource-constrained environments. The sub-invocation that creates the AOT cache uses its own Java heap with the same size as the heap used for the <a href="https://openjdk.org/projects/leyden/notes/05-training-runs">training run</a>. As a result, the memory needed to complete the one-step AOT cache generation is double the heap size specified on the command line. For example, if the one-step workflow <code>java<span class="token space"> </span>-XX:AOTCacheOutput=...</code> is accompanied by <code>-Xms2g<span class="token space"> </span>-Xmx2g</code>, specifying a 2GB heap, then the environment needs 4GB to complete the workflow.</p>
<p>A division of steps, as in a three-phase workflow, may be a better choice if you intend to deploy an application to small cloud tenancies. In such cases, you could run the training on a small instance while creating the AOT cache on a larger one. That way, the training run reflects the deployment environment, while the AOT cache creation can leverage the additional CPU cores and memory of the large instance.</p>
<p>Regardless of which workflow you choose, let’s take a closer look at AOT cache requirements and how to set it up to serve your application needs best.</p>
<h2 id="how-to-craft-the-aot-cache-your-application-needs">How to Craft the AOT Cache Your Application Needs</h2>
<p>Training and production runs should produce consistent results, just faster in deployment runs. To achieve that, the assembly phase intermediates what happens between training and production runs (<em>Figure 3</em>).</p>
<p><div id="attachment_6193" style="width: 2176px" class="wp-caption aligncenter"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6193" data-attachment-id="6193" data-permalink="https://www.javaadvent.com/2025/12/run-java-aot-cache-optimizations.html/train-assembly-run" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?fit=2166%2C907&amp;ssl=1" data-orig-size="2166,907" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="train-assembly-run" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?fit=300%2C126&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?fit=600%2C251&amp;ssl=1" class="wp-image-6193 size-full" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?resize=600%2C251&#038;ssl=1" alt="Figure 3: Training / Assembly / Deployment" width="600" height="251" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?w=2166&amp;ssl=1 2166w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?resize=300%2C126&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?resize=1024%2C429&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?resize=768%2C322&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?resize=1536%2C643&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?resize=2048%2C858&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?resize=1240%2C519&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?resize=508%2C213&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/train-assembly-run.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a><p id="caption-attachment-6193" class="wp-caption-text">Figure 3: Training / Assembly / Deployment</p></div></p>
<p>For consistent training runs and the subsequent ones, make sure that:</p>
<ul>
<li>Your JARs preserve their timestamp across training runs.</li>
<li>Your training runs and the production one use the same JDK release for the same hardware architecture and operating system.</li>
<li>Provide the classpath for your application as a list of JARs, without any directories, wildcards or nested JARs.</li>
<li>Production run classpath must be a superset of the training one.</li>
<li>Do not use use JVMTI agents that call the <code>AddToBootstrapClassLoaderSearch</code>and <code>AddToSystemClassLoaderSearch</code> APIs.</li>
</ul>
<p>To check if your JVM is correctly configured to use the AOT cache, you can add the option <code>-XX:AOTMode=on</code> to the command line:</p>
<pre>java -XX:AOTCache=app.aot -XX:AOTMode=on \
     -cp app.jar com.example.App ...</pre>
<p>The JVM will report an error if the AOT cache does not exist or if your setup disregards any of the above requirements. Furthermore, the features introduced in JDK 24 and 25 did not support the Z Garbage Collector (ZGC). Yet, this limitation no longer applies as of JDK 26, with the introduction of <a href="https://openjdk.org/jeps/516">JEP 516: Ahead-of-Time Object Caching with Any GC</a>.</p>
<p>To ensure the AOT cache works effectively in production, the training run and all following runs must be essentially identical. Training runs are a way of observing what an application is doing across different runs and are primarily two types:</p>
<ul>
<li>integration tests, which run at build time</li>
<li>production workloads, which require training in production.</li>
</ul>
<p>Avoid loading unused classes during the training step and skip rich test frameworks to keep the AOT cache minimal. Mock external dependencies in training to load needed classes, but be aware that this may introduce extra cache entries.</p>
<p>AOT cache effectiveness depends on how closely the training run matches production behavior. If you rebuild the application or upgrade its JDK, you must regenerate the AOT cache. Otherwise, you risk crashes or undefined behavior (methods missing from cache).</p>
<p>In case you need to debug the performance of your application, run it with <code>-Xlog:aot,class+path=info</code> to monitor what it loads from cache.</p>
<h2 id="tips-for-efficient-training-runs">Tips for Efficient Training Runs</h2>
<p>There is a trade-off between performance and how easy it is to run the training. Using a production run for training is not always practical, especially for server applications, which can create log files, open network connections, access databases, etc. For such cases, it is better to make a synthetic training run that closely resembles actual production runs.</p>
<p>Aligning the training run to load the same classes as production helps to achieve an optimized startup time. To determine which classes are loaded by your training run, you can append the <code>-verbose:class</code> flag upon launching it. Or observe the loaded classes by enabling the <code>jdk.ClassLoad</code> JFR event and profiling your application with it:</p>
<pre># configure the event
jfr configure jdk.ClassLoad#enabled=true

# profile as soon as your application launches
java -XX:StartFlightRecording:settings=custom.jfc,duration=60s,filename=/tmp/AOT.jfr

# profile on a running application identified through llvmid
jcmd llvmid JFR.start settings=custom.jfc duration=60s filename=/tmp/AOT.jfr
</pre>
<p>On the recording file, you may check the loaded classes, but also which methods your application frequently uses by running the following <code>jfr</code> commands:</p>
<pre># print jdk.ClassLoad events from a recording file
jfr print --events "jdk.ClassLoad" /tmp/AOT.jfr

# view frequently executed methods
jfr view hot-methods /tmp/AOT.jfr
</pre>
<p>If you determine that there are methods frequently used but not detected by your training run, exercise them. You can work out the standard modes of your application using a temporary file directory, a local network configuration, and a mocked database, if needed.<br />
Avoid loading unused classes during training and skip rich test frameworks to keep the AOT cache minimal. Instead, use smoke tests to cover typical startup paths; avoid extensive suites and stress/regression tests.</p>
<h2>Takeways</h2>
<p>To conclude, crafting an AOT cache for better performance requires you to look over:</p>
<ul>
<li>Cache validity or staleness; if you rebuild the application or upgrade the JDK, you must regenerate the AOT cache.</li>
<li>Portability, as the AOT cache is JVM and platform-specific.</li>
<li>Startup path coverage; the training run must cover typical application startup paths. If your training run is shallow, you will not warm up enough, and the benefits of the cache will be limited.</li>
<li>Operational setup as both the application JAR and the AOT cache must run with least privilege and according to immutable infrastructure practices.</li>
</ul>
<p>Application performance is an ongoing task because software evolves: new features are added, libraries change, workloads grow, and infrastructure shifts (e.g., to the cloud, container orchestration, etc.). Depending on those evolutions, your application performance goals evolve as well. Invest in training your application today and keep up with JDK releases to unlock available optimizations, as performance improves with each of them!<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Frun-java-aot-cache-optimizations.html&amp;action_name=Run%20Into%20the%20New%20Year%20with%20Java%E2%80%99s%20Ahead-of-Time%20Cache%20Features&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/run-java-aot-cache-optimizations.html">Run Into the New Year with Java’s Ahead-of-Time Cache Features</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/run-java-aot-cache-optimizations.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6128</post-id>	</item>
		<item>
		<title>The FFM API: How OpenJDK Changed the Game for Native Interactions (And Made Pi4J Better!)</title>
		<link>https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html</link>
					<comments>https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html#respond</comments>
		
		<dc:creator><![CDATA[Frank Delporte]]></dc:creator>
		<pubDate>Tue, 16 Dec 2025 03:03:39 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5947</guid>

					<description><![CDATA[<p>The Pi4J project is a Java library that allows you to control the GPIO pins and electronic components connected to a Raspberry Pi with pure Java code. It removes the complexity of using native libraries and the Java Native Interface (JNI), allowing you to focus on your application logic. In the Java Advent of 2020,&#160;I published&#160;&#8220;Light up [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fffm-api-for-java-on-raspberry-pi.html&amp;action_name=The%20FFM%20API%3A%20How%20OpenJDK%20Changed%20the%20Game%20for%20Native%20Interactions%20%28And%20Made%20Pi4J%20Better%21%29&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html">The FFM API: How OpenJDK Changed the Game for Native Interactions (And Made Pi4J Better!)</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>The <a href="https://www.pi4j.com/" target="_blank" rel="noreferrer noopener">Pi4J project</a> is a Java library that allows you to control the GPIO pins and electronic components connected to a Raspberry Pi with pure Java code. It removes the complexity of using native libraries and the <a href="https://www.baeldung.com/jni" target="_blank" rel="noreferrer noopener">Java Native Interface (JNI)</a>, allowing you to focus on your application logic.</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="250" data-attachment-id="5951" data-permalink="https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html/pi4j-overview" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?fit=2560%2C1069&amp;ssl=1" data-orig-size="2560,1069" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;}" data-image-title="pi4j-overview" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?fit=300%2C125&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?fit=600%2C250&amp;ssl=1" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview.jpg?resize=600%2C250&#038;ssl=1" alt="" class="wp-image-5951" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?resize=1024%2C427&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?resize=300%2C125&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?resize=768%2C321&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?resize=1536%2C641&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?resize=2048%2C855&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?resize=1240%2C518&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?resize=508%2C212&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/pi4j-overview-scaled.jpg?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></figure>



<p>In the Java Advent of 2020,&nbsp;I published&nbsp;&#8220;<a href="https://www.javaadvent.com/2020/12/light-up-your-christmas-lights-with-java-and-raspberry-pi.html" target="_blank" rel="noreferrer noopener">Light up your Christmas lights with Java and Raspberry Pi</a>&#8220;, using Java 11 and Pi4J V1.2. Wow, things have changed a lot: this article uses Java 25 and a snapshot of the soon-to-be-released Pi4J V4!</p>



<p>I became a contributor to Pi4J while working on my book &#8220;<a href="https://webtechie.be/books/" target="_blank" rel="noreferrer noopener">Getting Started with Java on the Raspberry Pi</a>&#8221; around 2020. But even after many years of working on Pi4J&#8217;s code, I get puzzled when I dive deep into its sources. Please help me, do you understand what&#8217;s happening in this piece of code?</p>



<div>
<pre>JNIEXPORT jobject JNICALL Java_com_pi4j_library_gpiod_internal_GpioD_c_1gpiod_1chip_1open
  (JNIEnv* env, jclass javaClass, jstring path) {
    struct gpiod_chip* chip;
    const char* nativeString = (*env)-&gt;GetStringUTFChars(env, path, NULL);
    chip = gpiod_chip_open(nativeString);
    (*env)-&gt;ReleaseStringUTFChars(env, path, nativeString);
    if(chip == NULL) {
      return NULL;
    }
    jclass cls = (*env)-&gt;FindClass(env, "java/lang/Long");
    jmethodID longConstructor = (*env)-&gt;GetMethodID(env, cls, "&amp;lt;init&amp;gt;", "(J)V");
    return (*env)-&gt;NewObject(env, cls, longConstructor, (jlong) (uintptr_t) chip);
}</pre>
</div>



<p>It&#8217;s one of the &#8220;connection points&#8221; between Java and the native libraries to communicate with the GPIO pins. Using JNI and <a href="https://github.com/java-native-access/jna" target="_blank" rel="noreferrer noopener">Java Native Access (JNA)</a>, Docker build environments are used to compile native libraries for use from Java. This complexity makes it easy for the end user to interact with Java, but difficult for the Pi4J developers to maintain and debug.</p>



<p>In this post, I&#8217;ll explain how the Foreign Function &amp; Memory API (FFM API) has revolutionized how Java developers interact with native libraries and memory, significantly simplifying the Pi4J project. This article is based on a talk I gave at the&nbsp;<a href="https://www.youtube.com/watch?v=2BcWWWkb8ac" target="_blank" rel="noreferrer noopener">Devoxx</a> and JFall conferences about the history and evolution of this addition to OpenJDK.</p>



<h2 class="wp-block-heading" id="a-quick-history-lesson">A Quick History Lesson</h2>



<p>My Java journey started 15 years ago when I switched from C# to Java and never looked back. That was right in the middle of Java&#8217;s 30-year history, and I&#8217;ve been following its evolution closely ever since. I even had the chance this year to&nbsp;<a href="https://foojay.io/today/foojay-podcast-71/" target="_blank" rel="noreferrer noopener">talk to James Gosling, the &#8220;Father of Java&#8221;, for the Foojay Podcast</a>.</p>



<p>In recent years, we have seen many evolutions in the Java language and virtual machine, such as improved switch-case, virtual threads, performance improvements, and more. One of the most significant recent developments has been <a href="https://openjdk.org/projects/panama/" target="_blank" rel="noreferrer noopener"><strong>Project Panama</strong></a>&nbsp;and the Foreign Function&nbsp;&amp;&nbsp;Memory&nbsp;(FFM)&nbsp;API that emerged from it.</p>



<h2 class="wp-block-heading" id="foreign-function--memory-ffm-api">Foreign Function &amp; Memory (FFM) API</h2>



<p>The FFM API was officially released in Java 22 as a finalized feature.&nbsp;It represents years of work within Project Panama and has three main goals:</p>



<ol class="wp-block-list">
<li><strong>Memory safety</strong>: Safe access to off-heap memory while maintaining proper cleanup.</li>



<li><strong>Easy interaction</strong>: Simple ways to call native libraries.</li>



<li><strong>High performance</strong>: Matching or exceeding JNI&#8217;s performance.</li>
</ol>



<h3 class="wp-block-heading" id="the-problem-with-jni">The Problem With JNI</h3>



<p>JNI has been around since Java 1.1, and while it works, it has a few critical drawbacks:</p>



<ul class="wp-block-list">
<li><strong>Manual memory management</strong>&nbsp;with a steep learning curve.</li>



<li><strong>Complex implementation</strong>&nbsp;requiring C headers and compilation steps.</li>



<li><strong>Hard to use by design</strong>: As I learned from Simon Ritter, a Sun engineer once said, JNI was deliberately made difficult to discourage people from using it!</li>
</ul>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="229" data-attachment-id="5949" data-permalink="https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html/ffm-quote-simon-ritter" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?fit=1175%2C447&amp;ssl=1" data-orig-size="1175,447" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="ffm-quote-simon-ritter" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?fit=300%2C114&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?fit=600%2C229&amp;ssl=1" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?resize=600%2C229&#038;ssl=1" alt="" class="wp-image-5949" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?resize=1024%2C390&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?resize=300%2C114&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?resize=768%2C292&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?resize=508%2C193&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-quote-simon-ritter.png?w=1175&amp;ssl=1 1175w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></figure>



<p>There were attempts to improve this situation with libraries such as JNA and&nbsp;<a href="https://github.com/jnr" target="_blank" rel="noreferrer noopener">Java Native Runtime (JNR)</a>,&nbsp;but they came with their own overhead and limitations.</p>



<h3 class="wp-block-heading" id="how-the-ffm-api-evolved">How The FFM API Evolved</h3>



<p>The development of the FFM API is a fascinating story that shows how OpenJDK evolves through careful iteration. The project was broken down into separate JEPs (JDK Enhancement Proposals), which started being delivered in Java 14. As you will see, most of the JEPs were incubator or preview features, which can only be used with the&nbsp;<code>--enable-preview</code>&nbsp;flag,&nbsp;as you can&nbsp;<a href="https://docs.azul.com/core/incubator-preview-features" target="_blank" rel="noreferrer noopener">learn in this detailed explanation</a>.</p>



<h4 class="wp-block-heading" id="foreign-memory-access-api">Foreign Memory Access API</h4>



<p>As a first step, the OpenJDK team created a new API to access off-heap memory, enabling safe, efficient access to foreign memory. None of these were finalized, as they got integrated into OpenJDK as incubator features, preparing for something bigger.</p>



<ul class="wp-block-list">
<li><a href="https://openjdk.org/jeps/370" target="_blank" rel="noreferrer noopener">OpenJDK 14: JEP 370: Foreign-Memory Access API (Incubator)</a></li>



<li><a href="https://openjdk.org/jeps/383" target="_blank" rel="noreferrer noopener">OpenJDK 15: JEP 383: Foreign-Memory Access API (Second Incubator)</a></li>



<li><a href="https://openjdk.org/jeps/393" target="_blank" rel="noreferrer noopener">OpenJDK 16: JEP 393: Foreign-Memory Access API (Third Incubator)</a></li>
</ul>



<h4 class="wp-block-heading" id="foreign-linker-api">Foreign Linker API</h4>



<p>In the next step, statically typed pure-Java access to native code got integrated, again as an incubator feature.</p>



<ul class="wp-block-list">
<li><a href="https://openjdk.org/jeps/389" target="_blank" rel="noreferrer noopener">OpenJDK 16: JEP 389: Foreign Linker API (Incubator)</a></li>
</ul>



<h4 class="wp-block-heading" id="foreign-function--memory-api">Foreign Function &amp; Memory API</h4>



<p>Finally, the FFM API was finalized in Java 22. It&#8217;s a combination of the two previous APIs, and it went through incubator and preview phases across multiple Java releases.</p>



<ul class="wp-block-list">
<li><a href="https://openjdk.org/jeps/412" target="_blank" rel="noreferrer noopener">OpenJDK 17: JEP 412: Foreign Function &amp; Memory API (Incubator)</a></li>



<li><a href="https://openjdk.org/jeps/419" target="_blank" rel="noreferrer noopener">OpenJDK 18: JEP 419: Foreign Function &amp; Memory API (Second Incubator)</a></li>



<li><a href="https://openjdk.org/jeps/424" target="_blank" rel="noreferrer noopener">OpenJDK 19: JEP 424: Foreign Function &amp; Memory API (Preview)</a></li>



<li><a href="https://openjdk.org/jeps/434" target="_blank" rel="noreferrer noopener">OpenJDK 20: JEP 434: Foreign Function &amp; Memory API (Second Preview)</a></li>



<li><a href="https://openjdk.org/jeps/442" target="_blank" rel="noreferrer noopener">OpenJDK 21: JEP 442: Foreign Function &amp; Memory API (Third Preview)</a></li>



<li><a href="https://openjdk.org/jeps/454" target="_blank" rel="noreferrer noopener">OpenJDK 22: JEP 454: Foreign Function &amp; Memory API</a></li>
</ul>



<p>This iterative approach allowed the OpenJDK team to gather community feedback and ensure the API was stable,&nbsp;performant,&nbsp;and truly useful.</p>



<h3 class="wp-block-heading" id="simple-code-examples">Simple Code Examples</h3>



<p>Let me show you how much simpler things have become with a few examples.</p>



<h4 class="wp-block-heading" id="accessing-memory-directly">Accessing Memory Directly</h4>



<p>Here&#8217;s a basic example of accessing memory directly:</p>



<div>
<pre>void main() {
    // Open a confined Arena that manages off-heap memory
    // and will release it automatically.
    try (Arena arena = Arena.ofConfined()) {
        // Allocate 5 ints in the arena
        MemorySegment segment = arena
           .allocate(ValueLayout.JAVA_INT, 5);

        // Fill the segment with random values for each int.
        System.out.print("Setting values: ");
        for (int i = 0; i &lt; 5; i++) {
            int randomValue = new Random().nextInt(100);
            segment.setAtIndex(ValueLayout.JAVA_INT, 
               i, randomValue);
            System.out.print(randomValue + " ");
        }
        System.out.println("");

        // Print the values back from memory.
        System.out.print("Reading values: ");
        for (int i = 0; i &lt; 5; i++) {
            System.out.print(segment
              .getAtIndex(ValueLayout.JAVA_INT, i) + " ");
        }
        System.out.println("");
    }
}</pre>
</div>



<p>Notice a few things about this code:</p>



<ul class="wp-block-list">
<li>I execute it with Java 25, which means I can use the new simplified main method introduced by <a href="https://openjdk.org/jeps/512" target="_blank" rel="noreferrer noopener">JEP 512: Compact Source Files and Instance Main Methods</a>. As a result, I don&#8217;t need to use a class and package, and a lot of the&nbsp;<code>main</code>&nbsp;method &#8220;clutter&#8221; is no longer needed in a simple example like this.</li>



<li>The&nbsp;<code>Arena</code>&nbsp;manages memory cleanup automatically in the&nbsp;<code>try</code>&nbsp;block.</li>



<li>The&nbsp;<code>MemorySegment</code>&nbsp;is a simple wrapper around a native memory address.</li>



<li>No manual memory management is needed.</li>
</ul>



<div>
<pre>$ java ArenaDemo.java
Setting values: 16 7 27 50 80 
Reading values: 16 7 27 50 80</pre>
</div>



<p>A more extended example is&nbsp;<a href="https://webtechie.be/code/ffm/FFMMemoryManagement.java" target="_blank" rel="noreferrer noopener">available here in&nbsp;<code>FFMMemoryManagement.java</code></a>,&nbsp;with a&nbsp;<a href="https://webtechie.be/code/ffm/Java11MemoryManagement.java" target="_blank" rel="noreferrer noopener">Java 11 example here&nbsp;<code>Java11MemoryManagement.java</code></a>&nbsp;illustrating how much more complex this was before the FFM API.&nbsp;The Java 11 version&nbsp;<a href="https://www.jbang.dev/">must be executed with JBang</a>&nbsp;as described in the comments,&nbsp;to force the use of Java 11.</p>



<h4 class="wp-block-heading" id="calling-native-functions">Calling Native Functions</h4>



<p>To illustrate how to call a native library function,&nbsp;we&#8217;ll make the most complex&nbsp;<code>String.length()</code>&nbsp;implementation&nbsp;<img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;A perfect example of how this can be done with FFM API within one simple-to-read method:</p>



<div>
<pre>void main() throws Throwable {
    // The text we will use in the demo.
    var text = "Hello JVM Advent!";

    // Obtain a Linker that knows how to call
    // native (C) functions on this platform.
    Linker linker = Linker.nativeLinker();

    // Create a SymbolLookup that can find symbols
    // (like C functions) in the default native libraries.
    SymbolLookup lookup = linker.defaultLookup();

    // Create a MethodHandle to call the native C function
    MethodHandle strlen = linker.downcallHandle(
        // Look up the address of the `strlen` symbol,
        // or throw if it isn't found.
        lookup.find("strlen").orElseThrow(),
        // Describe the C function:
        // - returns long
        // - takes one pointer (address) argument
        FunctionDescriptor.of(ValueLayout.JAVA_LONG,
          ValueLayout.ADDRESS)
    );

    // Open a confined Arena that manages off-heap memory
    // and will release it automatically.
    try (Arena arena = Arena.ofConfined()) {
        // Allocate native memory for a C-style string
        // and copy "Hello World!" into it.
        MemorySegment str = arena.allocateFrom(text);

        // Call the native `strlen` function 
        // via the MethodHandle,
        // passing the string's memory address,
        // and cast the result to a long.
        long length = (long) strlen.invoke(str);

        // Print the result.
        System.out.println("Length with native library: " 
          + length);
    }

    System.out.println("Length with String.length(): " 
      + text.length());
}</pre>
</div>



<p>No JNI headers,&nbsp;no C-compilation,&nbsp;just pure Java code!</p>



<div>
<pre>$ java LinkerDemo.java

Length with native library: 17
Length with String.length(): 17</pre>
</div>



<p>Also, for this example, you can find a more extended example&nbsp;<a href="https://webtechie.be/code/ffm/FFMNativeCalls.java" target="_blank" rel="noreferrer noopener">here in&nbsp;<code>FFMNativeCalls.java</code></a>,&nbsp;with a&nbsp;<a href="https://webtechie.be/code/ffm/Java11NativeCalls.java" target="_blank" rel="noreferrer noopener">Java 11 in&nbsp;<code>Java11NativeCalls.java</code></a>.</p>



<h4 class="wp-block-heading" id="performance-comparison-example">Performance Comparison Example</h4>



<p>I like demos that have a visual output. So I created a simple benchmark that generates moving gradients, which involves many memory writes. In both Java 11 and 25, I try to refresh the gradient every 5 milliseconds.</p>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-gradient-demo.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="390" data-attachment-id="5950" data-permalink="https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html/ffm-gradient-demo" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-gradient-demo.png?fit=903%2C587&amp;ssl=1" data-orig-size="903,587" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="ffm-gradient-demo" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-gradient-demo.png?fit=300%2C195&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-gradient-demo.png?fit=600%2C390&amp;ssl=1" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-gradient-demo.png?resize=600%2C390&#038;ssl=1" alt="" class="wp-image-5950" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-gradient-demo.png?w=903&amp;ssl=1 903w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-gradient-demo.png?resize=300%2C195&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-gradient-demo.png?resize=768%2C499&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/ffm-gradient-demo.png?resize=508%2C330&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></figure>



<p>The results speak for themselves&nbsp;(tested on a MaOS M2 with Azul Zulu 25):</p>



<pre class="wp-block-preformatted">$ jbang Java11PixelBuffer.java
[jbang] Building jar for Java11PixelBuffer.java...
Interval: 57 - Generated 250000 pixels
Interval: 56 - Generated 250000 pixels

$ java FFMPixelBuffer.java
Interval: 5 - Generated 250000 pixels
Interval: 5 - Generated 250000 pixels</pre>



<ul class="wp-block-list">
<li><strong><a href="https://webtechie.be/code/ffm/Java11PixelBuffer.java" target="_blank" rel="noreferrer noopener">Java 11 approach</a></strong>&nbsp;using&nbsp;<code>BufferedImage</code>&nbsp;and arrays: ~50-60 milliseconds per frame.</li>



<li><strong><a href="https://webtechie.be/code/ffm/FFMPixelBuffer.java" target="_blank" rel="noreferrer noopener">FFM API approach</a></strong>&nbsp;with direct memory access: ~5 milliseconds per frame.</li>
</ul>



<p>That&#8217;s up to <strong>11x performance improvement</strong>&nbsp;just by avoiding the overhead of Java object management!</p>



<h2 class="wp-block-heading" id="why-the-ffm-api-matters-for-raspberry-pi-projects">Why the FFM API Matters for Raspberry Pi Projects</h2>



<p>Let me first answer a more general question I get asked a lot: &#8220;<strong>Why Java on a Raspberry Pi?</strong>&#8221; The answer is simple: Java is the language that allows me to do everything. I can build user interfaces with JavaFX, make API calls to services, and leverage the extensive library ecosystem. When I started experimenting with Java on Raspberry Pi over five years ago, I didn&#8217;t want to learn a new language. I wanted to learn how to use and interact with electronic components, using the tools I already knew and loved.</p>



<p>This is where the Pi4J project comes in: it&#8217;s a Java library that lets you control the GPIO pins and electronic components connected to a Raspberry Pi. But here&#8217;s the catch: this library has always relied on native C/C++ code to communicate with the hardware. Until now, that meant dealing with the complexity of JNI and JNA.</p>



<h3 class="wp-block-heading" id="pi4j-architecture">Pi4J Architecture</h3>



<p><a href="https://www.pi4j.com/architecture/" target="_blank" rel="noreferrer noopener">Pi4J uses a plugin architecture</a> in which different &#8220;providers&#8221; handle communication with GPIO pins. Previously, these providers relied on native libraries and JNI/JNA, which meant:</p>



<ul class="wp-block-list">
<li>Complex multi-layered code with up to 5 levels before executing GPIO operations.</li>



<li>Need for Docker builds to compile native libraries.</li>



<li>Cryptic JNI header generation and wizard-like C code.</li>
</ul>



<p>These plugins get loaded dynamically at runtime,&nbsp;in the&nbsp;&#8220;black bar&#8221;&nbsp;in this architecture diagram:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="391" data-attachment-id="5952" data-permalink="https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html/pi4j-2-0-architecture-architecture-diagram" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?fit=2560%2C1671&amp;ssl=1" data-orig-size="2560,1671" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Pi4J 2.0 &#8211; Architecture &#8211; Architecture Diagram" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?fit=300%2C196&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?fit=600%2C391&amp;ssl=1" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram.jpeg?resize=600%2C391&#038;ssl=1" alt="" class="wp-image-5952" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?resize=1024%2C668&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?resize=300%2C196&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?resize=768%2C501&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?resize=1536%2C1002&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?resize=2048%2C1337&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?resize=1240%2C809&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?resize=508%2C332&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Pi4J-2.0-Architecture-Architecture-Diagram-scaled.jpeg?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></figure>



<h3 class="wp-block-heading" id="the-ffm-transformation">The FFM Transformation</h3>



<p>Pi4J now has an almost-ready FFM-based provider, which will be available in V4. The improvements are dramatic:</p>



<ul class="wp-block-list">
<li>Performance Benchmarks
<ul class="wp-block-list">
<li>Getting GPIO input state:&nbsp;<strong>10x faster!</strong>.</li>



<li>SPI communication: Significant improvement, though less dramatic.</li>
</ul>
</li>



<li>Code Simplification
<ul class="wp-block-list">
<li>Direct Java-to-kernel communication.</li>



<li>No more complex JNI layers.</li>



<li>Readable, maintainable Java code.</li>



<li>No need for native library compilation with a complex Docker build process.</li>
</ul>
</li>
</ul>



<h3 class="wp-block-heading" id="a-community-success-story">A Community Success Story</h3>



<p>What makes this even better is that the FFM implementation came from the community.&nbsp;<a href="https://github.com/DigitalSmile" target="_blank" rel="noreferrer noopener">Nick Gritsenko (aka @DigitalSmile)</a> had already created a Java 22 library for GPIO interaction using FFM.&nbsp;When I discovered his work,&nbsp;I asked if we could use it.&nbsp;But even better,&nbsp;he contributed it directly to Pi4J himself and made it fit perfectly into the plugin architecture!&nbsp;This resulted in a&nbsp;<a href="https://github.com/Pi4J/pi4j/pull/458" target="_blank" rel="noreferrer noopener">major pull request</a>&nbsp;that&#8217;s now merged into the Pi4J V4 snapshot.</p>



<p>I&#8217;m very proud that this happens again and again. In the past,&nbsp;<a href="https://www.pi4j.com/blog/2024/20240318_interview_alexander_liggesmeyer/" target="_blank" rel="noreferrer noopener">Alexander Liggesmeyer added support for the Raspberry Pi 5</a> with a new plugin.&nbsp;And at this moment,&nbsp;<a href="https://github.com/stefanhaustein" target="_blank" rel="noreferrer noopener">Stefan Haustein</a>,&nbsp;<a href="https://github.com/mores" target="_blank" rel="noreferrer noopener">Stephen More</a>,&nbsp;<a href="https://github.com/taartspi" target="_blank" rel="noreferrer noopener">Tom Aarts</a>,&nbsp;and others are actively working on a new&nbsp;<a href="https://github.com/pi4j/pi4j-drivers/" target="_blank" rel="noreferrer noopener">Pi4J dr</a>ivers library&nbsp;and example implementations to make it much easier for everyone to create applications that interact with more complex electronic components, such as joysticks,&nbsp;LCD screens,&nbsp;LED strips,&nbsp;and more&#8230;</p>



<h3 class="wp-block-heading" id="beyond-raspberry-pi">Beyond Raspberry Pi</h3>



<p>The FFM-based implementation opens up exciting possibilities.&nbsp;Since it&#8217;s based on standard Linux kernel methods available in Debian,&nbsp;we believe Pi4J could potentially work on:</p>



<ul class="wp-block-list">
<li>Orange Pi boards and other Linux-based SBCs (Single Board Computers).</li>



<li>RISC-V processors running Debian.</li>
</ul>



<p>I&#8217;m looking forward to experimenting with these different hardware platforms!&nbsp;Maybe you&#8217;ll read more about that in next year&#8217;s JVM Advent&#8230;</p>



<h3 class="wp-block-heading" id="pi4j-examples-using-the-ffm-api">Pi4J Examples Using the FFM API</h3>



<p>I often demo with a CrowPi&nbsp;–&nbsp;a neat kit with a Raspberry Pi and pre-wired components in a single box.&nbsp;It&#8217;s great for learning because you can&#8217;t wire things incorrectly!</p>



<p>Here&#8217;s a simplified example using JBang to blink an RGB LED, using a snapshot build of Pi4J V4. Once this version is released, you can remove the line with <code>//REPOS</code>&nbsp;and update the version.</p>



<div>
<pre>/// usr/bin/env jbang "$0" "$@" ; exit $?

//REPOS mavencentral,pi4j-snapshots=https://oss.sonatype.org/content/repositories/snapshots
//DEPS com.pi4j:pi4j-core:4.0.0-SNAPSHOT
//DEPS com.pi4j:pi4j-plugin-linuxfs:4.0.0-SNAPSHOT

import com.pi4j.Pi4J;
import com.pi4j.io.gpio.digital.*;

/**
 * Execute with `jbang Pi4JExample.java`
 */
void main() throws InterruptedException {
    var pi4j = Pi4J.newAutoContext();

    var red = pi4j.digitalOutput().create(17);
    var green = pi4j.digitalOutput().create(27);
    var blue = pi4j.digitalOutput().create(22);

    // Blink pattern
    for (int i = 0; i &lt; 10; i++) {
        red.high();
        Thread.sleep(500);
        red.low();
        green.high();
        Thread.sleep(500);
        green.low();
        blue.high();
        Thread.sleep(500);
        blue.low();
    }
}</pre>
</div>



<p>More examples like this,&nbsp;which can be executed with JBang,&nbsp;are available in the&nbsp;<a href="https://github.com/Pi4J/pi4j-jbang" target="_blank" rel="noreferrer noopener">Pi4J JBang repository</a>.</p>



<h2 class="wp-block-heading" id="important-notes-">Important Notes</h2>



<p>While working on the presentation about the FFM API and this article, I noticed a few points worth mentioning.</p>



<ul class="wp-block-list">
<li><strong>Memory Safety Warning</strong>: When using FFM, you&#8217;re stepping outside the &#8220;safe JVM-managed garbage collector&#8221;. You&#8217;ll see a warning about enabling native access with a few of the examples from this article. This is intentional as the OpenJDK team wants to ensure developers understand they&#8217;re working with potentially dangerous operations. Use the <code>--enable-native-access</code> flag to acknowledge this and suppress the warning. This may change in future versions of OpenJDK.</li>



<li><strong>JEPs Are Worth Reading</strong>: If you&#8217;re curious about how Java evolves, I highly recommend&nbsp;<a href="https://openjdk.org/jeps/0" target="_blank" rel="noreferrer noopener">reading the JDK Enhancement Proposals</a>. They&#8217;re not just technical specifications! They&#8217;re well-written documents that explain the thinking behind design decisions, include examples, and provide deep insights from the architects of Java. Similarly,&nbsp;<a href="https://openjdk.org/projects/" target="_blank" rel="noreferrer noopener">read more about the OpenJDK projects</a>, for instance, by subscribing to their mailing list to follow what is happening within such a project.</li>



<li><strong>Keep Up With Java Releases</strong>: Java has a six-month release cycle, and every release is a good one! Each brings many bug fixes, improvements, and evolutions (from projects and JEPs). If you can update your systems, especially your development systems, you should! The FFM API was introduced in Java 22, but I learned at Devoxx that Java 24 brought significant performance improvements to the FFM implementation, without any API changes! This shows that the OpenJDK team continues to optimize and improve existing features.</li>
</ul>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p>The FFM API has been a significant improvement for developers working with native code in recent years. It removes the complexity of JNI while maintaining, and even improving, performance. It opens new possibilities for Java in embedded systems and hardware interfacing. It will also drive closer integration of Artificial Intelligence (AI), Machine Learning (ML), and Large-Language Model (LLM) development with Java.</p>



<p>Feel free to experiment and contribute.&nbsp;OpenJDK and other open source projects,&nbsp;like Pi4J,&nbsp;thrive on community involvement!</p>



<p>If you&#8217;re interested in Java on Raspberry Pi,&nbsp;check out:</p>



<ul class="wp-block-list">
<li>The&nbsp;<a href="https://www.pi4j.com/" target="_blank" rel="noreferrer noopener">Pi4J website</a>.</li>



<li>My blog posts on&nbsp;<a href="https://webtechie.be/" target="_blank" rel="noreferrer noopener">webtechie.be</a>&nbsp;and&nbsp;<a href="https://foojay.io/today/author/frankdelporte/" target="_blank" rel="noreferrer noopener">Foojay.io</a>.</li>



<li>My ebook &#8220;<a href="https://webtechie.be/books/" target="_blank" rel="noreferrer noopener">Getting Started with Java on the Raspberry Pi</a>&#8220;, which I keep updating with new Java versions, Pi4J improvements, etc.</li>
</ul>



<figure class="wp-block-image size-medium"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="232" height="300" data-attachment-id="5954" data-permalink="https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html/cover-2" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?fit=1978%2C2560&amp;ssl=1" data-orig-size="1978,2560" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;cover&quot;,&quot;orientation&quot;:&quot;1&quot;}" data-image-title="cover" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?fit=232%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?fit=600%2C777&amp;ssl=1" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25.jpg?resize=232%2C300&#038;ssl=1" alt="" class="wp-image-5954" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?resize=232%2C300&amp;ssl=1 232w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?resize=791%2C1024&amp;ssl=1 791w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?resize=768%2C994&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?resize=1187%2C1536&amp;ssl=1 1187w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?resize=1583%2C2048&amp;ssl=1 1583w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?resize=1240%2C1605&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?resize=508%2C657&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?w=1978&amp;ssl=1 1978w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/cover_2025_java25-scaled.jpg?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 232px) 100vw, 232px" /></a></figure>



<p><strong>Remember:&nbsp;We must thank OpenJDK and all its contributors for amazing features like the Garbage Collector,&nbsp;JIT compilation,&nbsp;Lambdas,&nbsp;Virtual Threads,&nbsp;and now the FFM API.&nbsp;Java keeps getting better,&nbsp;and that&#8217;s worth celebrating!</strong></p>
<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fffm-api-for-java-on-raspberry-pi.html&amp;action_name=The%20FFM%20API%3A%20How%20OpenJDK%20Changed%20the%20Game%20for%20Native%20Interactions%20%28And%20Made%20Pi4J%20Better%21%29&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html">The FFM API: How OpenJDK Changed the Game for Native Interactions (And Made Pi4J Better!)</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/ffm-api-for-java-on-raspberry-pi.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5947</post-id>	</item>
		<item>
		<title>Tinkering with a &#8220;hands-off&#8221; agent</title>
		<link>https://www.javaadvent.com/2025/12/tinkering-with-a-hands-off-agent.html</link>
					<comments>https://www.javaadvent.com/2025/12/tinkering-with-a-hands-off-agent.html#respond</comments>
		
		<dc:creator><![CDATA[Tom Cools]]></dc:creator>
		<pubDate>Mon, 15 Dec 2025 03:03:31 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[Java Advent]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6080</guid>

					<description><![CDATA[<p>It’s great to write another entry for Java Advent this year! Last year, I wrote about how you can use Timefold Solver, an optimization library written in Java, to solve planning problems such as employee scheduling or, in true holiday spirit, optimizing Santa’s travel route. This year, as the world is now all going “agentic”, [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Ftinkering-with-a-hands-off-agent.html&amp;action_name=Tinkering%20with%20a%20%26%238220%3Bhands-off%26%238221%3B%20agent&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/tinkering-with-a-hands-off-agent.html">Tinkering with a &#8220;hands-off&#8221; agent</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p><span style="font-weight: 400">It’s great to write another entry for Java Advent this year! Last year, I wrote about how you can use <a href="https://github.com/TimefoldAI/timefold-solver">Timefold Solver</a>, an optimization library written in Java, to solve planning problems such as employee scheduling or, in true holiday spirit, optimizing Santa’s travel route.</span></p>



<p><span style="font-weight: 400">This year, as the world is now all going “agentic”, I was curious to learn more about agents and decided to start tinkering myself. I started a pet project with a basic idea:</span></p>



<p><span style="font-weight: 400"><em>“Could I generate a fully working optimization application purely from a simple problem description?”</em></span></p>





<p><span style="font-weight: 400">Here’s where that curiosity led.</span></p>



<h3 class="wp-block-heading"><b>Basics: Framework</b></h3>



<p><span style="font-weight: 400">I like to approach pet projects in a way that they incorporate some ideas/frameworks/concepts I’ve played with before, while still challenging me to learn some new things.</span></p>



<p><span style="font-weight: 400">There was no doubt in my mind about using Java, so I started looking for Java compatible agentic frameworks. I ran into <a href="https://docs.langchain4j.dev/tutorials/agents/">Langchain4J Agentic</a>, the <a href="https://github.com/google/adk-java">Agent Development Kit</a></span><span style="font-weight: 400"> and a few others. Since I had the most experience with Langchain4J already, that became my framework of choice.</span></p>



<p><span style="font-weight: 400">I scoured through the docs to get a sense of what building an agent was like and then just got to work. I just needed 1 more thing: a benchmark / test scenario.</span></p>



<h3 class="wp-block-heading"><b>Let’s Sing!</b></h3>



<p><span style="font-weight: 400">I was looking for an example scenario. Something I would love to optimize that was also in my typical fashion:</span></p>



<ul class="wp-block-list">
<li><i><span style="font-weight: 400">Silly (can’t be too serious when messing around with technology).</span></i></li>



<li><i><span style="font-weight: 400">Relatively simple (so everyone could understand it).</span></i></li>



<li><i><span style="font-weight: 400">Somewhat practical.</span></i></li>
</ul>



<p><span style="font-weight: 400">After talking to <a href="https://www.linkedin.com/in/wouter-bauweraerts-938689108/">Wouter Bauweraerts</a> from the Belgian Java Community (hi Wouter <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" />), I settled on a karaoke bar scheduler. Yes. Karaoke.</span></p>



<p><span style="font-weight: 400">Here’s the prompt I wanted the agent to digest:</span></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>I run a Karaoke bar. I have 2 stages and I want to schedule songs following these rules:<br /><br />&#8211; Avoid having the same singer 2 times in a row<br />&#8211; Avoid having songs from the same artist 2 times in a row<br /><br />A singer can sign up with a song. The duration of the performance depends on the lenght of the song.<br />I want to be able to create a clear clock of when the next song will start, so include timing.</strong></p>
</blockquote>



<p><span style="font-weight: 400">Time to get to work on the agent!</span></p>



<p><span style="font-weight: 400">(</span><i><span style="font-weight: 400">Note: All example prompts shown here are abbreviated. The real ones are as long as a 10 year old’s wishlist for Santa.</span></i><span style="font-weight: 400">)</span></p>



<h3 class="wp-block-heading"><b>Agent v0.1: Just an LLM prompt in disguise</b></h3>



<p><span style="font-weight: 400">The first version of my agent was pretty simple. Just take the input prompt, add a bit of information in the <code>@SystemMessage</code> and <code>@UserMessage</code> to steer the result a bit and hope for the best. After all, with all the hype, this was surely supposed to work? I even scoped it to only write the Timefold related code, not bothering with any sort of user interface.</span></p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler">
<div class="wp-block-embed__wrapper">https://gist.github.com/TomCools/c227ab745e1ae5a9c6f145cfe7e680e1</div>
</figure>



<p><span style="font-weight: 400">Then I needed to choose the underlying LLM model. Langchain4j makes it trivial to change the LLM the Agent uses. As I was still in the exploratory phase, I decided to use a locally hosted LLM, qwen3:8b, </span><i><span style="font-weight: 400">which I run on my macbook using Ollama.</span></i></p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler">
<div class="wp-block-embed__wrapper">https://gist.github.com/TomCools/625c5c2c88b3e6a046f10bc3fd4dd20a</div>
</figure>



<p><span style="font-weight: 400">As I was patiently waiting for my brand new agent to give its very first result, I was already dreaming of what awesome result would come out of that </span><span style="font-weight: 400">writeCode</span><span style="font-weight: 400"> method. As some of you might expect… it’s not that simple, unless the end result you wanted is a dumpster fire of mangled code.</span></p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler">
<div class="wp-block-embed__wrapper">https://gist.github.com/TomCools/c424652a075624b879c57e61174b7536</div>
</figure>



<p><span style="font-weight: 400">Hey, at least it did output </span><i><span style="font-weight: 400">something</span></i><span style="font-weight: 400">! So let’s see how we can improve this.</span></p>



<h3 class="wp-block-heading"><b>Agent v0.2: Limiting scope even more</b></h3>



<p><span style="font-weight: 400">I felt I was asking too much from my single agent (or maybe this is just my human bias talking), so I decided to split it up into 2 separate coding agents: 1 for the Java domain code, 1 for the Java constraint stream code.</span></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><span style="font-weight: 400">Constraint Streams is the syntax you use with Timefold to write constraints, such as the “Avoid having the same singer 2 times in a row” constraint.</span></p>
</blockquote>



<p><span style="font-weight: 400">The result was slightly better. The agents were more narrowly focussed and seemed able to keep it together. The output however is still nowhere near being workable. It was at least starting to look a bit more like workable Java code.</span></p>



<h3 class="wp-block-heading"><b>Agent v0.3: Better base model</b></h3>



<p><span style="font-weight: 400">Agents are only as good as their underlying model. Given that I’ve had more success with Claude and ChatGPT when asking similar coding questions, I felt it was time to replace the model used by my agent to <em><strong>Claude Sonnet 4.5 (20250929)</strong></em>.</span></p>



<p><span style="font-weight: 400">With this change in model I now have to start paying for my tokens. I started proceeding a bit more carefully and sometimes moved back to Ollama just for basic runs. <strong>Langchain4j makes this a 1 line code change <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f497.png" alt="💗" class="wp-smiley" style="height: 1em; max-height: 1em;" />.</strong></span></p>



<p><span style="font-weight: 400">Changing the model immediately brought my agent to life. The returned code at least looked like it was written by someone who had seen Java before. However, copy pasting the code into my IDE just showed how glaringly wrong it actually was. It’s the sort of code that would never pass a code-review… <strong>oh wait! </strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /></span></p>



<h3 class="wp-block-heading"><b>Agent v0.4: Review and Feedback</b></h3>



<p><span style="font-weight: 400">I had seen a subject header on the agentic langchain documentation about sub-agents and agentic workflows. So I decided to try it. Instead of a single “in and out” workflow, I added a Review agent, which reviews the written code, provides feedback and then puts the coding agent to work again with that feedback.</span></p>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image-3.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="312" data-attachment-id="6118" data-permalink="https://www.javaadvent.com/2025/12/tinkering-with-a-hands-off-agent.html/image-17" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image-3.png?fit=803%2C418&amp;ssl=1" data-orig-size="803,418" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image-3.png?fit=300%2C156&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image-3.png?fit=600%2C312&amp;ssl=1" class="wp-image-6118" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image-3.png?resize=600%2C312&#038;ssl=1" alt="" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image-3.png?w=803&amp;ssl=1 803w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image-3.png?resize=300%2C156&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image-3.png?resize=768%2C400&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image-3.png?resize=508%2C264&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a>
<figcaption class="wp-element-caption"><em>Agentic coder and code reviewer work in a loop. Instead of continuing after X reviews, you could also let the reviewer <strong>score</strong> the solution and continue once a certain score has been reached.</em></figcaption>
</figure>



<p><span style="font-weight: 400">The cool thing is that we can actually give these agents tools to work with… and what better tool to give an agent which needs to write </span><i><span style="font-weight: 400">compilable</span></i><span style="font-weight: 400"> code than an actual Java Compiler.</span></p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler">
<div class="wp-block-embed__wrapper">https://gist.github.com/TomCools/72342a51c40b92b5a5dcd4ad81b02f1b</div>
</figure>



<p><span style="font-weight: 400">The resulting code was pretty ok and usually compiled just fine. But it did make some very obvious mistakes from a Timefold perspective (e.g. adding both <code>@ShadowVariable</code> and <code>@PlanningVariable</code> to a single field).</span></p>



<h3 class="wp-block-heading"><b>Agent v0.5: Avoiding the same mistakes (easyRAG)</b></h3>



<p><span style="font-weight: 400">To avoid these similar mistakes, I tried to give the agents a bit more information to work with by using RAG (Retrieval Augmented Generation). As I just wanted a simple solution here, I used the <a href="https://docs.langchain4j.dev/tutorials/rag#easy-rag">easyRAG</a> features of Langchain4j. This allows me to just point to a directory which had some documentation included so the agent would be able to use the contained documents.</span></p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler">
<div class="wp-block-embed__wrapper">https://gist.github.com/TomCools/1159f3c86fc8f91ec62e5da3fc4be63e</div>
</figure>



<p><span style="font-weight: 400">We have a lot of documentation for Timefold Solver, generated to pdf, it’s a good 400 pages. Not all of that is relevant for every agent, so I added some simplified documentation in different subdirectories for each of agents and then added those to the agent with a </span><i><span style="font-weight: 400">ContentRetriever</span></i><span style="font-weight: 400">.</span></p>



<p><span style="font-weight: 400">I ran it a couple of times to see the impact these documents had. Whenever a new mistake seemed to become prevalent, I added more information to the relevant documents to improve the result.</span></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><span style="font-weight: 400">This also led me to make a couple updates to our actual documentation. Turns out that if an LLM can’t understand your documentation humans will struggle with it as well.</span></p>
</blockquote>



<p><span style="font-weight: 400">At this point, it was getting slightly painful to see my API credits decline. With the subagents in a loop, this was burning through a lot of tokens, so I tried to figure out: <em>how can I get a better result, without burning through so many tokens?</em></span></p>



<h3 class="wp-block-heading"><b>Agent v0.5: (Plant)UML to the rescue!</b></h3>



<p>Upon inspection, I noticed a<span style="font-weight: 400"> lot of the comments by the review agents were not about code details at all, but about the structure of the classes and the placement of the annotations Timefold Solver needs. When solving these problems myself, these are all things I’d add to a diagram before even opening my editor… so why not do the same here?</span></p>



<p><span style="font-weight: 400">Instead of skipping directly to the code, I introduced 1 new agent. This “modelling” agent would “model” how the solution should look and format it in <a href="https://plantuml.com/">PlantUML</a>. I gave this agent the documentation we have for modelling problems with Timefold Solver and explicitly asked it to add some details about design choices it made to the PlantUML diagram.</span></p>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="505" data-attachment-id="6087" data-permalink="https://www.javaadvent.com/2025/12/tinkering-with-a-hands-off-agent.html/image-13" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image.png?fit=969%2C816&amp;ssl=1" data-orig-size="969,816" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image.png?fit=300%2C253&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image.png?fit=600%2C505&amp;ssl=1" class="wp-image-6087" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image.png?resize=600%2C505&#038;ssl=1" alt="" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image.png?w=969&amp;ssl=1 969w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image.png?resize=300%2C253&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image.png?resize=768%2C647&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/image.png?resize=508%2C428&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a>
<figcaption class="wp-element-caption"><em>Generated Diagram, notes contain the design decisions</em>. <em>It did a pretty decent job here!</em></figcaption>
</figure>



<p><span style="font-weight: 400">Now I not only made “<em>thinking about the structure of our solution</em>” way more explicit, I also have a reviewable asset that is passed between agents: the PlantUML diagram. As it turns out, <strong>PlantUML is an excellent format if you want to pass information about the structure of your classes between agents without giving it the entire codebase.</strong></span></p>



<p><span style="font-weight: 400">I adjusted the coding agents to accept the PlantUML diagram as input, so they did not get overloaded with residual information about the problem statement.</span></p>



<p><span style="font-weight: 400">This massively improved consistency and reduced hallucinations. It now worked well enough that I ventured into the much harder part.</span></p>



<h3 class="wp-block-heading"><b>Agent v0.6: UI and Backend</b></h3>



<p><span style="font-weight: 400">Having a nice scheduling core is nice, hosting a Karaoke with hardcoded Java will probably not be very conductive for a great evening. <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> So we still need a UI.</span></p>



<p><span style="font-weight: 400">I don’t think that having GenAI scaffold an entire project is a good move here, so I looked into tools that would help me create a project structure.</span></p>



<p><span style="font-weight: 400">In the Java world, one potential solution is to rely on <a href="https://www.jhipster.tech/">JHipster</a> and more specifically the </span><b>JHipster Domain Language</b><span style="font-weight: 400"> (JDL) </span><b>. JDL</b><span style="font-weight: 400"> allows you to describe a project: entities, relationships and some other properties.</span></p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler">
<div class="wp-block-embed__wrapper">https://gist.github.com/TomCools/bdda56e9826fcf37779a781fda05c1e5</div>
</figure>



<p><span style="font-weight: 400">These are all things we can let an LLM fill in, based on the PlantUML and the problem description. Then you can create a full application with a single command (<code>jhipster import-jdl model.jdl</code>), which I wrapped into a Tool so I can have an agent create the JDL and generate a simple application for my problem.</span></p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="266" data-attachment-id="6093" data-permalink="https://www.javaadvent.com/2025/12/tinkering-with-a-hands-off-agent.html/screenshot-2025-11-27-at-10-50-38" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?fit=2538%2C1126&amp;ssl=1" data-orig-size="2538,1126" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-11-27 at 10.50.38" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?fit=300%2C133&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?fit=600%2C266&amp;ssl=1" class="wp-image-6093" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?resize=600%2C266&#038;ssl=1" alt="" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?resize=1024%2C454&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?resize=300%2C133&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?resize=768%2C341&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?resize=1536%2C681&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?resize=2048%2C909&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?resize=1240%2C550&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?resize=508%2C225&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.38.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></figure>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="266" data-attachment-id="6094" data-permalink="https://www.javaadvent.com/2025/12/tinkering-with-a-hands-off-agent.html/screenshot-2025-11-27-at-10-50-53-2" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?fit=2538%2C1126&amp;ssl=1" data-orig-size="2538,1126" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-11-27 at 10.50.53" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?fit=300%2C133&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?fit=600%2C266&amp;ssl=1" class="wp-image-6094" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?resize=600%2C266&#038;ssl=1" alt="" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?resize=1024%2C454&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?resize=300%2C133&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?resize=768%2C341&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?resize=1536%2C681&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?resize=2048%2C909&amp;ssl=1 2048w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?resize=1240%2C550&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?resize=508%2C225&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/Screenshot-2025-11-27-at-10.50.53-1.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></figure>



<p><span style="font-weight: 400">And now we have a fully working application (Angular + Spring Boot), including login screen, metrics and CRUD pages for every entity involved entity, all at a fraction of the token cost we’d have if we let GenAI do everything.</span></p>



<h3 class="wp-block-heading"><b>Conclusion</b></h3>



<p><span style="font-weight: 400">While I still have to resolve some gaps, like actually connecting Timefold Solver to the rest of the JHipster generated code and creating a better scheduling UI, I think I’ll stop this pet project here for now. <strong>For me, the real value of these tinkering projects isn’t what I end up building. It’s everything I learn while fumbling toward it.</strong></span></p>



<p><span style="font-weight: 400">Here are my main lessons.</span></p>



<ul class="wp-block-list">
<li><span style="font-weight: 400">Having </span><b><i>inspectable intermediate formats</i></b><span style="font-weight: 400"> makes the development process much easier and the end result way more auditable.</span></li>



<li><span style="font-weight: 400">At the moment of writing, it’s still much better to have a </span><b>human in the loop.</b><span style="font-weight: 400"> Even tiny mistakes made in one of the first steps can lead to a failed result in the end. Being able to intervene and correct makes the whole thing so much easier than this “fully autonomous” thing I was trying.</span></li>



<li><b><i>Better models = better results</i></b><span style="font-weight: 400">. We may not want to spend the money, but it did change my results from absolute garbage to something that quite often works. I was very happy with Langchain4J in this regard, I only needed to change 1 line of code to try a different model.</span></li>



<li><b>Learning is still so reinvigorating</b><span style="font-weight: 400">! It’s been a while since I’ve felt a big jolt of love for a pet project but this one struck like a lightning bolt. Yes, it can get annoying sometimes to get GenAI to do what you want, but as a dad raising a small 2 year old kid, I have been trained to be patient and steer it in the right direction.</span></li>
</ul>



<p><span style="font-weight: 400">Happy Holidays! <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></span></p>



<p>&nbsp;</p>
<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Ftinkering-with-a-hands-off-agent.html&amp;action_name=Tinkering%20with%20a%20%26%238220%3Bhands-off%26%238221%3B%20agent&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/tinkering-with-a-hands-off-agent.html">Tinkering with a &#8220;hands-off&#8221; agent</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/tinkering-with-a-hands-off-agent.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6080</post-id>	</item>
		<item>
		<title>Spec-Driven Development in Practice: How AI Simplify Full-Stack Java</title>
		<link>https://www.javaadvent.com/2025/12/spec-driven-development-in-practice-how-ai-simplify-full-stack-java.html</link>
					<comments>https://www.javaadvent.com/2025/12/spec-driven-development-in-practice-how-ai-simplify-full-stack-java.html#respond</comments>
		
		<dc:creator><![CDATA[Simon Martinelli]]></dc:creator>
		<pubDate>Sun, 14 Dec 2025 03:03:46 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6132</guid>

					<description><![CDATA[<p>AI is changing how we build software, but many teams still work as if nothing has changed. They treat code as the only reliable artifact. Everything else slowly gets outdated: AI code generation tools can make this even worse if used without structure. They produce code fast, but the process behind the code remains the [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fspec-driven-development-in-practice-how-ai-simplify-full-stack-java.html&amp;action_name=Spec-Driven%20Development%20in%20Practice%3A%20How%20AI%20Simplify%20Full-Stack%20Java&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/spec-driven-development-in-practice-how-ai-simplify-full-stack-java.html">Spec-Driven Development in Practice: How AI Simplify Full-Stack Java</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>AI is changing how we build software, but many teams still work as if nothing has changed. They treat code as the only reliable artifact. Everything else slowly gets outdated:</p>



<ul class="wp-block-list">
<li>Requirements documents drift away from reality</li>



<li>Diagrams do not match the current architecture</li>



<li>Tests only cover part of the behavior</li>



<li>Business logic hides deep in service classes</li>
</ul>



<p>AI code generation tools can make this even worse if used without structure. They produce code fast, but the process behind the code remains the same.</p>



<p>Spec-Driven Development (SDD) solves this by starting from clear specifications instead of starting from code. AI then uses these specifications to generate consistent code, tests, and documentation.</p>



<p>The <a href="https://aiup.dev">AI Unified Process</a> (AIUP) is a practical way to apply SDD in real projects. It keeps requirements, code, and documentation in sync. In this article, we look at AIUP in the context of a full-stack Java application built with Spring Boot, jOOQ, and Vaadin.</p>



<h2 class="wp-block-heading">From Code-Centered To Spec-Driven</h2>



<p>In most teams, development is code-centered:</p>



<ul class="wp-block-list">
<li>A ticket is created</li>



<li>A developer writes code</li>



<li>Documentation and tests come later</li>
</ul>



<p>This leads to long-term problems:</p>



<ul class="wp-block-list">
<li>You lose track of why a rule exists</li>



<li>Business decisions hide in commit messages</li>



<li>Refactoring becomes risky</li>



<li>The architecture stops matching the documentation</li>
</ul>



<p>Spec-Driven Development flips this:</p>



<ol class="wp-block-list">
<li>You start with a clear specification.</li>



<li>This specification becomes the single source of truth.</li>



<li>AI generates code, tests, and diagrams from it.</li>



<li>Developers review and adjust the generated results.</li>
</ol>



<p>When requirements change, you update the specification first, then regenerate.</p>



<h2 class="wp-block-heading">The AI Unified Process (AIUP)</h2>



<p>AIUP is an iterative process with a simple but powerful structure.<br>It is built on three core specification artifacts:</p>



<ol class="wp-block-list">
<li><strong>Requirements Catalog</strong></li>



<li><strong>Entity Model</strong></li>



<li><strong>System Use Cases</strong></li>
</ol>



<p>These artifacts are always updated in this order. Everything else flows from them.</p>



<h2 class="wp-block-heading">1. Requirements Catalog</h2>



<p>The Requirements Catalog is the foundation of AIUP.<br>It defines what the system must do and under which conditions.</p>



<p>It contains three types of requirements:</p>



<h3 class="wp-block-heading">Functional Requirements</h3>



<p>These describe&nbsp;<strong>what the system should do</strong>. In AIUP, they are usually written as&nbsp;<strong>user stories</strong>.</p>



<p>Examples:</p>



<ul class="wp-block-list">
<li>As a user, I want to create a customer with name and email.</li>



<li>As an admin, I want to deactivate customers who have no open orders.</li>



<li>As a sales agent, I want to see all active customers sorted by name.</li>
</ul>



<p>Functional requirements describe user goals and expected behavior, not code.</p>



<h3 class="wp-block-heading">Non-Functional Requirements</h3>



<p>These describe qualities of the system, like:</p>



<ul class="wp-block-list">
<li>Performance</li>



<li>Security</li>



<li>Availability</li>



<li>Logging</li>



<li>Observability</li>



<li>Scalability</li>
</ul>



<p>Example:</p>



<ul class="wp-block-list">
<li>All write operations must be audited with a timestamp, user, and change details.</li>
</ul>



<h3 class="wp-block-heading">Constraints</h3>



<p>These describe rules that must always be enforced.</p>



<p>Examples:</p>



<ul class="wp-block-list">
<li>Customer email must be unique.</li>



<li>Only authenticated users may access customer data.</li>



<li>Orders cannot be deleted once invoiced.</li>
</ul>



<p>The Requirements Catalog is written in simple, precise language. It is reviewed frequently and kept under version control. Everything in AIUP starts with this catalog.</p>



<h2 class="wp-block-heading">2. Entity Model</h2>



<p>The Entity Model describes the domain data structure.<br>It answers:</p>



<p><strong>Which concepts exist, and how do they relate?</strong></p>



<p>The Entity Model is based on the Requirements Catalog. Functional requirements describe what users want to do. Constraints often describe the domain rules. From both, you extract stable domain objects.</p>



<p>For example, from the requirements above, you define:</p>



<p><strong>Entities:</strong></p>



<ul class="wp-block-list">
<li>Customer</li>



<li>Order</li>
</ul>



<p><strong>Attributes:</strong></p>



<ul class="wp-block-list">
<li>Customer: id, name, email, active</li>



<li>Order: id, status, totalAmount, customerId</li>
</ul>



<p><strong>Relationships:</strong></p>



<ul class="wp-block-list">
<li>Customer 1..* Order</li>
</ul>



<p>The Entity Model lets AI generate:</p>



<ul class="wp-block-list">
<li>Database schema or migrations</li>



<li>jOOQ meta model</li>



<li>Domain classes and DTOs</li>



<li>Validation rules</li>



<li>Basic UI bindings in Vaadin</li>
</ul>



<p>It ensures that back end, database, and UI use the same domain structure.</p>



<h2 class="wp-block-heading">3. System Use Cases</h2>



<p>AIUP uses only&nbsp;<strong>System Use Cases</strong>. There is no separation between business and system use cases.</p>



<p>A System Use Case describes&nbsp;<strong>how the system behaves</strong>&nbsp;when triggered by a user or external system. It defines:</p>



<ul class="wp-block-list">
<li>Actors</li>



<li>Preconditions</li>



<li>Steps</li>



<li>Decision logic</li>



<li>Postconditions</li>



<li>Errors</li>
</ul>



<p>System Use Cases transform requirements into explicit logic.</p>



<h3 class="wp-block-heading">Example System Use Case: Deactivate Customer</h3>



<p><strong>Name:</strong> Deactivate Customer<br><strong>Actor:</strong> Administrator<br><strong>Preconditions:</strong></p>



<ul class="wp-block-list">
<li>User is authenticated</li>



<li>User has role ADMIN</li>
</ul>



<p><strong>Main Flow:</strong></p>



<ol class="wp-block-list">
<li>Load customer</li>



<li>Load related orders</li>



<li>Check for open orders</li>



<li>If open orders exist, return error</li>



<li>Set customer.active = false</li>



<li>Write audit entry</li>
</ol>



<p><strong>Postconditions:</strong></p>



<ul class="wp-block-list">
<li>Customer is inactive</li>



<li>Operation is logged</li>
</ul>



<h2 class="wp-block-heading">How AIUP Runs In Iterations</h2>



<p>AIUP uses short iterations. Each cycle follows this sequence:</p>



<ol class="wp-block-list">
<li>Update Requirements Catalog</li>



<li>Update Entity Model</li>



<li>Update System Use Cases</li>



<li>Generate code, diagrams, and tests with AI</li>



<li>Review and improve the generated output</li>



<li>Run tests</li>



<li>Refine the specifications</li>
</ol>



<p>This creates a stable, controlled development loop.</p>



<h2 class="wp-block-heading">Example: A Java Feature With AIUP</h2>



<p>Imagine you add a “Deactivate Customer” feature. All documentation is code and under version control.<br>The example uses Markdown, but you could also use AsciiDoc to get a richer Markdown syntax.</p>



<h3 class="wp-block-heading">Step 1: Requirements Catalog</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
| Category                  | Description                                                |
|---------------------------|------------------------------------------------------------|
| Functional requirement    | As an admin, I want to deactivate a customer who has no open orders. |
| Non-functional requirement| All write operations must be audited.                      |
| Constraint                | Customer email must be unique.    
</pre></div>


<h3 class="wp-block-heading">Step 2: Entity Model</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Entity: Customer with id, name, email, active  
Entity: Order with id, status, customerId  
Relation: Customer 1..* Order  
Rule: Status can be OPEN, COMPLETED, or CANCELLED
</pre></div>


<p>AI generates:</p>



<ul class="wp-block-list">
<li>Migration script for unique email</li>



<li>jOOQ code</li>



<li>Domain classes</li>
</ul>



<h3 class="wp-block-heading">Step 3: Use Cases</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
## UC-001: Deactivate Customer

**Name**  
Deactivate Customer

**Actor**  
Administrator

**Preconditions**  
- User is authenticated  
- User has role ADMIN

**Main Flow**  
1. Load customer  
2. Load related orders  
3. Check for open orders  
4. If open orders exist, return error  
5. Set customer.active = false  
6. Write audit entry

**Postconditions**  
- Customer is inactive  
- Operation is logged
</pre></div>


<p>AI generates:</p>



<ul class="wp-block-list">
<li>Vaadin UI</li>



<li>Service method implementing logic</li>



<li>jOOQ queries</li>



<li>Unit, integration tests, and E2E tests</li>
</ul>



<p>You review, adjust, and commit.</p>



<h2 class="wp-block-heading">Why This Fits Full-Stack Java So Well</h2>



<p>AIUP fits the Java ecosystem because:</p>



<ul class="wp-block-list">
<li>The Spring ecosystem is very mature</li>



<li>jOOQ is built around generated code</li>



<li>Vaadin UI components are superior</li>



<li>Java is strongly typed</li>



<li>Tests are easy to generate from System Use Cases</li>
</ul>



<p>AIUP connects these elements with a straightforward process.</p>



<h2 class="wp-block-heading">Benefits For Teams</h2>



<ul class="wp-block-list">
<li><strong>Clear Requirements</strong><br>Rules, constraints, and user stories are explicit.</li>



<li><strong>Consistent Architecture</strong><br>UI, Backend, and database reflect the same model.</li>



<li><strong>Faster Onboarding</strong><br>New developers read the specs instead of guessing</li>



<li><strong>Safer Use Of AI</strong><br>AI follows the spec and avoids random patterns.</li>



<li><strong>Higher Speed</strong><br>AI handles repetitive tasks</li>



<li><strong>Lower Risk</strong><br>System Use Cases generate strong test coverage.</li>
</ul>



<h2 class="wp-block-heading">AI Is A Partner, Not A Replacement</h2>



<p>AIUP assumes a skilled development team. Developers still:</p>



<ul class="wp-block-list">
<li>Make architectural decisions</li>



<li>Review the generated code</li>



<li>Ensure security</li>



<li>Ensure performance</li>



<li>Adjust implementation details</li>
</ul>



<p>AI accelerates work. Developers ensure correctness.</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p>Spec-Driven Development with AIUP provides teams with a structured approach to using AI. AI can generate code, tests, and diagrams that stay consistent with the specification.</p>



<p>In a Java stack using Spring Boot, jOOQ, and Vaadin, this approach is a natural fit. It increases speed, improves quality, and keeps the system aligned with business needs.</p>



<p>To get started:</p>



<ul class="wp-block-list">
<li>Write the Requirements Catalog</li>



<li>Define the Entity Model</li>



<li>Add System Use Cases</li>



<li>Let AI generate code and tests</li>



<li>Review, refine, and repeat</li>
</ul>



<p>This is how AIUP makes full-stack Java development faster, safer, and more consistent.</p>



<h2 class="wp-block-heading">See it in Action</h2>



<p>Two webinar recordings show how to work with the AIUP: <a href="https://www.youtube.com/live/jwPBHVNvLr0">Spec-driven Development</a> and <a href="https://www.youtube.com/live/3ReLE_BD0oM">Spec-driven Testing</a></p>
<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fspec-driven-development-in-practice-how-ai-simplify-full-stack-java.html&amp;action_name=Spec-Driven%20Development%20in%20Practice%3A%20How%20AI%20Simplify%20Full-Stack%20Java&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/spec-driven-development-in-practice-how-ai-simplify-full-stack-java.html">Spec-Driven Development in Practice: How AI Simplify Full-Stack Java</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/spec-driven-development-in-practice-how-ai-simplify-full-stack-java.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6132</post-id>	</item>
		<item>
		<title>Hidden Treasures of Eclipse Collections 2025 Edition</title>
		<link>https://www.javaadvent.com/2025/12/hidden-treasures-of-eclipse-collections-2025-edition.html</link>
					<comments>https://www.javaadvent.com/2025/12/hidden-treasures-of-eclipse-collections-2025-edition.html#respond</comments>
		
		<dc:creator><![CDATA[Nikhil Nanivadekar]]></dc:creator>
		<pubDate>Sat, 13 Dec 2025 03:03:52 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6220</guid>

					<description><![CDATA[<p>Eclipse Collections is an open source Java Collections framework. In this blog I am going to demonstrate four lesser known features of the framework. I have published similar blogs in Java Advent Calendars of 2018, 2019, 2020, 2021, 2022, 2023, and 2024. Please refer to the resources at the end of the blog for more [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fhidden-treasures-of-eclipse-collections-2025-edition.html&amp;action_name=Hidden%20Treasures%20of%20Eclipse%20Collections%202025%20Edition&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/hidden-treasures-of-eclipse-collections-2025-edition.html">Hidden Treasures of Eclipse Collections 2025 Edition</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Eclipse Collections is an open source Java Collections framework. In this blog I am going to demonstrate four lesser known features of the framework. I have published similar blogs in Java Advent Calendars of <a href="https://www.javaadvent.com/2018/12/hidden-treasures-of-eclipse-collections.html">2018</a>, <a href="https://www.javaadvent.com/2019/12/hidden-treasures-of-eclipse-collections-2019-edition.html">2019</a>, <a href="https://www.javaadvent.com/2020/12/hidden-treasures-of-eclipse-collections-2020-edition.html">2020</a>, <a href="https://www.javaadvent.com/2021/12/hidden-treasures-of-eclipse-collections-2021-edition.html">2021</a>, <a href="https://www.javaadvent.com/2022/12/hidden-treasures-of-eclipse-collections-2022-edition.html">2022</a>, <a href="https://www.javaadvent.com/2023/12/hidden-treasures-of-eclipse-collections-2023-edition.html">2023</a>, and <a href="https://www.javaadvent.com/2024/12/hidden-treasures-of-eclipse-collections-2024-edition.html">2024</a>. Please refer to the resources at the end of the blog for more information about the framework. The newly published <a href="https://a.co/d/62dHYun">Eclipse Collections Categorically: Level up your programming game</a> is a great book to dive deep in the design and methodology behind the iteration patterns of Eclipse Collections.</p>

<ol class="wp-block-list">
<li><code>groupByUniqueKey()</code>: Eclipse Collections offers a way to transform and collect the output in a map, where the key is the transformed value. The <code>groupByUniqueKey()</code> API ensures that the generated keys must each be unique, or else an exception is thrown. This is an easy way to create a map from a collection and guarantee that no keys are overridden.
<pre>@Test
public void groupByUniqueKey() {
    MutableList list = Lists.mutable.of(1, 2, 3, 4);
    MutableMap&lt;Integer, Integer&gt; groupByUniqueKeyMap =
        list.groupByUniqueKey(
            each -&gt; -1 * each); // Negate each integer
    
    MutableMap&lt;Integer, Integer&gt; expectedMap =
        Maps.mutable.of(
            -1, 1, //key, value pairs
            -2, 2,
            -3, 3,
            -4, 4);
    assertEquals(expectedMap, groupByUniqueKeyMap);
}

@Test
public void groupByUniqueKey_throws() {
    MutableList list = Lists.mutable.of(1, 2, 2, 3);
    
    // Throws exception because the element 2 is a duplicate
    assertThrows(
            IllegalStateException.class,
            () -&gt; list.groupByUniqueKey(each -&gt; each));
}
</pre>
</li>
<li><code>countByEach()</code>: Eclipse Collections offers a way to count the number of occurrences of each value after transforming each element of a collection. The <code>countByEach()</code> API does it by iterating through the collection and applying the function to each element and then counting the number of occurrences of the transformed value. This API returns a <code>Bag</code> which is an optimized data structure for counting number of objects. This is similar to the <code>countBy()</code> API that I covered in the 2019 blog with the difference that in case of <code>countByEach()</code> the transformation function returns an <code>Iterable</code>
<pre>@Test
public void countByEach() {
    MutableList&lt;Integer&gt; list = Lists.mutable.of(1, 2, 3, 4);

    MutableBag&lt;Integer&gt; counts = list
        .countByEach(
            each -&gt; Lists.mutable.of(each, each + 1));

    assertEquals(1, counts.occurrencesOf(1));
    assertEquals(2, counts.occurrencesOf(2));
    assertEquals(2, counts.occurrencesOf(3));
    assertEquals(2, counts.occurrencesOf(4));
    assertEquals(1, counts.occurrencesOf(5));
}</pre>
</li>
<li>
<div><code>sumBy*()</code>: Eclipse Collections offers a way to group and sum elements of the collection using <code>sumByInt()</code>, <code>sumByLong()</code>, <code>sumByDouble()</code>, and <code>sumByFloat()</code> methods. A salient point to note is that the return types are up-casted i.e. <code>sumByInt()</code> returns a <code>ObjectLongMap</code> and <code>sumByFloat()</code> returns a <code>ObjectDoubleMap</code>. This avoids overflow issues in the results.</div>
<pre>// Common function to classify even and odd numbers
Function&lt;Integer, String&gt; mappingFunction = each -&gt;
    {
        if (each % 2 == 0) {
            return "EVEN";
        }
        return "ODD";
    };

@Test
public void sumByInt() {
    MutableList list = Lists.mutable.of(1, 2, 3, 4);
    MutableObjectLongMap sumByInt = list.sumByInt(
            mappingFunction,
            each -&gt; each);

    MutableObjectLongMap expected =
        ObjectLongMaps.mutable.of(
            "EVEN", 6L, //key, value pairs
            "ODD", 4L);
    assertEquals(expected, sumByInt);
}

@Test
public void sumByLong() {
    MutableList list = Lists.mutable.of(1, 2, 3, 4);
    MutableObjectLongMap sumByLong = list.sumByLong(
            mappingFunction,
            Integer::longValue);

    MutableObjectLongMap expected =
        ObjectLongMaps.mutable.of(
            "EVEN", 6L, //key, value pairs
            "ODD", 4L);
    assertEquals(expected, sumByLong);
}

@Test
public void sumByDouble() {
    MutableList list = Lists.mutable.of(1, 2, 3, 4);
    MutableObjectDoubleMap sumByDouble = list.sumByDouble(
            mappingFunction,
            Integer::doubleValue);

    MutableObjectDoubleMap expected =
        ObjectDoubleMaps.mutable.of(
            "EVEN", 6.0, //key, value pairs
            "ODD", 4.0);
    assertEquals(expected, sumByDouble);
}

@Test
public void sumByFloat() {
    MutableList list = Lists.mutable.of(1, 2, 3, 4);
    MutableObjectDoubleMap sumByFloat = list.sumByFloat(
            mappingFunction,
            Integer::floatValue);

    MutableObjectDoubleMap expected =
        ObjectDoubleMaps.mutable.of(
            "EVEN", 6.0, //key, value pairs
            "ODD", 4.0);
    assertEquals(expected, sumByFloat);
}
</pre>
</li>
<li><code>aggregateBy()</code>: Eclipse Collections offers a way to aggregate and group results into a map using a grouping function. Please note the API signature — the first input is the grouping function, second input is the zero value function, and the last input is the aggregation function. The code below shows how each of these inputs impacts the behavior.
<pre>@Test
public void aggregateBy() {
    MutableList list = Lists.mutable.of(1, 2, 3, 4);
    MutableMap&lt;String, Integer&gt; aggregateBy1 =
        list.aggregateBy(
            mappingFunction, () -&gt; 0, Integer::sum);

    // Note that because the zero value is in this case 0,
    // the result is that
    // sum of even numbers is 0 + 2 + 4 = 6; 
    // sum of odd numbers is 0 + 1 + 3 = 4
    MutableMap&lt;String, Integer&gt; expected1 = Maps.mutable.of(
            "EVEN", 6,
            "ODD", 4);

    assertEquals(expected1, aggregateBy1);

    MutableMap&lt;String, Integer&gt; aggregateBy2 =
        list.aggregateBy(
            mappingFunction, () -&gt; 10, Integer::sum);

    // Note that because the zero value is in this case 10,
    // the result is that 
    // sum of even numbers is 10 + 2 + 4 = 16; 
    // sum of odd numbers is 10 + 1 + 3 = 14
    MutableMap&lt;String, Integer&gt; expected2 = Maps.mutable.of(
            "EVEN", 16,
            "ODD", 14);

    assertEquals(expected2, aggregateBy2);

    MutableMap&lt;String, Integer&gt; aggregateBy3 =
        list.aggregateBy(
            mappingFunction, 
            () -&gt; 0, 
            (before, each) -&gt; before + each + 10);

    // Note that because the aggregation function adds 10 to
    // every computation, the computation becomes: 
    // for even numbers: 0 + (2 + 10) + (4 + 10) = 26; 
    // for odd numbers: 0 + (1 + 10) + (3 + 10) = 24
    MutableMap&lt;String, Integer&gt; expected3 = Maps.mutable.of(
            "EVEN", 26,
            "ODD", 24);

    assertEquals(expected3, aggregateBy3);
}</pre>
</li>
</ol>
<div>
<h2>Summary:</h2>
<div>In this blog I explained a few lesser known features of Eclipse Collections <code>groupByUniqueKey()</code>, <code>countByEach()</code>, <code>sumBy*()</code>, and <code>aggregateBy()</code>. I hope you found the post informative. If you have not used Eclipse Collections before, give it a try. There are few resources below. Make sure you show us your support and put a star on our <a href="https://github.com/eclipse-collections/eclipse-collections">GitHub Repository</a>.</div>
<h2>Eclipse Collections Resources</h2>
<div><a href="https://github.com/eclipse-collections/eclipse-collections">Eclipse Collections</a> comes with it’s own implementations of <a href="https://eclipse.dev/collections/javadoc/11.1.0/org/eclipse/collections/impl/list/mutable/FastList.html">List</a>, <a href="https://eclipse.dev/collections/javadoc/11.1.0/org/eclipse/collections/impl/set/mutable/UnifiedSet.html">Set</a> and <a href="https://eclipse.dev/collections/javadoc/11.1.0/org/eclipse/collections/impl/map/mutable/UnifiedMap.html">Map</a>. It also has additional data structures like <a href="https://eclipse.dev/collections/javadoc/11.1.0/org/eclipse/collections/api/multimap/Multimap.html">Multimap</a>, <a href="https://eclipse.dev/collections/javadoc/11.1.0/org/eclipse/collections/api/bag/Bag.html">Bag</a> and an entire Primitive Collections hierarchy. Each of our collections have a fluent and rich API for commonly required iteration patterns.</div>
<div> </div>
<ul>
<li><a href="https://eclipse.dev/collections/">Website</a></li>
<li><a href="https://github.com/eclipse-collections/eclipse-collections">Source code on GitHub</a> (<em>Make sure to star the Repository</em>)</li>
<li><a href="https://github.com/eclipse-collections/eclipse-collections/blob/master/CONTRIBUTING.md">Contribution Guide</a></li>
<li><a href="https://github.com/eclipse-collections/eclipse-collections/blob/master/docs/guide.md#eclipse-collections-reference-guide">Reference Guide</a></li>
<li><a href="https://a.co/d/62dHYun">Eclipse Collections Categorically: Level up your programming game</a></li>
</ul>
</div>
<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fhidden-treasures-of-eclipse-collections-2025-edition.html&amp;action_name=Hidden%20Treasures%20of%20Eclipse%20Collections%202025%20Edition&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/hidden-treasures-of-eclipse-collections-2025-edition.html">Hidden Treasures of Eclipse Collections 2025 Edition</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/hidden-treasures-of-eclipse-collections-2025-edition.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6220</post-id>	</item>
		<item>
		<title>Strengthening your Software Supply Chain</title>
		<link>https://www.javaadvent.com/2025/12/strengthening-your-software-supply-chain.html</link>
					<comments>https://www.javaadvent.com/2025/12/strengthening-your-software-supply-chain.html#respond</comments>
		
		<dc:creator><![CDATA[Andres Almiray]]></dc:creator>
		<pubDate>Fri, 12 Dec 2025 03:03:02 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[java]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6044</guid>

					<description><![CDATA[<p>According to Wikipedia, a software supply chain is the components, libraries, tools, and processes used to develop, build, and publish a software artifact. The Java space provides thousands upon thousands of libraries that may be consumed as dependencies for building projects. Many of these libraries rely on Apache Maven as their build tool of choice, [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fstrengthening-your-software-supply-chain.html&amp;action_name=Strengthening%20your%20Software%20Supply%20Chain&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/strengthening-your-software-supply-chain.html">Strengthening your Software Supply Chain</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>According to Wikipedia, a <a href="https://en.wikipedia.org/wiki/Software_supply_chain">software supply chain</a> is the components, libraries, tools, and processes used to develop, build, and publish a software artifact. The Java space provides thousands upon thousands of libraries that may be consumed as dependencies for building projects. Many of these libraries rely on <a href="https://maven.apache.org/">Apache Maven</a> as their build tool of choice, followed by <a href="https://gradle.org/">Gradle</a> and <a href="https://ant.apache.org/">Apache Ant</a>. There are of course a few other more, the Java ecosystem provides plenty of options (<a href="https://rife2.com/bld">bld</a>, <a href="https://www.jbang.dev/">JBang</a>, etc).</p>
<p>While these build tools continue to improve their own development practices, their use alone is not enough to ensure that your artifacts, build pipelines, compiler tool chains, and other working parts of your software supply chain are secure and resistant to tampering or attacks. The Supply-chain Levels for Software Artifacts project, or <a href="https://slsa.dev/">SLSA</a> (pronounced &#8220;salsa&#8221;) is a it’s a security framework, a checklist of standards and controls to prevent tampering, improve integrity, and secure packages and infrastructure. In its landing page you&#8217;ll find the following description regarding places where potential attacks may occur</p>
<p><div id="attachment_6045" style="width: 310px" class="wp-caption aligncenter"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6045" data-attachment-id="6045" data-permalink="https://www.javaadvent.com/2025/12/strengthening-your-software-supply-chain.html/slsa-sscs" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?fit=1776%2C1232&amp;ssl=1" data-orig-size="1776,1232" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="slsa-sscs" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?fit=300%2C208&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?fit=600%2C416&amp;ssl=1" class="wp-image-6045 size-medium" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?resize=300%2C208&#038;ssl=1" alt="" width="300" height="208" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?resize=300%2C208&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?resize=1024%2C710&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?resize=768%2C533&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?resize=1536%2C1066&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?resize=1240%2C860&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?resize=508%2C352&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-sscs.png?w=1776&amp;ssl=1 1776w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a><p id="caption-attachment-6045" class="wp-caption-text">© 2025 The Linux Foundation</p></div></p>
<p>Yes, attacks may come from many places but also happen at many locations within the software supply chain.</p>
<p>We must take action at different locations, fortunately the previously mentioned build tools provide some support but we need a few more things. Do you recall the news that shocked the IT world in December 2021? That&#8217;s right, it was <a href="https://en.wikipedia.org/wiki/Log4Shell">Log4Shell</a>. Or the failed <a href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor">XZ Backdoor</a> from 2024? These and many other attacks could have been prevented or have their impact lessened if specific techniques, additional metadata, and tools were also available and applied on time.</p>
<p>Let&#8217;s begin with the straight forward ones, then move on with more complex options. But before we do, I would like to remark that <a href="https://jreleaser.org/">JReleaser</a>, while not strictly a build tool but rather a release tool, provides support for all of the features we&#8217;ll cover. Onward.</p>
<h3>Reproducible Artifacts</h3>
<p>Reproducible artifacts come from <a href="https://reproducible-builds.org/">Reproducible Builds</a>, a software development practice where building the exact same source code with the same tools and instructions always results in a bit-for-bit identical binary output. If you happen to have a compatible reproducible environment (similar or identical OS, Java distribution, environment variables, etc) then you may rebuild a given codebase at an specific point of its history (say a tagged release) and obtain a bit-by-bit identical set of artifacts that may be compared at different build dates.</p>
<p>Luckily for many Java developers, Maven makes it quite easy to generate reproducible artifacts (JARs, ZIPs, TARs) by simply setting a fixed <a href="https://maven.apache.org/guides/mini/guide-reproducible-builds.html">timestamp</a>. Gradle offers similar capabilities although the value of the timestamp can&#8217;t be changed. <a href="https://jreleaser.org/">JReleaser</a> also lets you assemble reproducible archives with any of its <a href="https://jreleaser.org/guide/latest/reference/assemble/index.html">assemblers</a>.</p>
<p>For example, this <a href="https://github.com/jreleaser/helloworld-java-bin">repository</a> contains a simple Helloworld project. Its build creates an executable JAR file, while its <a href="https://github.com/jreleaser/helloworld-java-bin/blob/main/jreleaser.yml">release configuration</a> defines a binary distribution as a ZIP file. Here&#8217;s a short snippet of said configuration</p>
<pre>assemble:
  javaArchive:
    helloworld:
      active: ALWAYS
      formats: [ ZIP ]
      fileSets:
        - input: '.'
          includes: [ 'LICENSE' ]
      mainJar:
        path: target/{{distributionName}}-{{projectVersion}}.jar</pre>
<p>Invoking the assemble command results in archive containing the following entries:</p>
<pre>Archive: java-archive/helloworld-1.0.0.zip
Length Date Time Name
--------- ---------- ----- ----
    11357 01-06-2025 19:30 helloworld-1.0.0/LICENSE
     4533 01-06-2025 19:30 helloworld-1.0.0/bin/helloworld
     1889 01-06-2025 19:30 helloworld-1.0.0/bin/helloworld.bat
     3777 01-06-2025 19:30 helloworld-1.0.0/lib/helloworld-1.0.0.jar
--------- -------
    21556 4 files</pre>
<p>That timestamp may look arbitrary but its guaranteed to be reproducible, as it happens to match the tagged commit for that release, which is</p>
<pre>commit eaa60a9314e3555db6e44da8442808d46f2fcd35
Author: Andres Almiray &lt;aalmiray@gmail.com&gt;
Date: Mon Jan 6 19:30:58 2025 +0100</pre>
<p>Neat, isn&#8217;t it? Let&#8217;s continue.</p>
<h3>Digital Signatures</h3>
<p>Another way to add an extra layer of security is by providing digital signatures for a given set of artifacts. We&#8217;ve been using PGP for decades since its inception. Pretty much every build tool supports generating these kind of signatures with either explicit mechanism (plugins) or calling out to external commands (gnupg for example).</p>
<p>This works fine, except when it doesn&#8217;t, which is when key management comes into play. Some developers are lazy and set their keys to never expire, while some are too concerned that they rotate their keys quite often. No matter where you stand in the key management spectrum, it requires performing additional tasks as a burden. For this reason, a new idea emerged a bit more than a decade ago: <a href="https://www.sigstore.dev/">Sigstore</a>.</p>
<p>Sigstore provides an alternative for automating artifact signing and signature verification. While the signing tool was originally created for signing container images (hence the name cosign) and written in Go, nowadays there&#8217;s a Java API that enables Maven and Gradle support. JReleaser in turn also supports <a href="https://jreleaser.org/guide/latest/reference/signing.html">generating signatures</a> for all artifacts to be released as either PGP or Sigstore, making it easier to provide such signatures as it doesn&#8217;t matter how those artifacts were built/assembled or which tool did it.</p>
<p>Enabling digital signatures for a release, whether with PGP or Sigstore, is quite easy with JReleaser, for example, the minimum configuration would be something similar to</p>
<div>
<pre>signing:
  active: ALWAYS
  armored: true</pre>
</div>
<p>Where&#8217;s the rest? Well, the values of the public &amp; secret keys, as well as an associated passphrase (when required) may be supplied via environment variables, to keep these value as secrets.</p>
<h3>SBOMs and SWID Tags</h3>
<p>The next level of protection is comprised of additional metadata files that may be used to inspect an artifact for its set of dependencies, or in the case of an archive, for its contained files. By now I hope you&#8217;ve heard of SBOMs and how to procure them during a build. SBOMs have become more important these days as software vendors and makers will be required to provide them upon request, according to <a href="https://en.wikipedia.org/wiki/Cyber_Resilience_Act">CRA</a> and <a href="https://en.wikipedia.org/wiki/Digital_Operational_Resilience_Act">DORA</a>.</p>
<p>Both Maven and Gradle provide plugins that generate SBOMs in different formats. You may also use <a href="https://github.com/anchore/syft">Syft</a> to create such files. These SBOMs should provide a list of all dependencies required for building the matching artifact, a JAR file in this case. But what about ZIPs and TARs, or other kind of archives? In this case JReleaser can generate SBOMs for all archives created via any of its <a href="https://jreleaser.org/guide/latest/reference/assemble/index.html">assemblers</a>.</p>
<p>Here&#8217;s a snippet showing how you can configure SBOM generation for artifacts using both <a href="https://cyclonedx.org/">cyclonedx</a> and Syft. The resulting SBOMs will be packaged in a single ZIP and will be available to be uploaded as release assets</p>
<pre>catalog:
  sbom:
    cyclonedx:
      active: ALWAYS
      pack:
       enabled: true
       name: '{{projectName}}-{{projectVersion}}-cyclonedx-sboms'
    syft:
      active: ALWAYS
      pack:
      enabled: true
      name: '{{projectName}}-{{projectVersion}}-syft-sboms'</pre>
<p>You only need one format or the other, this particular example shows how easy is to configure either format.</p>
<p>Additionally to SBOMs there&#8217;s also <a href="https://csrc.nist.gov/projects/software-identification-swid">SWID Tags</a>, created by the National Institute of Standards and Technology (<a href="https://en.wikipedia.org/wiki/National_Institute_of_Standards_and_Technology">NIST</a>). This format provides a thorough list of all entries found inside a given archive, with a checksum per entry. Computing both SBOMs and SWID Tags provides a level of redundancy as the same checksum values should appear in both files, making it harder for an attacker to fake values. Also, you may be contractually obligated to deliver such files depending on the type of projects and customers you may be working with. As far as I recall there&#8217;s a Maven plugin for generating SWID tags but no Gradle plugin.</p>
<p>Activating SWID tag generation for assembled archives is as easy as configuring the following snippet in your JReleaser configuration file</p>
<pre>catalog:
  swid:
    swid-tag:
      active: ALWAYS</pre>
<p>Now, adding SWID tag to a given assembly is done as follows</p>
<pre>assemble:
  javaArchive:
    helloworld:
      active: ALWAYS
      formats: [ ZIP ]
      fileSets:
        - input: '.'
          includes: [ 'LICENSE' ]
      mainJar:
        path: target/{{distributionName}}-{{projectVersion}}.jar
      swid:
        tagRef: swid-tag</pre>
<p>Notice the reference to <strong>swid-tag</strong>, which is the custom name that we defined in a previous snippet.</p>
<h3>Attestation Files</h3>
<p>Attestations bind some subject (a named artifact along with its digest) to a predicate (some assertion about that subject) using the <a href="https://github.com/in-toto/attestation/tree/main/spec/v1">in-toto</a> format. <a href="https://github.com/in-toto/attestation/tree/main/spec/predicates#in-toto-attestation-predicates">Predicates</a> consist of a type URI and a JSON object containing type-dependent parameters.</p>
<p>Attestation files are usually digitally signed, and guess what, this is where Sigstore appears again. Attestation providers also offer a command line tool that may be used to verify the signatures of the attestation files, as well as their contents.</p>
<p>If you happen to use GitHub (whether free or enterprise) then you have access to GitHub Attestations. Making use of this feature is straight forward and requires you to use the <a href="https://github.com/actions/attest">actions/attest</a> action. There are a few ways to supply the list of files to be included for attestation, one of them is a checksums file. It so happens that JReleaser automatically calculates such a file when a release is performed, or when the <em>checksum</em> command is explicitly invoked. This makes it quite easy to combine it with the <em>actions/attest</em> action, like so</p>
<pre>- name: Release
  uses: jreleaser/release-action@v2
  with:
    arguments: release
  env:
    # change this value to your own version number
    JRELEASER_PROJECT_VERSION: 1.2.3

- name: Attestations
  uses: actions/attest-build-provenance@v1
  with:
    subject-checksums: out/jreleaser/checksums/. checksums_sha256.txt
  predicate-type: 'https://example.com/predicate/v1'
  predicate: '{}'</pre>
<p>The SLSA framework also provides its own attestation feature. You may run it directly from GitHub via the <a href="https://github.com/slsa-framework/slsa-github-generator">slsa-framework/slsa-github-generator</a>, for which you&#8217;d need to configure more things depending on your build requirements. Luckily the SLSA team made things easier by enabling other tools to create their own sanctioned SLSA builders (BYOB), as explained in this <a href="https://slsa.dev/blog/2023/08/bring-your-own-builder-github">link</a>.</p>
<p>JReleaser is an early adopter of the BYOB feature, providing a seamless integration with GitHub Actions. The <a href="https://github.com/jreleaser/helloworld-java-slsa">jreleaser/helloworld-java-slsa</a>  repository showcases how this integration works. There is one thing to take into consideration, the SLSA builder must be provided with both build and release instructions, as they must happen within a controlled environment. I said earlier that JReleaser is a release tool, not a build tool, however it does allow custom commands to be invoked at certain stages of its execution. Thus if we&#8217;re able to supply build instructions that are guaranteed to be invoked before a release then we should be good to go. And that&#8217;s exactly what we can do.</p>
<p>The <a href="https://github.com/jreleaser/helloworld-java-slsa/blob/main/jreleaser.yml">release configuration file</a> in the helloworld-java-slsa repository has a section specifying the required build instructions, and that they should be invoked during the <em>assemble</em> step.</p>
<pre>hooks:
  script:
    before:
      - run: './mvnw -ntp verify'
        condition: '"{{ Env.CI }}" == true'
        verbose: true
        filter:
          includes: ['assemble']</pre>
<p>Now, the next bit of configuration is found in a GitHub Actions workflow such as <a href="https://github.com/jreleaser/helloworld-java-slsa/blob/main/.github/workflows/release.yml">this one</a></p>
<pre> release:
   name: Release
   needs: [ precheck ]
   permissions:
     contents: write
     id-token: write
     actions: read
     packages: write
   uses: jreleaser/jreleaser-slsa/.github/workflows/builder_slsa3_java.yml@v1.1.0
  with:
    project-version: ${{ needs.precheck.outputs.VERSION }}
    rekor-log-public: true
  secrets:
  github-token: ${{ secrets.GITHUB_TOKEN }}</pre>
<p>This snippet defines specific permissions granted to the default GITHUB_TOKEN, such that the invoked trusted workflow (builder_slsa4_java.yml) can not access more than it shouldn&#8217;t. This trusted workflow will in turn invoke the SLSA generator and make sure that the attestation file is attached as a release asset. It all just happens automagically as seen in <a href="https://github.com/jreleaser/helloworld-java-slsa/releases/tag/v1.0.0">this</a> release:</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6054" data-permalink="https://www.javaadvent.com/2025/12/strengthening-your-software-supply-chain.html/slsa-release" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?fit=1792%2C1590&amp;ssl=1" data-orig-size="1792,1590" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="slsa-release" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?fit=300%2C266&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?fit=600%2C533&amp;ssl=1" class="size-medium wp-image-6054 aligncenter" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?resize=300%2C266&#038;ssl=1" alt="" width="300" height="266" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?resize=300%2C266&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?resize=1024%2C909&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?resize=768%2C681&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?resize=1536%2C1363&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?resize=1240%2C1100&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?resize=508%2C451&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/slsa-release.png?w=1792&amp;ssl=1 1792w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p>
<p>And tough this is a Java Advent entry, I would be remiss it I did not mention that JReleaser can be used with any kind of project independent of its source code, its not just for Java. Likewise, the SLSA support offered by JReleaser may be used with <a href="https://github.com/jreleaser/helloworld-go-slsa">Go</a>, <a href="https://github.com/jreleaser/helloworld-rust-slsa">Rust</a>, and <a href="https://github.com/jreleaser/helloworld-zig-slsa">Zig</a>, with additional languages coming later.</p>
<h3>Summary</h3>
<p>Securing your Software Supply Chain requires a paradigm shift, adapting build and releases processes, learning new tools and techniques. The build tools you&#8217;re already used to may provide answers to some of these concerns, while JReleaser provides additional support, specifically during the release portion of the chain.<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fstrengthening-your-software-supply-chain.html&amp;action_name=Strengthening%20your%20Software%20Supply%20Chain&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/strengthening-your-software-supply-chain.html">Strengthening your Software Supply Chain</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/strengthening-your-software-supply-chain.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6044</post-id>	</item>
		<item>
		<title>A Glance at GPU Goodness in Java: LLM Inference with TornadoVM</title>
		<link>https://www.javaadvent.com/2025/12/a-glance-at-gpu-goodness-in-java-llm-inference-with-tornadovm.html</link>
					<comments>https://www.javaadvent.com/2025/12/a-glance-at-gpu-goodness-in-java-llm-inference-with-tornadovm.html#respond</comments>
		
		<dc:creator><![CDATA[Edoardo Vacchi]]></dc:creator>
		<pubDate>Thu, 11 Dec 2025 03:03:21 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[gpu]]></category>
		<category><![CDATA[llama3]]></category>
		<category><![CDATA[tornadovm]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5844</guid>

					<description><![CDATA[<p>It seems like it&#8217;s become a tradition that I announce I have joined a new company for the Java Advent of Code. At least this time it&#8217;s actually an old friend: I am excited to be back at Red Hat, in the llm-d team! Does that mean I forgot about Java? Of course not. If [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fa-glance-at-gpu-goodness-in-java-llm-inference-with-tornadovm.html&amp;action_name=A%20Glance%20at%20GPU%20Goodness%20in%20Java%3A%20LLM%20Inference%20with%20TornadoVM&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/a-glance-at-gpu-goodness-in-java-llm-inference-with-tornadovm.html">A Glance at GPU Goodness in Java: LLM Inference with TornadoVM</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>It seems like it&#8217;s become a tradition that I announce I have joined a new company for the Java Advent of Code. At least this time it&#8217;s actually an old friend: I am excited to be back at Red Hat, in the <a href="https://llm-d.ai/">llm-d</a> team! Does that mean I forgot about Java? Of course not. If anything, this is an opportunity to learn more about GPU programming, and since Java is my comfort-zone language, what better occasion than looking into <a href="https://www.tornadovm.org/">TornadoVM</a>?</p>



<p>Recently, the TornadoVM team released <a href="https://www.tornadovm.org/gpullama3">gpullama3</a>, a proof-of-concept demonstrating LLM inference on GPUs using pure Java. Let&#8217;s explore this together!</p>



<h2 class="wp-block-heading">What is TornadoVM?</h2>



<p><a href="https://www.tornadovm.org/">TornadoVM</a> is a plugin for the OpenJDK that enables Java programs to automatically run on heterogeneous hardware (GPUs, FPGAs, and multi-core CPUs) using standard Java code annotated for parallel compute.</p>



<p>Under the hood, TornadoVM:</p>



<ol class="wp-block-list">
<li>takes your Java bytecode</li>



<li>compiles it to GPU-specific kernels (via OpenCL, PTX, or SPIR-V)</li>



<li>manages data transfers between CPU and GPU memory</li>



<li>executes the computation on the GPU</li>



<li>returns the results back to your Java program</li>
</ol>



<p>Transformer-based language models are computationally expensive but highly-parallelizable. At inference time, generating each token requires matrix multiplications across billions of parameters. GPUs excel at these operations because they can perform thousands of computations in parallel. Thus, TornadoVM is the perfect tool for this kind of workload.</p>



<h1 class="wp-block-heading">Installing TornadoVM</h1>



<p>Installing <code>GPULlama3.java</code> was surprisingly straightforward. Make sure you have your favorite flavor of JDK 21 installed. I use <code>sdkman</code>, so I made sure I had <a href="https://adoptium.net/temurin/releases">Temurin 21</a> installed:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
sdk install java 21.0.9-tem
</pre></div>


<p>Then you&#8217;ll want to make sure you have installed <code>cmake</code>, a C/C++ toolchain, Python and <code>pip</code>. Now you can clone the repo:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
git clone https://github.com/beehive-lab/GPULlama3.java
</pre></div>


<p>and follow the instructions on the <a href="https://github.com/beehive-lab/GPULlama3.java?tab=readme-ov-file#install-build-and-run">README</a>; for instance, on macOS/Linux:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
# Enter the TornadoVM submodule directory
cd external/tornadovm

# Optional: Create and activate a Python virtual environment if needed
python3 -m venv venv
source ./venv/bin/activate

# Install TornadoVM with a supported JDK 21 and select the backends (--backend opencl,ptx).
# To see the compatible JDKs run: ./bin/tornadovm-installer --listJDKs
# For example, to install with OpenJDK 21 and build the OpenCL backend, run: 
./bin/tornadovm-installer --jdk jdk21 --backend opencl

# Source the TornadoVM environment variables
source setvars.sh
</pre></div>


<p>You can verify the installation was successful by running one example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
cd tornado-examples
mvn package
cd ..
tornado -cp tornado-examples/target/tornado-examples-1.1.2-dev-e1d2d12.jar uk.ac.manchester.tornado.examples.compute.MatrixVectorRowMajor
</pre></div>


<p>Of course, make sure you replace <code>tornado-examples-1.1.2-dev-e1d2d12.jar</code> with the right jar name! Your output should look something like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
WARNING: Using incubator modules: jdk.incubator.vector
Matrix-Vector Multiplication Benchmark
======================================
Configuration:
- Input dimension (columns): 8192
- Output dimension (rows): 2048
- Local work group size: 32
- Backend: OPENCL
- DP4A benchmarks enabled: false
- Warmup iterations: 140
- Benchmark iterations: 120

Initializing data...
Setting up TornadoVM execution...
Warming up sequential implementation...
Benchmarking sequential implementation...
Warming up parallel implementation...
Benchmarking parallel implementation...
Validating results...
Validation PASSED ✓

Performance Results:
====================
Matrix size: 2048 x 8192
Sequential Implementation:
  Average time: 15.892 ms
  Min time: 15.673 ms
  Max time: 16.795 ms
  Performance: 2.11 GFLOP/s
Parallel Implementation (TornadoVM):
  Average time: 2.405 ms
  Min time: 2.030 ms
  Max time: 5.069 ms
  Performance: 13.95 GFLOP/s
Pure TornadoVM @Parallel Implementation (TornadoVM):
  Average time: 4.931 ms
  Min time: 3.745 ms
  Max time: 8.357 ms
  Performance: 6.81 GFLOP/s
Parallel Implementation FP16 (TornadoVM):
  Average time: 1.840 ms
  Min time: 1.575 ms
  Max time: 3.090 ms
  Performance: 18.24 GFLOP/s
Q8 Vectorized:
  Average time: 1.746 ms
  Min time: 1.459 ms
  Max time: 6.097 ms
  Performance: 19.22 GFLOP/s

Speedup: KernelContext vs Java 6.61x
Speedup: @Parallel vs Java 3.22x
Speedup: KernelContext vs @Parallel 2.05x
Speedup: Q8 Vectorized vs KernelContext 1.38x
Speedup: Q8 Vectorized vs KernelContext FP16 1.05x
</pre></div>


<h1 class="wp-block-heading">Baby&#8217;s First GPU Kernel</h1>



<p>Here&#8217;s a simple <code>Example.java</code>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
import uk.ac.manchester.tornado.api.annotations.*;
import uk.ac.manchester.tornado.api.*;
import uk.ac.manchester.tornado.api.enums.DataTransferMode;
import uk.ac.manchester.tornado.api.types.arrays.FloatArray;

public class Example {
    
    public static void vectorMul(FloatArray a, FloatArray b, FloatArray result) {
        for (@Parallel int i = 0; i &lt; result.getSize(); i++) {
            result.set(i, a.get(i) * b.get(i));
        }
    }

    public static void main(String... args) {
        int size = 1024;
        
        var a = new FloatArray(size);
        var b = new FloatArray(size);
        var result = new FloatArray(size);
        
        for (int i = 0; i &lt; size; i++) {
            a.set(i, i * 2.0f);
            b.set(i, i + 1.0f);
        }
        
        var taskGraph = new TaskGraph(&quot;multiply&quot;)
            .transferToDevice(DataTransferMode.FIRST_EXECUTION, a, b)
            .task(&quot;vectorMul&quot;, Example::vectorMul, a, b, result)
            .transferToHost(DataTransferMode.EVERY_EXECUTION, result);

        var snapshot = taskGraph.snapshot();
        new TornadoExecutionPlan(snapshot).execute();
        
        System.out.println(&quot;\nVector Multiplication Results (first 10):&quot;);
        for (int i = 0; i &lt; 10; i++) {
            System.out.printf(&quot;result&#x5B;%d] = %.2f (a&#x5B;%d]=%.2f * b&#x5B;%d]=%.2f)\n&quot;, 
                i, result.get(i), i, a.get(i), i, b.get(i));
        }
    }
}

</pre></div>


<p>The <code>@Parallel</code> annotation tells TornadoVM this loop can be parallelized. The <code>TaskGraph</code> API manages data movement and execution scheduling. You can compile it with the following (if you followed the installation guide correctly <code>$TORNADO_SDK</code> will point to the right path):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
javac -g --enable-preview -source 21 -cp &quot;$TORNADO_SDK/share/java/tornado/*&quot; Example.java
</pre></div>


<p>Notice that <code>-g</code> is required for this to work correctly. Now you can run it with:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
tornado Example
</pre></div>


<p>It will print the first 10 items in the resulting vector.</p>



<h2 class="wp-block-heading">Playing with <strong>GPULlama3</strong>.java</h2>



<p>The <a href="https://www.tornadovm.org/gpullama3">gpullama3</a> project demonstrates running a Llama 3 model entirely in Java with GPU acceleration. Assuming you are back at the root of the repo, continue with the setup procedure.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
# Navigate back to the project root directory
cd ../../

# Source the project-specific environment paths -&amp;gt; this will ensure the correct paths are set for the project and the TornadoVM SDK
# Expect to see: &#x5B;INFO] Environment configured for Llama3 with TornadoVM at: /home/YOUR_PATH_TO_TORNADOVM
source set_paths

# Build the project using Maven (skip tests for faster build)
# mvn clean package -DskipTests or just make
make

</pre></div>


<p>Now let&#8217;s download a compatible model using the HuggingFace CLI:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
# download and install the hugging face CLI
pip install -U huggingface_hub

# download a model to ./models/
hf download beehive-lab/Llama-3.2-1B-Instruct-GGUF-FP16 --include '*.gguf' --local-dir models
</pre></div>


<p>Try it! Even on my poor MacBook Air with 8 GB RAM (provided I don&#8217;t have too many applications open) this returns:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
❯ python llama-tornado --gpu --verbose-init --opencl --model models/Llama-3.2-1B-Instruct-FP16.gguf --prompt &quot;tell me a joke&quot;
WARNING: Using incubator modules: jdk.incubator.vector
Loading model weights in TornadoVM format (loading F16)

Starting TornadoVM initialization...
TornadoVM GPU execution plan creation: 1011.56 ms
Java to GPU JIT compiler warmup: 4994.25 ms
Transfer read-only weights to GPU: 13958.97 ms
Finished TornadoVM initialization...

Here's one:

What do you call a fake noodle?

(wait for it...)

An impasta!

Hope that made you laugh!


achieved tok/s: 3.00. Tokens: 42, seconds: 13.98
</pre></div>


<p>Disclaimer: even if you have better CPU/GPUs at your disposal, they are unlikely to affect the quality of the joke.</p>
<h1 class="wp-block-heading">What just happened?</h1>



<p>GPULlama3.java currently supports a few FP16 (16-bit floating point) and 8-bit quantized models:</p>



<ul class="wp-block-list">
<li>Llama 3.2 (1B) &#8211; FP16</li>



<li>Llama 3.2 (3B) &#8211; FP16</li>



<li>Llama 3 (8B) &#8211; FP16</li>



<li>Mistral (7B) &#8211; FP16</li>



<li>Qwen3 (0.6B) &#8211; FP16</li>



<li>Qwen3 (1.7B) &#8211; FP16</li>



<li>Qwen3 (4B) &#8211; FP16</li>



<li>Qwen3 (8B) &#8211; FP16</li>



<li>Phi-3-mini-4k &#8211; FP16</li>



<li>Qwen2.5 (0.5B)</li>



<li>Qwen2.5 (1.5B)</li>



<li>DeepSeek-R1-Distill-Qwen (1.5B)</li>
</ul>



<p>Depending on the model being selected, a different execution plan will be built. The execution plan corresponds to the model architecture. In our case, we picked the unquantized <strong>Llama 3.2 1B FP16</strong>. Let&#8217;s take a look at the <code>setupTornadoForwardPlan()</code> method in FP16LayerPlanner, used by LLama 3.2:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
abstract class FP16LayerPlanner ... {
    ...
    protected final void setupTornadoForwardPlan() {
        List&lt;ImmutableTaskGraph&gt; allTaskGraphs = new ArrayList&lt;&gt;();
        GridScheduler masterScheduler = new GridScheduler();

        // 1. Activation layer (common to all models)
        allTaskGraphs.add(activationLayer.getImmutableTaskGraph());
        activationLayer.updateGridScheduler(masterScheduler);

        // 2. FFN layers (N transformer layers - model-specific)
        allTaskGraphs.addAll(ffnLayers.getFfnLayerTaskGraphs());
        ffnLayers.updateGridScheduler(masterScheduler);

        // 3. Logits layer (common to all models)
        allTaskGraphs.add(logitsLayer.getTaskGraph().snapshot());
        logitsLayer.updateGridScheduler(masterScheduler);

        // Cache for future retrievals
        this.immutableTaskGraphs = allTaskGraphs;
        this.gridScheduler = masterScheduler;
    }

}
</pre></div>


<p>In the <strong>Activation layer</strong> we mostly look up token embeddings and apply an initial normalization step, while the <strong>Logit layer</strong> is where we convert the model&#8217;s internal representation into token predictions. So let&#8217;s concentrate a bit more on the <strong>Feed-Forward Network layer</strong> <strong>(FFN)</strong>, and in particular on the <strong>Attention</strong> implementation. The <a href="https://github.com/beehive-lab/GPULlama3.java/blob/0060fc66f30b8f023d84bc3bc5c031f26c262bae/src/main/java/org/beehive/gpullama3/tornadovm/layers/type/fp16/LlamaFP16FFNLayers.java#L99"><code>LlamaFP16FFNLayers#setupSingleFFNLayer</code> method</a> is a bit cryptic at a first glance; let&#8217;st start from its signature:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
TaskGraph setupSingleFFNLayer(LlamaTornadoWeights weights, Configuration config, int layerIndex)
</pre></div>


<p>The method is building a <code>TaskGraph</code>, essentially describing the data flow of our GPU kernels. Let&#8217;s focus on QKV and attention, using <a href="https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/07_gpt_to_llama/standalone-llama32.ipynb">Sebastian Raschka</a><sup class="fn" data-fn="7a8f1308-64fa-485f-a8c1-46592c445251"><a id="7a8f1308-64fa-485f-a8c1-46592c445251-link" href="#7a8f1308-64fa-485f-a8c1-46592c445251">1</a></sup><a href="https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/07_gpt_to_llama/standalone-llama32.ipynb">&#8216;s excellent Python Notebook as a reference</a>. The following is the architecture diagram of the Llama 3.2 1B model:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="663" data-attachment-id="5859" data-permalink="https://www.javaadvent.com/2025/12/a-glance-at-gpu-goodness-in-java-llm-inference-with-tornadovm.html/llama321b" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?fit=1061%2C1172&amp;ssl=1" data-orig-size="1061,1172" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="llama321b" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?fit=272%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?fit=600%2C663&amp;ssl=1" class="wp-image-5859" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?resize=600%2C663&#038;ssl=1" alt="" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?resize=927%2C1024&amp;ssl=1 927w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?resize=272%2C300&amp;ssl=1 272w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?resize=768%2C848&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?resize=508%2C561&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/llama321b.png?w=1061&amp;ssl=1 1061w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></figure>



<p>For obvious reasons of brevity, we aren&#8217;t going to explore this in detail, but we do want to take a look at the implementation of the <a href="https://en.wikipedia.org/wiki/Attention_(machine_learning)">attention heads</a>. In particular, let&#8217;s take a look at how we compute the Query, Key, Value matrices (<code>Q,K,V = project(x)</code> in the Python version):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
.task(&quot;qmatmul&quot;, 
        TransformerComputeKernelsLayered::matrixVectorGeneric, context, 
        state.wrapXb, state.wrapQ, 
        weights.wqLayered&#x5B;layerIndex].asHalfFloatArray(), 
        config.dim(), config.dim(),
        LOCAL_WORK_GROUP_SIZE_ALLOC)
.task(&quot;kmatmul&quot;, 
        TransformerComputeKernelsLayered::matrixVectorGeneric, context, 
        state.wrapXb, state.wrapK, 
        weights.wkLayered&#x5B;layerIndex].asHalfFloatArray(), 
        config.dim(), config.kvDim(),
        LOCAL_WORK_GROUP_SIZE_ALLOC)
.task(&quot;vmatmul&quot;, 
        TransformerComputeKernelsLayered::matrixVectorGeneric, context, 
        state.wrapXb, state.wrapV, 
        weights.wvLayered&#x5B;layerIndex].asHalfFloatArray(), 
        config.dim(), config.kvDim(),
        LOCAL_WORK_GROUP_SIZE_ALLOC)
</pre></div>


<p>This is followed by the RoPE rescaling to encode token positions:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
.task(&quot;rope&quot;, TransformerComputeKernelsLayered::ropeRotation, context, 
        state.positionHolder, 
        state.wrapQ, state.wrapK, 
        config.kvDim(), 
        config.headSize())

</pre></div>


<p>Now we are ready to compute <strong>attention. </strong>The generic version (there is also an NVidia-specific implementation) is:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
unifiedLayer.task(&quot;parallel-attention&quot;, TransformerComputeKernelsLayered::processHeadsParallel, 
        state.wrapQ, state.wrapKeyCache, state.wrapValueCache, state.wrapXb,
        config.numberOfHeads(), config.headSize(), config.kvDim(), config.kvMul(),
             config.contextLength(), 
        state.positionHolder, state.wrapAtt, 
        layerIndex, config.contextLength());

</pre></div>


<p>Let&#8217;s drill down into <code>TransformerComputeKernelsLayered::processHeadsParallel</code> to see how that is performed. The following is one of the GPU kernels. It essentially computes:</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Screenshot-2025-12-10-at-10.08.16.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6284" data-permalink="https://www.javaadvent.com/2025/12/a-glance-at-gpu-goodness-in-java-llm-inference-with-tornadovm.html/screenshot-2025-12-10-at-10-08-16" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Screenshot-2025-12-10-at-10.08.16.png?fit=698%2C94&amp;ssl=1" data-orig-size="698,94" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="attention(qkv)" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Screenshot-2025-12-10-at-10.08.16.png?fit=300%2C40&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Screenshot-2025-12-10-at-10.08.16.png?fit=600%2C81&amp;ssl=1" class="size-full wp-image-6284 aligncenter" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Screenshot-2025-12-10-at-10.08.16.png?resize=600%2C81&#038;ssl=1" alt="$Attention(Q, K, V) = \text{Softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V$" width="600" height="81" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Screenshot-2025-12-10-at-10.08.16.png?w=698&amp;ssl=1 698w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Screenshot-2025-12-10-at-10.08.16.png?resize=300%2C40&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/12/Screenshot-2025-12-10-at-10.08.16.png?resize=508%2C68&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p>You will notice that the method:</p>
<ol>
<li>takes the <code>Q,K</code> <strong>(Query, Key) vectors</strong> that we computed earlier and it computes the attention score up to the current position <code>pos</code> (scaled by the square root of the head size), filling the <code>wrapAtt</code> vector</li>
<li>next, it applies <code>softmax</code> to the <code>wrapAtt</code>, turning the scores into attention weights (steps 2-4), accumulating partial results onto the same <code>wrapAtt</code> vector</li>
<li>finally (step 5) it computes a weighted sum of the <b>Value vectors </b>(<code>value_cache</code>) up to <code>pos</code>, using the calculated <b>Attention Weights </b>(<code>wrapAtt</code>). </li>
</ol>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
    /**
     * Computes attention for a single head. Implements scaled dot-product attention with softmax normalization.
     *
     * Steps: 1. Compute attention scores: Q·K / sqrt(head_size) 2. Apply softmax (with max subtraction for numerical stability) 3. Compute weighted sum of values
     *
     * @param allQ
     *         All query vectors
     * @param key_cache
     *         Cached keys
     * @param value_cache
     *         Cached values
     * @param allXb
     *         Output buffer
     * @param h
     *         Head index to process
     * @param headSize
     *         Dimension per head
     * @param kvDim
     *         Key/value dimension
     * @param kvMul
     *         Key multiplier for grouped attention
     * @param loff
     *         Layer offset in cache
     * @param pos
     *         Current position
     * @param wrapAtt
     *         Attention weights buffer
     */
    private static void processHeadTornado(FloatArray allQ, FloatArray key_cache, FloatArray value_cache, FloatArray allXb, int h, int headSize, int kvDim, int kvMul, long loff, int pos,
            FloatArray wrapAtt) {

        // Base index for this head's attention weights
        int headOffset = h * (pos + 1);

        // STEP 1: Calculate attention scores for all timesteps
        for (int t = 0; t &lt;= pos; t++) {
            int kvHeadIdx = h / kvMul;
            int keyOffset = (int) (loff + t * kvDim + kvHeadIdx * headSize);

            float score = 0.0f;
            for (int i = 0; i &lt; headSize; i++) {
                score += allQ.get(h * headSize + i) * key_cache.get(keyOffset + i);
            }
            score = score / TornadoMath.sqrt(headSize);

            // Store in attention buffer
            wrapAtt.set(headOffset + t, score);
        }

        // STEP 2: Find max score for softmax stability
        float maxScore = wrapAtt.get(headOffset);
        for (int t = 1; t &lt;= pos; t++) {
            float val = wrapAtt.get(headOffset + t);
            if (val &gt; maxScore) {
                maxScore = val;
            }
        }

        // STEP 3: Compute exponentials and sum
        float sum = 0.0f;
        for (int t = 0; t &lt;= pos; t++) {
            int idx = headOffset + t;
            float expScore = TornadoMath.exp(wrapAtt.get(idx) - maxScore);
            wrapAtt.set(idx, expScore);
            sum += expScore;
        }

        // STEP 4: Normalize
        float normFactor = (sum &gt; 0.0f) ? (1.0f / sum) : (1.0f / (pos + 1));
        for (int t = 0; t &lt;= pos; t++) {
            int idx = headOffset + t;
            wrapAtt.set(idx, wrapAtt.get(idx) * normFactor);
        }

        // STEP 5: Compute weighted sum of values for each dimension
        for (int i = 0; i &lt; headSize; i++) {
            float weightedSum = 0.0f;
            for (int t = 0; t &lt;= pos; t++) {
                int kvHeadIdx = h / kvMul;
                int valueOffset = (int) (loff + t * kvDim + kvHeadIdx * headSize);
                weightedSum += wrapAtt.get(headOffset + t) * value_cache.get(valueOffset + i);
            }
            allXb.set(h * headSize + i, weightedSum);
        }
    }
</pre></div>


<p>After the attention mechanism computes relationships between tokens, the result is added to the original input, normalized, and passed through a feed-forward network. This process repeats across multiple layers before finally producing the next-token prediction (the <em>logit layer</em>).</p>



<p>Because it’s an <em>autoregressive</em> model, this entire process repeats for each token, using the previously generated sequence as input.</p>



<p>In short, TornadoVM handled GPU compilation and execution transparently, allowing a pure Java program to perform LLM inference!</p>



<h1 class="wp-block-heading">Conclusions</h1>



<p>We’ve completed our whirlwind tour of <strong>Llama3GPU.java</strong> and <strong>TornadoVM</strong>. If your head is still spinning, don’t worry, you’re not alone! It’s a lot to take in, but I hope this post has sparked your interest and inspired you to dig deeper: I know I will!</p>


<ol class="wp-block-footnotes"><li id="7a8f1308-64fa-485f-a8c1-46592c445251">Sebastian Raschka is the author of <a href="https://github.com/rasbt/LLMs-from-scratch">Build a Large Language Model from Scratch</a> <a href="#7a8f1308-64fa-485f-a8c1-46592c445251-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol><img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fa-glance-at-gpu-goodness-in-java-llm-inference-with-tornadovm.html&amp;action_name=A%20Glance%20at%20GPU%20Goodness%20in%20Java%3A%20LLM%20Inference%20with%20TornadoVM&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/a-glance-at-gpu-goodness-in-java-llm-inference-with-tornadovm.html">A Glance at GPU Goodness in Java: LLM Inference with TornadoVM</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/a-glance-at-gpu-goodness-in-java-llm-inference-with-tornadovm.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5844</post-id>	</item>
		<item>
		<title>Breaking keys and building trust: The JAVA way!</title>
		<link>https://www.javaadvent.com/2025/12/breaking-keys-and-building-trust-the-java-way.html</link>
					<comments>https://www.javaadvent.com/2025/12/breaking-keys-and-building-trust-the-java-way.html#respond</comments>
		
		<dc:creator><![CDATA[Ixchel Ruiz]]></dc:creator>
		<pubDate>Wed, 10 Dec 2025 03:03:48 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6008</guid>

					<description><![CDATA[<p>— Preparing for the Quantum Shift &#160; Table of Contents The EU’s Post-Quantum Plan: Recommendation (EU) 2024/1101 What will upcoming EU cybersecurity legislation require? And how can Java help you prepare? Why this is significant for architects? Java advisory note NIS2 — Cryptography as Organizational Governance Engineering Implications Java advisory note DORA — Operational Resilience [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fbreaking-keys-and-building-trust-the-java-way.html&amp;action_name=Breaking%20keys%20and%20building%20trust%3A%20The%20JAVA%20way%21&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/breaking-keys-and-building-trust-the-java-way.html">Breaking keys and building trust: The JAVA way!</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div id="header">
<div class="paragraph">
<p><em>— Preparing for the Quantum Shift</em></p>
<p>&nbsp;</p>
</div>
<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#pqc-plan">The EU’s Post-Quantum Plan: Recommendation (EU) 2024/1101</a>
<ul class="sectlevel2">
<li><a href="#intro-upcoming-legislation">What will upcoming EU cybersecurity legislation require? And how can Java help you prepare?</a></li>
<li><a href="#pqc-plan-significance">Why this is significant for architects?</a></li>
<li><a href="#pqc-plan-java-note">Java advisory note</a></li>
</ul>
</li>
<li><a href="#nis2">NIS2 — Cryptography as Organizational Governance</a>
<ul class="sectlevel2">
<li><a href="#nis2-engineering-implications">Engineering Implications</a></li>
<li><a href="#nis2-java-note">Java advisory note</a></li>
</ul>
</li>
<li><a href="#dora">DORA — Operational Resilience</a>
<ul class="sectlevel2">
<li><a href="#dora-engineering-implications">Engineering Implications</a></li>
<li><a href="#dora-java-note">Java advisory note</a></li>
</ul>
</li>
<li><a href="#cra">Cyber Resilience Act — Secure-by-Design Software</a>
<ul class="sectlevel2">
<li><a href="#cra-core-objectives">Core Objectives</a></li>
<li><a href="#cra-engineering-implications">Engineering Implications</a></li>
<li><a href="#cra-java-note">Java advisory note</a></li>
</ul>
</li>
<li><a href="#standards">Standards &amp; Certification — ENISA EUCC, ACM v2, ETSI TS 103 744</a>
<ul class="sectlevel2">
<li><a href="#standards-enisa-eucc">ENISA &amp; EUCC</a></li>
<li><a href="#standards-etsi-ts-103-744">ETSI TS 103 744 — The Foundation of Hybrid TLS</a></li>
<li><a href="#standards-java-note">Java advisory note</a></li>
</ul>
</li>
<li><a href="#national">National Implementations — Germany and France</a></li>
<li><a href="#checklist">Practical Checklist for Java-Based Systems</a>
<ul class="sectlevel2">
<li><a href="#checklist-ctos">For CTOs and Security Architects</a></li>
<li><a href="#checklist-platform-teams">For Platform Teams</a></li>
<li><a href="#checklist-java-developers">For Java Developers</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
<li><a href="#references">References</a></li>
</ul>
</div>
</div>



<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>&nbsp;</p>
<p>Over the next decade, European cryptographic systems will undergo a significant transformation similar to the change from <strong>DES to AES</strong> and <strong>SSL to TLS</strong>. The converging forces of regulatory requirements, <strong>post-quantum research</strong>, and market pressure are set to redefine software security. This is particularly important for organizations in sectors such as <strong>finance, critical infrastructure, cloud services, public administration</strong>, and <strong>large-scale digital platforms</strong>. <strong>This is not optional:</strong> it marks the beginning of a significant architectural shift that will impact nearly every system with a security boundary.</p>
</div>
<div class="paragraph">
<p>&nbsp;</p>
<p>At the center of this shift lies a simple but powerful reality: <strong>The cryptography we rely on today will not be sufficient for the systems we build for tomorrow.</strong></p>
</div>
<div class="paragraph">
<p>&nbsp;</p>
<p>The <strong>European Union</strong> is preparing for this future through a combination of high-impact legislation (<strong>NIS2</strong>, <strong>DORA</strong>, the <strong>Cyber Resilience Act</strong>), forward-looking strategic documents (the Post-Quantum Cryptography Roadmap), and emerging standards (<strong>ETSI hybrid key-exchange</strong> profiles, <strong>ENISA’s EUCC</strong> and <strong>ACM</strong> mechanisms). Together, these frameworks make one expectation unmistakable: <strong><em>systems must be crypto-agile, updatable, and robust in the face of long-term cryptographic threats—including those posed by quantum computing.</em></strong></p>
</div>
<div class="paragraph">
<p>&nbsp;</p>
<p>For technical leaders, this means the timeline for learning about <strong>post-quantum transition</strong> and <strong>cryptographic lifecycle management</strong> is not &#8220;sometime in the 2030s&#8221;—it is <strong>right now</strong>. System designs created today must remain defensible <strong>five, ten, or fifteen years from now</strong>. <em>Cryptographic decisions made today will determine whether your systems remain compliant, secure, and operational in the era of hybrid and post-quantum algorithms.</em></p>
</div>
<div class="paragraph">
<p>This article is written to help you navigate what comes next. It does three things:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>It explains the major EU legislative and strategic changes that will require system upgrades over the coming years, what they mean, why they matter, and how they will influence long-term architecture.</li>
<li>It provides a practical advisory note for Java practitioners, showing how the JVM has quietly been preparing for this future through a series of foundational security enhancements.</li>
<li>It highlights recent Java features—KEM/KDF APIs, modern PEM support, hybrid TLS efforts, and PQC-related proposals and explains how they enable crypto agility and long-term resilience.</li>
</ol>
</div>
<div class="paragraph">
<p>This is not about compliance checklists or abstract policy. <em>It is about engineering: <strong>how to build systems today</strong> that will still be secure and supportable a decade from now.</em></p>
</div>
<div class="paragraph">
<p>Java, with its <strong>modern cryptographic architecture</strong>, <strong>provider-based extensibility</strong>, and emerging support for <strong>hybrid and PQC operations</strong>, offers a powerful foundation for this transition. Many of the tools you need—structured <strong>KEM/KDF APIs</strong>, improved <strong>certificate handling</strong>, modular cryptographic providers, and simplified security models—already exist in current JDKs or are in active development.</p>
</div>
<div class="paragraph">
<p>&nbsp;</p>
<p>The coming years will challenge our assumptions about how cryptography is integrated into software systems. But with the right understanding—and the right platform choices—you can position your systems to not just meet regulatory expectations, but to be resilient in a security landscape that is about to change more rapidly than any time in recent memory.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="pqc-plan">The EU’s Post-Quantum Plan: Recommendation (EU) 2024/1101</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="intro-upcoming-legislation">What will upcoming EU cybersecurity legislation require? And how can Java help you prepare?</h3>
<div class="paragraph">
<p>Between the <strong>Post-Quantum Cryptography (PQC)</strong> transition, the <strong>NIS2 Directive</strong>, the <strong>DORA Regulation</strong>, and the <strong>Cyber Resilience Act (CRA)</strong>, the EU is moving toward a future in which:</p>
</div>
<div class="ulist">
<ul>
<li><strong>Cryptography must be upgradable</strong>,</li>
<li><strong>Systems must be resilient</strong> against long-term threats, and</li>
<li><strong>Software products must be secure-by-design</strong> and maintainable for years.</li>
</ul>
</div>
<div class="paragraph">
<p>If you design, operate, or oversee systems that must endure well into the 2030s, these legal changes are not mere theoretical concepts; they are <strong>integral components of your engineering strategy</strong>. They affect protocol choices, PKI design, cryptographic libraries, update processes, compliance documentation, and runtime platform capabilities.</p>
</div>
<div class="paragraph">
<p>On <strong>11 April 2024</strong>, the European Commission issued <strong>Recommendation (EU) 2024/1101</strong> on a &#8220;Coordinated Implementation Roadmap for the transition to Post-Quantum Cryptography&#8221; <a href="#references">[1]</a>. It instructs Member States to &#8220;develop and implement a harmonised approach as the Union transitions to post-quantum cryptography.&#8221; <a href="#references">[2]</a></p>
</div>
<div class="paragraph">
<p>Furthermore, &#8220;Within two years of this Recommendation, the NIS Cooperation Group should develop a coordinated implementation roadmap for the transition to post-quantum cryptography.&#8221; <a href="#references">[3]</a></p>
</div>
<div class="paragraph">
<p>This Roadmap was subsequently published on <strong>23 June 2025</strong> <a href="#references">[4]</a>.</p>
</div>
<div class="paragraph">
<p>The Roadmap defines <strong>phases, not hard deadlines</strong>.</p>
</div>
<div class="paragraph">
<p>These phases include guidance such as:</p>
</div>
<div class="ulist">
<ul>
<li><strong>By the mid-2020s:</strong> build governance, inventory crypto assets, identify exposure to <strong>&#8220;harvest-now-decrypt-later&#8221;</strong>, and launch PQC pilots <a href="#references">[5]</a>.</li>
<li><strong>&#8220;Around 2030&#8221;:</strong> high-risk and high-value systems should use quantum-safe or hybrid cryptography—this phrasing appears in public commentary, not as an exact quote from the official text <a href="#references">[6]</a>.</li>
<li><strong>&#8220;Beyond 2030&#8221;:</strong> wider system migration—again, this is interpretive, based on analyses that infer the long-tail implications of the roadmap <a href="#references">[6]</a>.</li>
</ul>
</div>
<div class="paragraph">
<p><strong>Note:</strong> To be precise, the recommendation does not explicitly state &#8220;2030&#8221; or &#8220;2035&#8221;.</p>
</div>
</div>
<div class="sect2">
<h3 id="pqc-plan-significance">Why this is significant for architects?</h3>
<div class="paragraph">
<p>Even without binding deadlines, the roadmap clearly signals that:</p>
</div>
<div class="ulist">
<ul>
<li><strong>PQC migration</strong> will be expected for critical systems,</li>
<li><strong>Hybrid cryptography</strong> will act as the transition strategy,</li>
<li>Long-term systems must be <strong>crypto-agile</strong>, not locked into fixed primitives.</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="pqc-plan-java-note">Java advisory note</h3>
<div class="paragraph">
<p>Java offers several essential building blocks that are crucial for this transition:</p>
</div>
<div class="ulist">
<ul>
<li><strong>JEP 452 — Key Encapsulation Mechanism (KEM) API</strong> (Delivered: Java 21): clean provider-based integration point for PQC KEMs.</li>
<li><strong>JEP 510 — KDF API</strong> (Delivered: Java 25): necessary for combining classical and PQ secrets in hybrid schemes.</li>
<li><strong>JEP 524 — PEM Encodings</strong> (Preview / In development: Integrated: Java 26): modern handling of keys/certificates required for PQC formats.</li>
<li><strong>JEPs 496 / 497 — Quantum-Resistant Cryptography</strong> (Delivered: Java 24): Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism &amp; Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm</li>
<li><strong>Hybrid TLS Key Exchange</strong> (Under development: Candidate; JEP 527 — Post-Quantum Hybrid Key Exchange for TLS 1.3): draft efforts to enhance the security of Java applications that require secure network communication by implementing hybrid key exchange algorithms for TLS 1.3.</li>
</ul>
</div>
<div class="paragraph">
<p><a href="#article-title">Back to top</a></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="nis2">NIS2 — Cryptography as Organizational Governance</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The <strong>NIS2 Directive</strong>—Directive (EU) 2022/2555—was adopted on <strong>14 December 2022</strong> and published on 27 December 2022 <a href="#references">[7]</a>. Its purpose is &#8220;to achieve a high common level of cybersecurity across the Union.&#8221; <a href="#references">[7]</a></p>
</div>
<div class="paragraph">
<p>Member States were required to transpose NIS2 by <strong>17 October 2024</strong> <a href="#references">[8]</a>.</p>
</div>
<div class="paragraph">
<p>NIS2 applies to <strong>essential and important entities</strong> across sectors such as:</p>
</div>
<div class="ulist">
<ul>
<li>Energy, transport, water, healthcare, banking,</li>
<li>Financial market infrastructures,</li>
<li>Digital infrastructure, managed services,</li>
<li>Public administration, and more.</li>
</ul>
</div>
<div class="paragraph">
<p>Most relevant to system architects is NIS2’s requirement to define <strong>&#8220;policies and procedures regarding the use of cryptography and, where appropriate, encryption.&#8221;</strong> <a href="#references">[7]</a></p>
</div>
<div class="sect2">
<h3 id="nis2-engineering-implications">Engineering Implications</h3>
<div class="paragraph">
<p>NIS2 does not name algorithms. Instead, it demands:</p>
</div>
<div class="ulist">
<ul>
<li><strong>Documented cryptographic policies</strong>,</li>
<li><strong>Upgradeability strategies</strong>,</li>
<li><strong>Lifecycle management</strong> for keys and algorithms,</li>
<li><strong>Demonstrable resilience</strong> against known risks.</li>
</ul>
</div>
<div class="paragraph">
<p>This means:</p>
</div>
<div class="ulist">
<ul>
<li>Fixed, non-upgradable TLS stacks become a <strong>liability</strong>.</li>
<li>Systems must prepare for <strong>algorithm deprecation cycles</strong>.</li>
<li>Quantum-vulnerable algorithms must be assessed as part of risk management.</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="nis2-java-note">Java advisory note</h3>
<div class="paragraph">
<p>Recent Java features support NIS2 expectations:</p>
</div>
<div class="ulist">
<ul>
<li>The <strong>KEM/KDF APIs</strong> make algorithm agility feasible.</li>
<li>Modern <strong>PEM support</strong> simplifies adoption of evolving certificate profiles.</li>
<li>Forward-looking <strong>hybrid TLS implementations</strong> allow for controlled, gradual migration.</li>
<li>Removal of the Security Manager (<strong>JEP 486</strong>) reduces the number of brittle, hard-to-audit legacy security layers.</li>
</ul>
</div>
<div class="paragraph">
<p><a href="#article-title">Back to top</a></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="dora">DORA — Operational Resilience</h2>
<div class="sectionbody">
<div class="paragraph">
<p><strong>Regulation (EU) 2022/2554 (DORA)</strong> became applicable on <strong>17 January 2025</strong> <a href="#references">[9]</a>. It states that &#8220;ICT systems support complex systems used for everyday activities.&#8221; <a href="#references">[9]</a></p>
</div>
<div class="paragraph">
<p>DORA mandates:</p>
</div>
<div class="ulist">
<ul>
<li><strong>Continuous ICT risk management</strong>,</li>
<li><strong>ICT incident reporting</strong>,</li>
<li><strong>Operational resilience testing</strong>,</li>
<li><strong>Oversight of critical ICT third-party providers</strong>.</li>
</ul>
</div>
<div class="sect2">
<h3 id="dora-engineering-implications">Engineering Implications</h3>
<div class="paragraph">
<p>DORA requires institutions to demonstrate:</p>
</div>
<div class="ulist">
<ul>
<li>Resilience even under <strong>cryptographic degradation</strong>,</li>
<li>Preparedness for <strong>long-term confidentiality threats</strong>,</li>
<li>Robust <strong>key management and secure communications</strong>,</li>
<li><strong>Crypto agility and migration planning</strong>.</li>
</ul>
</div>
<div class="paragraph">
<p>Many financial infrastructures rely heavily on Java. This means:</p>
</div>
<div class="ulist">
<ul>
<li>Java-based systems will be evaluated as part of ICT operational resilience.</li>
<li><strong>Quantum-resilience</strong> will increasingly be seen as part of ICT risk, not theoretical research.</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="dora-java-note">Java advisory note</h3>
<div class="paragraph">
<p>Java’s modern crypto stack helps architects meet DORA’s expectations:</p>
</div>
<div class="ulist">
<ul>
<li><strong>KEM/KDF abstractions</strong> enable controlled PQ/HKDF migration designs.</li>
<li><strong>Hybrid TLS</strong> (draft features—exact JEP number requires verification) reduces exposure to <strong>&#8220;harvest now, decrypt later&#8221;</strong> attacks.</li>
<li>Proposed PQ algorithms (<strong>JEPs 496/497</strong>) enable early integration testing.</li>
</ul>
</div>
<div class="paragraph">
<p><a href="#article-title">Back to top</a></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cra">Cyber Resilience Act — Secure-by-Design Software</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The <strong>Cyber Resilience Act</strong>—Regulation (EU) 2024/2847—entered into force on <strong>10 December 2024</strong>. Most obligations take effect from <strong>11 December 2027</strong> <a href="#references">[11]</a>. CRA declares, &#8220;Cybersecurity is one of the key challenges for the Union.&#8221; <a href="#references">[11]</a></p>
</div>
<div class="sect2">
<h3 id="cra-core-objectives">Core Objectives</h3>
<div class="paragraph">
<p>It applies to <strong>all products with digital elements</strong>, demanding:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li><strong>Secure-by-design</strong> and <strong>secure-by-default</strong> development practices</li>
<li>Mandatory <strong>vulnerability management and disclosure</strong> processes</li>
<li>Obligatory <strong>security update mechanisms</strong></li>
<li>Regulation of cybersecurity risks across the <strong>entire lifecycle</strong> of digital products</li>
<li>Increase <strong>transparency and accountability</strong> for vendors and manufacturers</li>
</ol>
</div>
</div>
<div class="sect2">
<h3 id="cra-engineering-implications">Engineering Implications</h3>
<div class="paragraph">
<p>CRA influences platform choices due to its impact on user experience and engagement:</p>
</div>
<div class="ulist">
<ul>
<li>Cryptography must be <strong>updatable throughout the product’s lifecycle</strong>.</li>
<li>Security controls must be <strong>auditable</strong>.</li>
<li>The architecture must be <strong>maintainable for years to come</strong>.</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="cra-java-note">Java advisory note</h3>
<div class="paragraph">
<p>Java provides:</p>
</div>
<div class="ulist">
<ul>
<li>Removal of the outdated <strong>Security Manager</strong>, improving auditability;</li>
<li>Standardized <strong>crypto APIs</strong> that enable algorithms to evolve;</li>
<li>Modern <strong>TLS and PEM handling</strong> for future profiles.</li>
</ul>
</div>
<div class="paragraph">
<p><em>These do not &#8220;make you CRA-compliant,&#8221; but they make *compliance architecturally achievable.*</em></p>
</div>
<div class="paragraph">
<p><a href="#article-title">Back to top</a></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="standards">Standards &amp; Certification — ENISA EUCC, ACM v2, ETSI TS 103 744</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="standards-enisa-eucc">ENISA &amp; EUCC</h3>
<div class="paragraph">
<p>The <strong>EU Cybersecurity Act</strong> (Regulation (EU) 2019/881) established the EU cybersecurity certification framework <a href="#references">[13]</a>. Under it, ENISA published the <strong>EUCC Guidelines on Cryptography</strong> in <strong>July 2024</strong> <a href="#references">[14]</a>.</p>
</div>
<div class="paragraph">
<p>These guidelines recommend that developers use the <strong>Agreed Cryptographic Mechanisms version 2 (ACM v2)</strong> for certified products <a href="#references">[15]</a>.</p>
</div>
<div class="paragraph">
<p>Public analyses confirm that ACM v2 introduces <strong>hybrid and quantum-safe mechanisms</strong> for high-assurance contexts <a href="#references">[16]</a>.</p>
</div>
</div>
<div class="sect2">
<h3 id="standards-etsi-ts-103-744">ETSI TS 103 744 — The Foundation of Hybrid TLS</h3>
<div class="paragraph">
<p>ETSI’s <strong>Quantum-Safe Cryptography</strong> working group authored <strong>TS 103 744</strong>, which defines &#8220;quantum-safe hybrid key establishment schemes pairing a high-assurance but quantum-vulnerable key exchange with a quantum-safe key encapsulation mechanism.&#8221; <a href="#references">[17]</a></p>
</div>
<div class="paragraph">
<p>The updated version <strong>1.2.1</strong> describes itself as offering &#8220;a clear, testable, and interoperable path to deploy hybrid key establishment today.&#8221; <a href="#references">[19]</a></p>
</div>
</div>
<div class="sect2">
<h3 id="standards-java-note">Java advisory note</h3>
<div class="paragraph">
<p>Draft and prototype implementations of <strong>hybrid TLS in Java</strong> follow this ETSI pattern:</p>
</div>
<div class="ulist">
<ul>
<li>Classical <strong>ECDHE + PQC KEM</strong> → combined via a <strong>KDF</strong></li>
<li>Using provider abstractions defined in <strong>JEP 452/510</strong>.</li>
</ul>
</div>
<div class="paragraph">
<p><a href="#article-title">Back to top</a></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="national">National Implementations — Germany and France</h2>
<div class="sectionbody">
<div class="paragraph">
<p><strong>Germany:</strong> Draft law <strong>NIS2UmsuCG</strong> for NIS2 implementation <a href="#references">[20]</a> and <strong>FinmadiG</strong> for DORA and MiCAR alignment <a href="#references">[21]</a>.</p>
</div>
<div class="paragraph">
<p><strong>France:</strong> The <em>Projet de loi relatif à la résilience des infrastructures critiques et au renforcement de la cybersécurité</em> was adopted in first reading by the Senate on <strong>12 March 2025</strong> <a href="#references">[23]</a>.</p>
</div>
<div class="paragraph">
<p>These national laws that dictate how supervisory authorities will assess systems in real-world scenarios transform EU obligations into auditable operational requirements:</p>
</div>
<div class="ulist">
<ul>
<li>Supervisory authorities can request evidence that systems support <strong>cryptographic evolution</strong>.</li>
<li>Algorithms must be <strong>replaceable without requiring significant code rewrites</strong>.</li>
<li>TLS configurations must be compatible with <strong>hybrid and post-quantum cryptographic mechanisms</strong>.</li>
<li>PKI infrastructures must be able to handle <strong>new certificate profiles</strong>.</li>
<li>Legacy security designs must be replaced with <strong>maintainable and testable architectures</strong>.</li>
</ul>
</div>
<div class="paragraph">
<p><a href="#article-title">Back to top</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="checklist">Practical Checklist for Java-Based Systems</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="checklist-ctos">For CTOs and Security Architects</h3>
<div class="ulist">
<ul>
<li>Map your systems to <strong>NIS2, DORA, CRA</strong> applicability.</li>
<li>Identify sensitive data requiring <strong>long-term confidentiality</strong>.</li>
<li>Build a <strong>crypto-agility roadmap</strong> aligned with PQC phases.</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="checklist-platform-teams">For Platform Teams</h3>
<div class="ulist">
<ul>
<li>Plan pilots of <strong>hybrid TLS</strong> and roadmap-aligned cryptography.</li>
<li>Transition away from legacy, <strong>hard-coded crypto</strong>.</li>
<li>Establish internal standards for <strong>provider-based crypto</strong>.</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="checklist-java-developers">For Java Developers</h3>
<div class="ulist">
<ul>
<li>Familiarize yourself with <strong>KEM/KDF APIs</strong> and <strong>PEM updates</strong>.</li>
<li>Avoid bespoke crypto; rely on <strong>JCA/JCE</strong> where possible.</li>
<li>Track <strong>PQC-related JEPs</strong> (especially those still in proposal form).</li>
</ul>
</div>
<div class="paragraph">
<p><a href="#article-title">Back to top</a></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="conclusion">Conclusion</h2>
<div class="sectionbody">
<div class="paragraph">
<p>EU cybersecurity legislation is setting the direction for the next decade: <strong>resilience, agility, post-quantum readiness, and security-by-design</strong>. These requirements affect protocols, libraries, build pipelines, PKI, update mechanisms, and runtime platforms.</p>
</div>
<div class="paragraph">
<p>Java’s recent and proposed features—<strong>modern crypto APIs, hybrid-TLS work, improved certificate handling, simplified security architecture</strong>—provide solid foundations for architects who need to prepare for this future. <strong>The mandates are coming. The engineering must begin now.</strong></p>
</div>
<div class="paragraph">
<p><a href="#article-title">Back to top</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="references">References</h2>
<div class="sectionbody">
<div class="ulist">
<ul>
<li>[1] European Commission. Recommendation (EU) 2024/1101 of 11 April 2024 on a Coordinated Implementation Roadmap for the transition to Post-Quantum Cryptography. 2024. <a href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32024H1101">url</a></li>
<li>[2] European Commission. Communication accompanying Recommendation (EU) 2024/1101 (quotation referenced). 2024.</li>
<li>[3] European Commission. Recommendation (EU) 2024/1101, Article 2: Tasking the NIS Cooperation Group. 2024.</li>
<li>[4] NIS Cooperation Group. <em>A Coordinated Implementation Roadmap for the Transition to Post-Quantum Cryptography.</em> 23 June 2025. <a href="https://digital-strategy.ec.europa.eu/en/library/coordinated-implementation-roadmap-transition-post-quantum-cryptography">url</a></li>
<li>[5] NIS Cooperation Group. PQC Roadmap – Phases and Timeline Guidance. 2025.</li>
<li>[6] Industry and policy analyses summarising PQC milestones (2026 planning, 2030 critical systems, ~2035 broader migration). 2025.</li>
<li>[7] European Parliament &amp; Council. Directive (EU) 2022/2555 (NIS2 Directive) on measures for a high common level of cybersecurity across the Union. 2022. <a href="https://eur-lex.europa.eu/eli/dir/2022/2555/oj">url</a></li>
<li>[8] European Commission. NIS2 Transposition Overview – Transposition by 17 October 2024. 2024.</li>
<li>[9] European Parliament &amp; Council. Regulation (EU) 2022/2554 (DORA). 2022. <a href="https://eur-lex.europa.eu/eli/reg/2022/2554/oj">url</a></li>
<li>[10] Legal and compliance analyses confirming DORA applicability from 17 January 2025. 2024–2025.</li>
<li>[11] European Parliament &amp; Council. Regulation (EU) 2024/2847 (Cyber Resilience Act). 2024.</li>
<li>[12] Regulatory summaries detailing CRA’s entry into force (10 December 2024) and applicability (11 December 2027). 2024–2025.</li>
<li>[13] European Parliament &amp; Council. Regulation (EU) 2019/881 (Cybersecurity Act). 2019.</li>
<li>[14] ENISA. EUCC Cryptography Guidelines. 17 July 2024.</li>
<li>[15] ENISA &amp; ECCG. Agreed Cryptographic Mechanisms version 2 (ACM v2). 2024–2025.</li>
<li>[16] Keysight &amp; other vendors. Analyses of ACM v2 and the introduction of hybrid/PQC mechanisms. 2024–2025.</li>
<li>[17] ETSI TC CYBER QSC. ETSI TS 103 744 – Quantum-Safe Hybrid Key Exchanges. 2020–2024. <a href="https://www.etsi.org/">url</a></li>
<li>[18] ETSI. TS 103 744 – Technical definition of hybrid KEX (Classical KEX + PQC KEM). 2020–2024.</li>
<li>[19] ETSI. TS 103 744 v1.2.1 – &#8220;Clear, testable, and interoperable path to deploy hybrid key establishment today.&#8221; 2024.</li>
<li>[20] Germany (BMI). Draft NIS2UmsuCG – NIS2 Implementation and Cybersecurity Strengthening Act. 2024–2025.</li>
<li>[21] Germany. Finanzmarktdigitalisierungsgesetz (FinmadiG). 2024–2025.</li>
<li>[22] German legal commentaries discussing FinmadiG’s alignment with DORA/MiCAR. 2024–2025.</li>
<li>[23] France. Projet de loi relatif à la résilience des infrastructures critiques et au renforcement de la cybersécurité – Senate Adoption 12 March 2025. 2025.</li>
<li>[24] French legal analyses on NIS2/CER/DORA transposition. 2025.</li>
</ul>
</div>
<div class="paragraph">
<p><a href="#article-title">Back to top</a></p>
</div>
<h4 class="c-s-speaker-info__name">Ixchel Ruiz</h4>
<p class="c-s-speaker-info__tagline">Karakun AG &#8211; Basel, Switzerland</p>
<div class="c-s-speaker-info__bio">
<p>Ixchel Ruiz has been developing software applications and tools since 2000. Her research interests include Java, dynamic languages, client-side technologies, and testing. As a member of the JCP Executive Committee, Java Champion, Oracle ACE Pro, Testcontainers Community Champion, CDF Ambassador, Hackergarten enthusiast, Open Source advocate, public speaker, and mentor, Ixchel is deeply committed to fostering inclusive and collaborative tech communities. She actively mentors aspiring developers and champions initiatives aimed at increasing diversity and accessibility in the technology sector.<br /><br />Ixchel’s work is characterised by a relentless pursuit of innovation, a deep understanding of user needs, and an unwavering commitment to ethical technology development.</p>
</div>
</div>
</div>
</div>



<div id="footer">
<div id="footer-text"> </div>
</div>
<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fbreaking-keys-and-building-trust-the-java-way.html&amp;action_name=Breaking%20keys%20and%20building%20trust%3A%20The%20JAVA%20way%21&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/breaking-keys-and-building-trust-the-java-way.html">Breaking keys and building trust: The JAVA way!</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/breaking-keys-and-building-trust-the-java-way.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6008</post-id>	</item>
		<item>
		<title>Discover Roq, the Quarkus Way for Static Site Generation in Java</title>
		<link>https://www.javaadvent.com/2025/12/discover-roq-the-quarkus-way-for-static-site-generation-in-java.html</link>
					<comments>https://www.javaadvent.com/2025/12/discover-roq-the-quarkus-way-for-static-site-generation-in-java.html#respond</comments>
		
		<dc:creator><![CDATA[Andy Damevin]]></dc:creator>
		<pubDate>Tue, 09 Dec 2025 02:02:47 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[CMS]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[quarkus]]></category>
		<category><![CDATA[ssg]]></category>
		<category><![CDATA[static-site-generator]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5996</guid>

					<description><![CDATA[<p>Did you know about Roq? A powerful new tool that combines Java and Quarkus. Ok, prep a warm drink and put on some soft music and let's find out why Roq is so cool with the comfort of Quarkus Dev Mode and all its eco-system. Bonus: a touch of TailwindCss to make it look great!<br />
<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fdiscover-roq-the-quarkus-way-for-static-site-generation-in-java.html&amp;action_name=Discover%20Roq%2C%20the%20Quarkus%20Way%20for%20Static%20Site%20Generation%20in%20Java&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/discover-roq-the-quarkus-way-for-static-site-generation-in-java.html">Discover Roq, the Quarkus Way for Static Site Generation in Java</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="400" data-attachment-id="6005" data-permalink="https://www.javaadvent.com/2025/12/discover-roq-the-quarkus-way-for-static-site-generation-in-java.html/roq-advent" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?fit=1536%2C1024&amp;ssl=1" data-orig-size="1536,1024" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="roq-advent" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?fit=600%2C400&amp;ssl=1" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?resize=600%2C400&#038;ssl=1" alt="" class="wp-image-6005" style="width:528px;height:auto" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?resize=1024%2C683&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?resize=300%2C200&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?resize=768%2C512&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?resize=1240%2C827&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?resize=508%2C339&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-advent.png?w=1536&amp;ssl=1 1536w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></figure></div>


<div class="wp-block-jetpack-markdown"><p>Just another Static Site Generator (SSG)? Honestly, yes — but Roq is a little different. It’s a thin layer on Quarkus, which gives it a different kind of potential.</p>
<p>I’ve spent time looking at other SSGs in the JavaScript ecosystem (Gatsby, Next.js, Nuxt) and in other languages (Hugo, Jekyll, JBake…). Roq borrows many of their popular features and conventions.</p>
<p>What really stands out, though, is that these SSGs have to re-implement most of the core building blocks inside their framework.</p>
<p>With Quarkus, we already get almost everything we need out of the box — and that’s a key distinction:</p>
<ul>
<li>Quarkus has Qute as Type-Safe template engine, some sugar for Roq.</li>
<li>Roq Plugins and Themes are Quarkus extensions.</li>
<li>Quarkus allow to serve files statically and dynamically.</li>
<li>CDI allows to extend and bind data and templates together.</li>
<li>Quarkus extensions can be used with Roq, the most use-full is the Quarkus Web-Bundler (to bundle script, styles and web deps without any config).</li>
<li>Quarkus test framework.</li>
<li>And Quarkus Dev Mode <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f970.png" alt="🥰" class="wp-smiley" style="height: 1em; max-height: 1em;" /> !</li>
</ul>
<p>Roq is a rock on top of Quarkus:</p>
<ul>
<li>Create endpoints for all your static site based on conventions (dir structure and Frontmatter data).</li>
<li>Allow to define data files (yml or json) and consume them in templates.</li>
<li>Provide plugins and themes.</li>
<li>Add a command to export you Quarkus app as a static site.</li>
<li>A GitHub Action for automation.</li>
<li>Soon: A CMS to manage article and pages from the Quarkus Dev-UI.</li>
</ul>
<p>In this demo, we will install Quarkus and clone a repository, change a few things to see how it reacts.</p>
<h2>Setup</h2>
<p>Make sure you have the JDK 17+ on your machine and install the <a href="https://quarkus.io/guides/cli-tooling">Quarkus CLI</a> using the command bellow:</p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; gutter: false; title: ; notranslate">
# Install the Quarkus CLI
curl -Ls https://sh.jbang.dev | bash -s - trust add https://repo1.maven.org/maven2/io/quarkus/quarkus-cli/
curl -Ls https://sh.jbang.dev | bash -s - app install --fresh --force quarkus@quarkusio
‎
</pre></div>


<div class="wp-block-jetpack-markdown"><p><strong>NOTE:</strong> We started working on a Quarkus Wrapper to allow starting dev-mode and soon also editor mode without anything to install on the machine.</p>
<p><strong>TIP:</strong> You can optionally install <a href="https://quarkus.io/guides/ide-tooling">Quarkus IDE tooling</a> to make the xp even smoother.</p>
<p>I cooked a <a href="https://github.com/ia3andy/the-code-site">demo repo</a> with Quarkus, Roq and Tailwind extensions in the pom.xml:</p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; gutter: false; title: ; notranslate">
# Clone the starter repo (or download):
git clone https://github.com/ia3andy/the-code-site.git
cd the-code-site
‎ 
</pre></div>


<div class="wp-block-jetpack-markdown"><p>You should be all set for the whole journey <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f44c.png" alt="👌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>What did I clone <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f928.png" alt="🤨" class="wp-smiley" style="height: 1em; max-height: 1em;" />?</p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; gutter: false; title: ; notranslate">
the-coder-site/
├── content/
│   ├── index.html                # Website index page and metadata
│   └── **                        # Articles and pages
├── public/images/                # Images for your site
├── web/
│   ├── *.js                      # Scripts (auto-bundled)
│   └── *.css                     # Styles (auto-bundled)
├── templates/
│   ├── layouts/
│   │   ├── default.html          # Base HTML structure
│   │   ├── post.html             # Layout for a blog post
│   │   └── page.html             # Layout for a page
│   └── partials/
│       ├── header.html           # Site header
│       └── footer.html           # Site footer
├── config/application.properties # Site config 
├── pom.xml                       # Quarkus setup (Roq, TailwindCSS)
└── ...                           # Gitignore, Maven Wrapper

</pre></div>


<p>Let&#8217;s start Quarkus Dev-Mode:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; gutter: false; title: ; notranslate">
quarkus dev
‎ 
</pre></div>


<div class="wp-block-jetpack-markdown"><p>When Quarkus starts — after the initial download of its dependencies, press <code>w</code> on you keyboard and let the magic happen!</p>
<p>I suggest you put your browser on your second screen if you have one, this content is also available in your new <a href="http://localhost:8080/posts/discover-roq-the-quarkus-way-for-static-site-generation-in-java/">blog</a> (or in <code>content/posts/2025-01-02-demo.md</code>)</p>
<h2><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Episode 1 &#8211; The Index Page and Live-Reload</h2>
<p>Let’s open <code>content/index.html</code> and have a look.</p>
<p>The first part is the FrontMatter header, it allows to set up the site and provide data for the templates:</p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; highlight: [3]; title: ; notranslate">
---
layout: default
title: Your Name
description: &gt;-
  Personal blog - A programmer sharing thoughts on software development,
  Java, and web technologies.
greeting: Hi, I'm Your Name!
tagline: Just a coder
navigation:
  - title: Blog
    url: /
  - title: Tags
    url: /tags
  - title: About
    url: /about
paginate: posts
---

</pre></div>


<div class="wp-block-jetpack-markdown"><p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f469-1f3fb-200d-1f4bb.png" alt="👩🏻‍💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <strong>Change the <code>title:</code> with your name</strong></p>
<p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f440.png" alt="👀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <em>Switch to the browser and see the change, Live reload should be real quick</em></p>
<p>The <code>layout: default</code> is the template which will wrap this page content, they are defined in <code>template/layouts/</code> but we will see that later.</p>
<p>The content part is in html (because it a <code>.html</code> file), it is pretty straightforward. You can see how pagination on posts happens.</p>
<h2><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f3c4.png" alt="🏄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Episode 2 &#8211; Web-Bundling</h2>
<p>The Quarkus Web Bundler, takes the <code>web/</code> dir stuff and use the <a href="https://mvnpm.org/">mvnpm</a> dependencies, to create a production ready “bundle” for your page. <code>{#bundle /}</code> is included in the default layout and add the resulting script and style html tags.</p>
<p>Let’s give it a ride:</p>
<p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f469-1f3fb-200d-1f4bb.png" alt="👩🏻‍💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <strong>In the <code>web/styles.css</code>, change the <code>@theme { ... }</code> part by this:</strong></p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
@theme {
    --font-sans: 'Atkinson Hyperlegible', system-ui, -apple-system, sans-serif;
    --color-primary: #7c2d12;
    --color-secondary: #9a3412;
    --color-tertiary: #ea580c;
    --color-surface: #fff7ed;
    --color-surface-2: #ffedd5;
    --color-surface-3: #fed7aa;
    --color-card: #ffffff;
    --color-card-border: #fdba74;
    --color-border: #fdba74;
    --color-border-strong: #fb923c;
    --color-code-bg: #fff7ed;
    --color-code-text: #c2410c;
    --color-pre-bg: #ffedd5;
    --color-pre-text: #7c2d12;
    --color-accent-300: #fdba74;
    --color-accent-400: #fb923c;
    --color-accent-500: #f97316;
    --color-accent-600: #ea580c;
    --color-accent-700: #c2410c;
}

</pre></div>


<div class="wp-block-jetpack-markdown"><p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f440.png" alt="👀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <em>Slick right?</em> (you also have a dark mode button in the site if you want to give it a shot)</p>
<p><strong>Note:</strong> The design is using TailwindCSS which is supported by Roq and Quarkus using the <code>quarkus-web-bundler-tailwindcss</code> extension.</p>
<p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f469-1f3fb-200d-1f4bb.png" alt="👩🏻‍💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <strong>In <code>web/app.js</code></strong>, add this in the bottom:</p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
alert('Hello Roq');
 
</pre></div>


<div class="wp-block-jetpack-markdown"><p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f440.png" alt="👀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <em>Check the browser</em> (and then remove it <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f605.png" alt="😅" class="wp-smiley" style="height: 1em; max-height: 1em;" />)</p>
<p>If you have a look to the pom.xml, you’ll see mvnpm deps for <code>hightlightjs</code> and the font <code>Atkinson Hyperlegible</code> used in the css (dependabot will take a good care of them).</p>
<h2><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Episode 3 &#8211; Writing Posts and Pages</h2>
<p>To Create a new page:</p>
<ul>
<li>Create a new file in <code>content/</code> with <code>.md</code> or <code>.html</code> extension.</li>
<li>Add it to the menu in the index page (it will be under <code>[filename]/</code> by default).</li>
<li><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f440.png" alt="👀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>
</ul>
<p>To Create a new post:</p>
<ul>
<li>Create a new file in <code>content/posts</code> with <code>.md</code> or <code>.html</code> extension.</li>
<li><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f440.png" alt="👀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> It is already available in the blog!</li>
<li>Using a FrontMatter header (in yaml between <code>---</code>), add a <code>title</code>, <code>description</code>, some <code>tags</code> (the path is based on a <code>slug</code> of the title by default).</li>
<li>Feel free to also add content to your post.</li>
</ul>
<p><strong>TIP:</strong> You can also create a directory with an <code>index</code> file instead if you want to access relative static files in your page or post.</p>
<p>Ok, let’s have a bit of fun:</p>
<p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f469-1f3fb-200d-1f4bb.png" alt="👩🏻‍💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <strong>open <code>config/application.properties</code>, uncomment the line (remove the <code>#)</code> and go back to the “Blog” page.</strong></p>
<p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f440.png" alt="👀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <em>I didn’t know you could write articles that fast <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></em></p>
<p>This is faker data generation to help you with pagination and tagging (it’s only enabled in dev mode thanks to <code>%dev</code>).</p>
<h2><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f3cc-1f3fb-200d-2640-fe0f.png" alt="🏌🏻‍♀️" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Episode 4 &#8211; Data</h2>
<p>We already covered a lot, let’s quickly cover the rest.</p>
<p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f469-1f3fb-200d-1f4bb.png" alt="👩🏻‍💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <strong>Create <code>data/navigation.yml</code> with the <code>index.html</code> FrontMatter navigation content:</strong></p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
items:
  - title: Blog
    url: /
  - title: Tags
    url: /tags
  - title: About
    url: /about

</pre></div>


<div class="wp-block-jetpack-markdown"><p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f469-1f3fb-200d-1f4bb.png" alt="👩🏻‍💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <strong>In <code>templates/partials/header.html</code> change this:</strong></p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; gutter: false; highlight: [2]; title: ; notranslate">
- {#for item in site.data.navigation}
+ {#for item in cdi:navigation.items}

</pre></div>


<div class="wp-block-jetpack-markdown"><p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f440.png" alt="👀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <em>Congratulation, you didn’t change a thing <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f605.png" alt="😅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></em></p>
<p><strong>Tip</strong> You can also <a href="https://docs.quarkiverse.io/quarkus-roq/dev/quarkus-roq-data.html">map this data to a structure</a> (Java class or record) for type-safety and making sure your data is meeting expectations.</p>
<h2><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f971.png" alt="🥱" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Episode 5 &#8211; Templates: Layouts, Partials, Tags and Extensions</h2>
<p>This is a bit boring but important to know.</p>
<p>Layouts let you share and reuse parts of the HTML around your content — headers, footers, wrappers — so pages only provide the unique content while layouts handle the surrounding structure.</p>
</div>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="600" height="582" data-attachment-id="6006" data-permalink="https://www.javaadvent.com/2025/12/discover-roq-the-quarkus-way-for-static-site-generation-in-java.html/roq-layouts" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?fit=1356%2C1316&amp;ssl=1" data-orig-size="1356,1316" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="roq-layouts" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?fit=300%2C291&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?fit=600%2C582&amp;ssl=1" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?resize=600%2C582&#038;ssl=1" alt="" class="wp-image-6006" style="width:546px;height:auto" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?resize=1024%2C994&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?resize=300%2C291&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?resize=768%2C745&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?resize=1240%2C1203&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?resize=508%2C493&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/roq-layouts.png?w=1356&amp;ssl=1 1356w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></figure></div>


<div class="wp-block-jetpack-markdown"><p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f469-1f3fb-200d-1f4bb.png" alt="👩🏻‍💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <strong>In <code>templates/layouts/default.html</code> replace the <code>{#insert /}</code> this in the <code>&lt;main&gt;</code> by this:</strong></p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; gutter: false; title: ; notranslate">
&lt;h1 class=&quot;text-4xl font-bold tracking-tight sm:text-5xl&quot;&gt;One template to rule them all <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f48d.png" alt="💍" class="wp-smiley" style="height: 1em; max-height: 1em;" />&lt;/h1&gt;
 
</pre></div>


<div class="wp-block-jetpack-markdown"><p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f440.png" alt="👀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>›</strong> <em>All pages in the site is now showing this message</em></p>
<p>Partials (located in <code>templates/partials/</code>) let you reuse small HTML/Qute snippets—like a header, a footer, a card, a pagination block, or a meta block. Instead of repeating the same HTML everywhere, you include them with <code>{#include partials/… /}</code>, keeping layouts and pages clean and consistent.</p>
<p>Tags (located in <code>templates/tags/</code>) are small, self-contained components you can call from any template. They behave like mini-templates with parameters, useful for things like buttons, cards, or repeated UI fragments. You invoke them using Qute’s <code>{#your-tag foo=&quot;bar&quot;}</code> syntax, and they keep your templates much cleaner by replacing boilerplate HTML with a reusable tag definition.</p>
<p><code>@TemplateExtension</code> <a href="https://quarkus.io/guides/qute-reference#template_extension_methods">methods</a> can be used to extend the data classes with new functionality from Java (to extend the set of accessible properties and methods). For example, it is possible to add computed properties and virtual methods.</p>
<h2><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Episode 6 &#8211; Themes</h2>
<p>If you create a <a href="https://code.quarkus.io/?a=roq-with-blog&amp;e=io.quarkiverse.roq%3Aquarkus-roq">Roq app</a> using Code Quarkus, you’ll notice that you get a fully styled, well-structured website without writing any template or CSS yourself. That’s because Roq allow to use themes, which provide all the building blocks: layouts, components, styles, scripts, and templates.</p>
<p><a href="https://iamroq.com/docs/themes/">Roq themes</a> are deeply overridable, letting you replace or extend only what you need while keeping the rest intact. This keeps your project focused on the content, not the design system. Whenever you want to adjust a layout, change a component, or tweak the styling, you simply override that part in your project, and the rest of the theme continues to work seamlessly.</p>
<p>You can also create your own. The process is very similar to what we’ve seen in this demo: you define your layouts, templates, components, and styles, and Roq takes care of wiring everything together. This gives you full freedom to shape the look and feel of your site while still benefiting from Roq’s structure and conventions.</p>
<h2><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f5ff.png" alt="🗿" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Season Finale – Publishing</h2>
<p>Up to this point, you haven’t actually generated anything — you’ve just been using Quarkus to build and render your app. Add a Java service, plug in a database with Quarkus extensions, and it works fine. That’s a different path, though, because you’ll need a server to run it.</p>
<p>For static site generation, you only need static files to run on a static server. Roq makes this simple by providing a command for your CI or a GitHub Action. Learn more about publishing with Roq <a href="https://iamroq.com/docs/publishing/">here</a>.</p>
<h2>Conclusion</h2>
<p>I hope you enjoyed the demo and consider using Roq for your next site. If you want to show your support, give <a href="https://github.com/quarkiverse/quarkus-roq">Roq a star on GitHub</a>.</p>
</div>



<div class="wp-block-jetpack-markdown"><p>The Roq <a href="https://iamroq.com/roqers/">users</a> and <a href="https://github.com/quarkiverse/quarkus-roq?tab=readme-ov-file#contributors-">community</a> are growing, and I hope to see you there soon <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" />.</p>
<p>Plenty of new things are coming in the next few months — a CMS, i18n for collections, a dead-link checker, an mkdocs theme, and more.</p>
<p>If you spot issues, have ideas for cool features, or want to contribute, you’re more than welcome <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2764.png" alt="❤" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</div>



<p></p>
<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fdiscover-roq-the-quarkus-way-for-static-site-generation-in-java.html&amp;action_name=Discover%20Roq%2C%20the%20Quarkus%20Way%20for%20Static%20Site%20Generation%20in%20Java&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/discover-roq-the-quarkus-way-for-static-site-generation-in-java.html">Discover Roq, the Quarkus Way for Static Site Generation in Java</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/discover-roq-the-quarkus-way-for-static-site-generation-in-java.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5996</post-id>	</item>
		<item>
		<title>Nice and Naughty Cases of Pattern Matching</title>
		<link>https://www.javaadvent.com/2025/12/nice-and-naughty-cases-of-pattern-matching.html</link>
					<comments>https://www.javaadvent.com/2025/12/nice-and-naughty-cases-of-pattern-matching.html#respond</comments>
		
		<dc:creator><![CDATA[Cay Horstmann]]></dc:creator>
		<pubDate>Mon, 08 Dec 2025 02:02:26 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[pattern matching]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5990</guid>

					<description><![CDATA[<p>Since Java 14, the Java switch and instanceof statements have been enhanced, in multiple phases, to support pattern matching and a &#8220;data-oriented&#8221; programming style. In this article, I explore when this programming style is beneficial, and why. I look at the sweet spot of perfect pattern usage, absolute antipatterns where it should not be used, [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fnice-and-naughty-cases-of-pattern-matching.html&amp;action_name=Nice%20and%20Naughty%20Cases%20of%20Pattern%20Matching&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/nice-and-naughty-cases-of-pattern-matching.html">Nice and Naughty Cases of Pattern Matching</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img data-recalc-dims="1" style='width: 30%;float: right' src="https://i0.wp.com/horstmann.com/unblog/2025-12-12/naughty-nice.webp?w=600&#038;ssl=1" /></p>
<p>Since Java 14, the Java switch and instanceof statements have been enhanced, in multiple phases, to support pattern matching and a &#8220;data-oriented&#8221; programming style. In this article, I explore when this programming style is beneficial, and why. I look at the sweet spot of perfect pattern usage, absolute antipatterns where it should not be used, no matter how many examples you see in blogs and conference presentations, and corner cases where the switch syntax clashes with legacy behavior.</p>
<h2>Sealed Hierarchies and Records are Nice</h2>
<p>Pattern matching is one of the shiny new objects in the Java language. Maybe not that new anymore—it started with Java 14. And maybe not that shiny. How many times have you used it in your code?</p>
<p>There may be a reason. Pattern matching works best with a sealed hierarchy of interfaces and record types. (If you want to show off, you can call them “algebraic data types”.) Everywhere that you have such a hierarchy, pattern matching is a natural tool.</p>
<p>How many such hierarchies do you have in your code base? Well, that may explain why you are not often reaching for that tool.</p>
<p>Some people argue that you should actively organize your data into such a form. This is sometimes called <em>data-oriented</em> programming, and it can be a good idea when it fits the problem domain. For examples with a business context, I can recommend <a href='https://livebook.manning.com/book/data-oriented-programming-in-java'>this book </a> by Chris Kiehl, currently in early access. He discusses real-life scenarios, such as</p>
<pre>public sealed interface Lifecycle {
    record Pending() implements Lifecycle {}
    record Billed(String invoiceId) implements Lifecycle {}
    record Rejected(Reason reason) implements Lifecycle {}
    record InReview(ApprovalId approvalId) implements Lifecycle {}
}
</pre>
<p>Since these scenarios require a fair amount of domain knowledge, let me use a simple and familiar example: JSON values. There are four kinds of primitive values, and arrays and objects. </p>
<p><img data-recalc-dims="1" src="https://i0.wp.com/horstmann.com/unblog/2023-09-19/json.png?w=600&#038;ssl=1" alt='.png' loading='lazy' /></p>
<p>Make the leaves of the inheritance tree into records, or, if they only have finitely many instances, into enums. And all other types into sealed interfaces:</p>
<pre>sealed interface JSONValue {}
sealed interface JSONPrimitive extends JSONValue {}

enum JSONBoolean implements JSONPrimitive { FALSE, TRUE; }
enum JSONNull implements JSONPrimitive { INSTANCE; }

record JSONNumber(double value) implements JSONPrimitive {}
record JSONString(String value) implements JSONPrimitive {}

record JSONArray(List&lt;JSONValue&gt; values) implements JSONValue {}
record JSONObject(Map&lt;String, JSONValue&gt; entries) implements JSONValue {}
</pre>
<p>Now we can use pattern matching:</p>
<pre>static String quote(String s) {
    return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
}

static String stringify(JSONValue j) {
    return <b>switch (j)</b> {
        case JSONNumber(var v) -&gt; "" + v;
        case JSONString(var s) -&gt; quote(s);
        case JSONBoolean.TRUE -&gt; "true";
        case JSONBoolean.FALSE -&gt; "false";
        case JSONNull.INSTANCE -&gt; "null";
        case JSONArray(var values) -&gt;
            values.stream()
            .map(this::stringify)
            .collect(Collectors.joining(",", "[", "]"));
        case JSONObject(var entries) -&gt;
            entries.entrySet()
            .stream()
            .map(e -&gt; quote(e.getKey()) + ":" + stringify(e.getValue()))
            .collect(Collectors.joining(",", "{", "}"));
    };
}
</pre>
<p>This is a switch <em>expression</em>. Each case yields a value (after the <code>-&gt;</code> token). The expression <code>switch (j) { ... }</code> yields the value of the matching case. The <code>return</code> statement returns that value.</p>
<p>The value in parentheses in <code>switch (<b>j</b>)</code> is called the <em>selector</em>. In our case, the type of the selector <code>j</code> is the <code>JSONValue</code> interface.</p>
<p>Note the <em>record patterns</em>, such as:</p>
<pre>    case JSONNumber(<b>var v</b>) -&gt; "" + v;
</pre>
<p>If <code>j</code> is a <code>JSONNumber</code>, the variable <code>v</code> is set to the record component. The type of <code>v</code> is the component type, in this case <code>double</code>.</p>
<p>Also note that some cases are enum instances, such as <code>case JSONBoolean.TRUE -&gt; ...</code>. These are called <em>constant patterns</em>.</p>
<p>Finally, note that the switch is <em>exhaustive</em>. It covers all possible values for the selector. All switch expressions must be exhaustive. Because no matter what the selector, the expression must have a value.</p>
<p>Ok, not all values are covered. What if the selector <code>j</code> is <code>null</code>? Then a <code>NullPointerExpression</code> is thrown. If <code>j</code> is <code>new JSONString(null)</code>, then <code>s</code> is <code>null</code>, also causing an NPE. That is just to be expected. Generally, <code>null</code> is exempted from exhaustiveness checking because it would be too exhausting to check for them, particularly in nested positions.</p>
<p>Why is pattern matching nice? The object-oriented alternative would have been to add a <code>stringify</code> method to all levels of the hierarchy:</p>
<ul>
<li>as an abstract or default method in each interface</li>
<li>as a concrete method in each record or enum</li>
</ul>
<p>That is easy enough to do—after all, it is a sealed hierarchy. But it has two drawbacks. First, only the owner of the hierarchy can add methods. And the logic of the action, here, stringification, is sprinkled over multiple classes.</p>
<p>By using an external method and pattern matching, the logic is all in one place. And anyone, not just the hierarchy owner, can go forth and pattern match. Without the need of a <a href='https://en.wikipedia.org/wiki/Visitor_pattern'>visitor pattern</a>. This is good.</p>
<h2>Future Niceness</h2>
<p><code>Optional</code> could have been declared as a sealed interface whose subtypes are a record <code>Optional.Of</code> and an enum <code>Optional.Empty</code>. Then you could use code like this:</p>
<pre>var result = switch(stream.max(comparator)) { // Not actually
   case Optional.Of(x) -&gt; x;
   case Optional.Empty.INSTANCE -&gt; someDefault;
};</pre>
<p>Of course, that is not how <code>Optional</code> actually works. But <a href='https://openjdk.org/projects/amber/design-notes/patterns/towards-member-patterns'>there are plans</a> to make <em>deconstruction</em> work with arbitrary classes. Then you will be able to write something like this:</p>
<pre>var result = switch(stream.max(comparator)) { // Maybe soon
   case Optional.of(x) -&gt; x;
   case Optional.empty() -&gt; someDefault;
};</pre>
<p>The details are in flux, so I won’t belabor them. Once available, such “member patterns” (or whatever they will end up being called) will make pattern matching practical for a wider set of classes.</p>
<h2>Naughty Fallthrough</h2>
<p>The classic <code>switch</code> statement, which came to Java via C and C++, has a single raison d’être: to allow the compiler to construct a jump table.</p>
<p><img src='https://horstmann.com/unblog/2025-12-12/jumptable.drawio.svg' /></p>
<p>If the labels fall in a compact range, the jump addresses can be in an array. Otherwise, the table is an array of pairs (label, address), sorted by label. Binary search finds the matching case.</p>
<p>As of Java 5, labels can also be strings. Then the jump table contains the hash, and each jump target checks if the string actually matches. </p>
<p>The jump table also explains the <em>fallthrough</em> behavior. After jumping to the code of the case, the program keeps running, until a <code>break</code> causes a jump to the end of the statement. Or, if there is no <code>break</code>, it keeps running with the instructions of the next case. Which is almost always unintended, and a common error. Stay away from it.</p>
<p>Java 14 gave us four forms of switch. The classic statement. A lovely new <code>switch</code> expression. And a switch statement without fallthrough. Also a <code>switch</code> expression with fallthrough—very naughty. That was only added in an effort to make the language more regular.</p>
<p>My advice: If you want a jump table, use the new <code>switch</code> statement without fallthrough. Simply use <code>-&gt;</code> tokens instead of <code>:</code>, and drop the <code>break</code> statements.</p>
<pre>switch (c) {
    case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -&gt; {
        ndigit[c-'0']++;
    }
    case ' ', '\n', '\t' -&gt; {
        nwhite++;
    }
    default -&gt; {
        nother++;
    }
}
</pre>
<p>If you want pattern matching, use a <code>switch</code> expression. For sure without fall-through.</p>
<h2>Type Patterns</h2>
<p>You have seen record patterns (matching a record and extracting its components), as well as the more general member patterns of the future.</p>
<p>Another pattern, called <em>type pattern</em>, checks whether the selector expression has a particular type. It then binds a variable to the type cast:</p>
<pre>Object x = ...;
Object doubled = switch (x) {
    case String s -&gt; s + s; // s is a String
    case Number n -&gt; n.doubleValue() * 2; // n is a Number
    default -&gt; List.of(x, x);
}
</pre>
<p>Of course, this example is completely artificial. When is the last time you had business logic that made type tests like this?</p>
<p>The blogosphere is full of examples like this, in order to illustrate the finer points of <code>switch</code>. Or to gleefully present puzzles that explore cruel conflicts between classic and modern syntax and semantics. <a href='https://horstmann.com/presentations/2025/jcrete/index.html#(9)'>Not that I would ever do such a thing</a>.</p>
<p>It is not common to write code that starts with an <code>Object</code> and then narrows down the type. If that is important for you, go ahead and learn more about type patterns. But if you feel the need for the occasional <code>instanceof</code>, just stick with it.</p>
<p>In fact, <code>instanceof</code> has gotten better. A classic code snippet such as</p>
<pre>if (x instanceof String) {
    String s = (String) x;
    <var>Do something with</var> s
}
</pre>
<p>is more easily expressed in modern Java as</p>
<pre>if (x instanceof String s) {
    <var>Do something with</var> s
}
</pre>
<h2>Primitive Patterns</h2>
<p>The classic <code>switch</code> statement in Java 1.0 permitted selector types <code>int</code>, <code>short</code>, <code>char</code>, and <code>byte</code>. Why not <code>long</code>, <code>float</code>, <code>double</code>, or <code>boolean</code>? They aren’t all that useful with jump tables.</p>
<p>As of Java 25, the selector type can be any type, except for those four types. A proposal, now in its <a href='https://openjdk.org/jeps/530'>fourth preview</a>, aims to remedy this anomaly. To make the language more regular.</p>
<p>If you just use constant case labels, this is unsurprising.</p>
<pre>double x = ...;
String result = switch (x) { // JEP 530 allows selector of type double
    case 3.141592653589793 -&gt; "π";
    case 1.4142135623730951 -&gt; "√2";
    default -&gt; "something else";
};
</pre>
<p>But there are also <em>primitive patterns</em>:</p>
<pre>result = switch (x) {
    case <b>int n</b> when n % 2 == 0 -&gt; "an even integer";
    case <b>float _</b> -&gt; "a float";
    default -&gt; "something else";
};
</pre>
<p>The selector is a 64-bit <code>double</code>. The first <code>case</code> tests whether it actually represents a 32-bit <code>int</code>. The second <code>case</code> checks if fits into a 32-bit <code>float</code> without losing any bits of information. To fully understand the latter, you need to be familiar with the internals of the IEEE 754 floating-point standard.</p>
<p>What if you also toss in some wrapper types? Of course, I would never do this. Just kidding, I certainly would in the interest of creating yet another naughty puzzler. But <a href='https://mail.openjdk.org/pipermail/amber-dev/2025-September/009404.html'>Simon Ritter beat me to it:</a></p>
<pre>int x = ...;
switch (x) {
    case Integer i -&gt; System.out.println("int");
    case byte b -&gt; System.out.println("byte");
}</pre>
<p>This should not compile. After all, the second case can never happen, and pattern matching is generally good about flagging such <em>dominance</em>. </p>
<p>But it does compile. In this instance, poor <code>switch</code> is getting overwhelmed. There is so much historical baggage that must be respected. And sometimes the results are counterintuitive.</p>
<p>Do not mix primitive patterns and type patterns in the same <code>switch</code>. They do completely different things. A primitive pattern checks whether a value can be <em>converted</em> to a different type. A type pattern checks whether a value <em>belongs</em> to a different type.</p>
<p>The conversion tests can be useful, but in many practical situations, they work better with <code>instanceof</code>:</p>
<pre>int x = ...;
if (x instanceof byte b) {
    out.write(b);
} else { // x is not between -128 and 127
    ...
}</pre>
<p>Ok, maybe it’s not that useful. Normally you have bytes between 0 and 255. But that’s another story.</p>
<p>Project Valhalla promises to let us define our own types that act like primitive types, such as long double, short float, unsigned byte. It is not yet clear how pattern matching will work with those types, but I would not be surprised if it was complex and a fertile ground for nasty puzzlers.</p>
<p>Right now, there is a lot of noise about primitive patterns, because it is a new feature. But it is unlikely to impact many programmers. Except as pitfalls. Consider this:</p>
<pre>JSONObject o = ...;
var result = switch (o) {
   case JSONNumber(int x) -&gt; x;
   case JSONNull.INSTANCE -&gt; 0;
   default -&gt; throw new IllegalArgumentException();
};
</pre>
<p>Did the programmer really mean <code>case JSONNumber(<b>int</b> x)</code>?  It is an <a href='https://mail.openjdk.org/pipermail/amber-spec-observers/2025-September/004562.html'>easy mistake</a> to accidentally write <code>int</code> instead of <code>double</code>. Before primitive patterns, the compiler rejected this. Now it has an exciting new meaning: Is <code>o</code> an instance of <code>JSONNumber</code> whose <code>value</code> component is actually an integer? This can be useful, of course, if intended. But what if it isn’t?</p>
<p>Tip: Get into the habit of always using <code>var</code> with record patterns. Then you don’t run into this issue.</p>
<h2>Constant Patterns</h2>
<p>In our sealed JSON hierarchy, we had a mixture of record patterns and enum constant patterns:</p>
<pre>case JSONNumber(var v) -&gt; "" + v;
case <b>JSONBoolean.TRUE</b> -&gt; "true"; // a constant pattern
</pre>
<p>And that’s fine.</p>
<p>A classic jump table switch only has constant patterns. That’s fine to.</p>
<p>For now, constant patterns have an unfortunate limitation: they don’t nest.</p>
<pre>record Point(int x, int y) {}
Point p = ...;
var result = switch (p) {
    case Point(<b>0</b>, _) -&gt; "on x-axis"; // ERROR, can't nest constant pattern
    ...
}</pre>
<p>You have to write:</p>
<pre>var result = switch (p) {
    case Point(x, _) <b>when x == 0</b> -&gt; "on x-axis";
    ...
}
</pre>
<p>This limitation may get fixed at some point in the future.</p>
<p>Even with top-level constant patterns, the rules can get pretty arcane. For example, what is wrong with this?</p>
<pre>Object x = ...;
String result = switch (x) {
   case "" -&gt; "empty";
   case 0 -&gt; "zero";
   case JSONNull.INSTANCE -&gt; "null";
   default -&gt; "something else";
};
</pre>
<p>You can only use string cases when the selector type is <code>String</code>, and integer cases when the selector type is <code>int</code> or <code>Integer</code>. Or <code>short</code> or <code>char</code> or <code>byte</code>. But not <code>long</code>, <code>double</code>, or <code>float</code>. Here the selector type is <code>Object</code>, so they are not allowed. Yet, with an <code>Object</code> selector, <code>enum</code> constants are ok.</p>
<p>You are unlikely to run into real-life <code>switch</code> expressions with <code>Object</code> or <code>Integer</code> selectors, so this too is more of an issue for puzzlers than real-life scenarios.</p>
<h2>Conclusion</h2>
<p>There is a lot going on with modern <code>switch</code>, and some usages are nicer than others.</p>
<p>The sweet spot is the “sealed hierarchies of records and enums” use case. For bragging rights, call it “algebraic data types”.</p>
<p>In the future, that convenience will be extended to other classes such as <code>Optional</code>.</p>
<p>For other type tests, prefer the modern form of <code>instanceof</code> over a <code>switch</code> with type patterns.</p>
<p>Also, if you want to convert between primitive types, try the new <code>instanceof</code> form first.</p>
<p>If you use jump tables, that’s totally fine. But refactor without fallthrough.</p>
<p>Blogs and puzzler presentations will delight in exploring the interactions between classic and “enhanced” switches, which can get arcane and complex. (<a href='https://mail.openjdk.org/pipermail/amber-dev/2018-April/002929.html'>Nobody could have predicted that</a>.) </p>
<p>Don’t let that scare you away from using pattern matching. It is a truly useful feature, and it is well worth organizing appropriate parts of your code with pattern matching in mind.<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fnice-and-naughty-cases-of-pattern-matching.html&amp;action_name=Nice%20and%20Naughty%20Cases%20of%20Pattern%20Matching&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/nice-and-naughty-cases-of-pattern-matching.html">Nice and Naughty Cases of Pattern Matching</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/nice-and-naughty-cases-of-pattern-matching.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5990</post-id>	</item>
		<item>
		<title>Java Hello World, LLVM Edition</title>
		<link>https://www.javaadvent.com/2025/12/java-hello-world-llvm-edition.html</link>
					<comments>https://www.javaadvent.com/2025/12/java-hello-world-llvm-edition.html#respond</comments>
		
		<dc:creator><![CDATA[James Hamilton]]></dc:creator>
		<pubDate>Sun, 07 Dec 2025 02:02:37 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[Java Advent]]></category>
		<category><![CDATA[jdk]]></category>
		<category><![CDATA[JVM]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5872</guid>

					<description><![CDATA[<p>After exploring Java bytecode in previous years (2022, 2023, 2024), this year we&#8217;ll take an unexpected detour for a Java advent: instead of generating Java bytecode, we’ll use Java to build and execute LLVM IR, the intermediate language behind compilers like clang. Using Java’s Foreign Function &#38; Memory (FFM) API, we’ll call the LLVM C [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fjava-hello-world-llvm-edition.html&amp;action_name=Java%20Hello%20World%2C%20LLVM%20Edition&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/java-hello-world-llvm-edition.html">Java Hello World, LLVM Edition</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>After exploring Java bytecode in previous years (<a href="https://www.javaadvent.com/2022/12/jvm-hello-world.html">2022</a>, <a href="https://www.javaadvent.com/2023/12/my-first-compiler.html">2023</a>, <a href="https://www.javaadvent.com/2024/12/peering-through-the-peephole-build-a-peephole-optimiser-using-the-new-java-class-file-api.html">2024</a>), this year we&#8217;ll take an unexpected detour for a Java advent: instead of generating Java bytecode, we’ll use Java to build and execute <a href="https://llvm.org/docs/LangRef.html">LLVM IR</a>, the intermediate language behind compilers like clang.</p>



<p>Using Java’s <a href="https://docs.oracle.com/en/java/javase/22/core/foreign-function-and-memory-api.html">Foreign Function &amp; Memory (FFM) API</a>, we’ll call the LLVM C API, generate a “Hello, World!” program, and even JIT-compile it to native code &#8211; all from Java.</p>



<p>The task is simple: create a program that simply prints “Hello, World!”. But we must do this from Java via LLVM.</p>



<h2 class="wp-block-heading">What is LLVM?</h2>



<p>The <a href="https://llvm.org/">LLVM Project</a>, a collection of modular compiler and toolchain technologies, began as a research project over 20 years ago at the University of Illinois. It has grown significantly, underpinning many compilers and tools like clang.</p>



<p>The core libraries provide a source &amp; target independent optimizer along with code generation for a multitude of target machines. They are built around the <a href="https://llvm.org/docs/LangRef.html">LLVM IR</a>, an intermediate representation, which we’ll generate &amp; execute from Java.</p>



<h2 class="wp-block-heading">Installing LLVM</h2>



<p>To use the LLVM C API from Java, we’ll need LLVM’s shared libraries and headers installed locally. There is an automatic installation script available to easily install LLVM on Ubuntu/Debian systems, for example to install LLVM 20:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ wget https://apt.llvm.org/llvm.sh
$ chmod +x llvm.sh
$ ./llvm.sh 20
</pre></div>


<p>Once we have LLVM installed we can use the LLVM tooling to execute textual-form LLVM IR and we’ll also be able to use the LLVM C API in Java via the FFM API.</p>



<h2 class="wp-block-heading">LLVM IR</h2>



<p>LLVM IR is a strongly-typed, SSA-based intermediate language. It abstracts away most machine-specific details, making it easier to represent high-level constructs in a compiler-friendly format. There are three equivalent representations of the IR: an in-memory format, a bitcode format for serialisation and<a href="http://llvm.org/docs/LangRef.html"> a human readable assembly language representation</a>.</p>



<p>The textual form of the LLVM IR for our “Hello, World!” looks like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: cpp; title: ; notranslate">
@str = private constant &#x5B;14 x i8] c&quot;Hello, World!\00&quot;

declare i32 @puts(ptr)

define i32 @main() {
  call i32 @puts(ptr @str)
  ret i32 0
}
</pre></div>


<p>Eventually, we’ll generate this via Java but, for now, if you save this in a file called helloworld.ll you can try executing it with the LLVM interpreter, <a href="https://llvm.org/docs/CommandGuide/lli.html">lli</a>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ lli helloworld.ll
Hello, World!
</pre></div>


<p>There are a few types of entities used in the helloworld.ll example:</p>



<ul class="wp-block-list">
<li>A global variable containing the string “Hello World!”</li>



<li>A declaration of the external <a href="https://man7.org/linux/man-pages/man3/puts.3.html">libc puts</a> function</li>



<li>A definition of the main function</li>



<li>Instructions to call puts and return an integer exit code</li>
</ul>



<p>You can dive deeper into the <a href="https://jameshamilton.eu/programming/llvm-hello-world">LLVM “Hello, World!” example here</a> if you like before continuing to the next section, where we’ll start using the Java FFM API.</p>



<h2 class="wp-block-heading">What is the Java FFM API?</h2>



<p>The <a href="https://docs.oracle.com/en/java/javase/22/core/foreign-function-and-memory-api.html">Foreign Function and Memory (FFM) API</a> enables Java programs to interoperate with code and data outside the Java runtime. The API is a replacement for the older JNI API that enables Java programs to call native libraries in a safer way. The API can be used to call foreign functions and safely access foreign memory that is not managed by the JVM.</p>



<p>A companion to the FFM API is a tool named <a href="https://docs.oracle.com/en/java/javase/21/core/call-native-functions-jextract.html">jextract</a> that can automatically generate Java bindings from a C header file. <code>jextract</code> parses C header files and automatically generates the Java source code with method handles and type-safe FFM bindings.</p>



<p>We’ll use the <code>jextract</code> tool to generate bindings for the LLVM C API and those bindings will allow us to call the LLVM API from Java.</p>



<h2 class="wp-block-heading">Getting started</h2>



<p>First, let’s create a simple project to start. We’ll use maven to build our project but you can use another build tool if you like, it’s not important:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ mvn archetype:generate -DgroupId=com.example -DartifactId=jvm-llvm-helloworld -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
</pre></div>


<p>Once you have a project skeleton, update the pom.xml file to set the Java version &gt;= 22:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
 &lt;properties&gt;
    &lt;maven.compiler.source&gt;25&lt;/maven.compiler.source&gt;
    &lt;maven.compiler.target&gt;25&lt;/maven.compiler.target&gt;
 &lt;/properties&gt;
</pre></div>


<p>Then build and run the program to check everything is OK:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ mvn clean install
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App
Hello World!
</pre></div>


<p>The maven generated sample already printed “Hello, World!” but that’s too easy! We’ll remove that and generate it via LLVM in the following sections.</p>



<p>Let’s now create the LLVM bindings using <code>jextract</code> so that we can use the LLVM API.</p>



<h2 class="wp-block-heading">Creating LLVM bindings</h2>



<p>We’ll use jextract to generate bindings from the LLVM C API header files. Make sure LLVM is available on your system (see Installing LLVM above) and you’ll also need to download <a href="https://docs.oracle.com/en/java/javase/21/core/call-native-functions-jextract.html">jextract</a>.</p>



<p>The following jextract command (on Linux) will create Java bindings for the specified LLVM C headers, placing the generated code into the <code>com.example.llvm</code> package within the <code>src/main/java</code> directory, with the main header class named <code>LLVM</code>.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ jextract -l LLVM-20 -I /usr/include/llvm-c-20 \
     -I /usr/include/llvm-20 \
     -t com.example.llvm \
     --output src/main/java \
     --header-class-name LLVM \
     /usr/include/llvm-c-20/llvm-c/Core.h \
     /usr/include/llvm-c-20/llvm-c/Support.h \
     /usr/include/llvm-c-20/llvm-c/ExecutionEngine.h \
     /usr/include/llvm-c-20/llvm-c/Target.h \
     /usr/include/llvm-c-20/llvm-c/TargetMachine.h
</pre></div>


<p>To test the generated bindings, let’s print the LLVM version using the static method generated for LLVM version string constant: edit the sample’s App.java file to print the version using the following:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
package com.example;

import static com.example.llvm.LLVM.LLVM_VERSION_STRING;

/**
 * LLVM Hello world!
 *
 */
public class App 
{
  public static void main(String&#x5B;] args)
  {
    var version = LLVM_VERSION_STRING();
    System.out.println(&quot;LLVM version: &quot; + version.getString(0));
  }
}

</pre></div>


<p>If you run this, you’ll see the LLVM version printed:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar --enable-native-access=ALL-UNNAMED com.example.App
LLVM version: 20.0.0
</pre></div>


<p>Note the use of <code>--enable-native-access=ALL-UNNAMED</code> to prevent warnings about native code access; I’ll omit this for brevity in later commands.</p>



<h2 class="wp-block-heading">Memory Segments</h2>



<p>The <code>LLVM_VERSION_STRING</code> method returns a <a href="https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/MemorySegment.html">MemorySegment</a> rather than a Java String. In the FFM API, a <code>MemorySegment</code> represents a contiguous region of memory—either on or off the Java heap—enabling safe, structured access to native memory.</p>



<p>Let’s take a look at the implementation in the generated source file:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
   public static MemorySegment LLVM_VERSION_STRING() {
    class Holder {
      static final MemorySegment LLVM_VERSION_STRING
         = LLVM.LIBRARY_ARENA.allocateFrom(&quot;20.0.0&quot;);
    }
    return Holder.LLVM_VERSION_STRING;
  }
</pre></div>


<p>This method allocates memory containing the version string that contains the version number. The allocated MemorySegment is returned from the method and to get the String back into Java-land we need to call <code>getString(0)</code> on the memory segment which reads a null-terminated string at the given offset (<code>0</code>), using the UTF-8 charset.</p>



<p>Memory segments are managed through arenas (such as the <code>LLVM.LIBRARY_ARENA</code> in the code above), which bridge Java&#8217;s managed heap and foreign memory spaces by applying familiar resource management patterns like try-with-resources.</p>



<p>Since we’ll need to allocate native memory, let’s declare an <a href="https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/Arena.html">Arena</a>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
 public static void main(String&#x5B;] args)
 {
    try (Arena arena = Arena.ofConfined()) {
       // TODO
    }
 }
</pre></div>


<h2 class="wp-block-heading">Creating an LLVM module</h2>



<p>As a reminder, we need to recreate the following LLVM IR via the LLVM C API:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: cpp; title: ; notranslate">
declare i32 @puts(ptr)

@str = constant &#x5B;14 x i8] c&quot;Hello, World!\00&quot;

define i32 @main() {
  call i32 @puts(ptr @str)
  ret i32 0
}
</pre></div>


<p>Let’s start by creating an LLVM module &#8211; the container for all functions and globals &#8211; and print it so that we can run it through the LLVM interpreter:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // TODO: Fill in the module
            
  	var llvmIrCharPtr = LLVMPrintModuleToString(module);

    try {
      System.out.println(llvmIrCharPtr.getString(0));
    } catch (Exception e) {
      System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    }

	   // Clean up LLVM resources
     LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>If we execute this now, we’ll see an empty IR module:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App
; ModuleID = 'hello'
source_filename = &quot;hello&quot;
</pre></div>


<p>If you pass this output through the LLVM interpreter, you’ll see that it tries to execute the module but cannot find the entry point main function:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App | lli
Symbols not found: &#x5B; main ]
</pre></div>


<p>We now have an LLVM module, but it has no executable code &#8211; the interpreter rightly complains that main is missing; so let’s add the main function.</p>



<h2 class="wp-block-heading">Adding a main function</h2>



<p>The entry point to our program is the function named main which takes no parameters and returns an integer exit code, where a non-negative integer denotes success. We can add a function to the module using the <a href="https://llvm.org/doxygen/group__LLVMCCoreModule.html#gaaf70ab92a261e636dc0b2cf30cfede9a">LLVMAddFunction</a> function, along with the <a href="https://llvm.org/doxygen/classllvm_1_1FunctionType.html">LLVMFunctionType</a> and <a href="https://llvm.org/doxygen/group__LLVMCCoreTypeInt.html#ga71ee1444644798c8750ffb5be6a06819">LLVMInt32Type</a> functions to create the function type. </p>



<p>Notice that all of these functions return a <code>MemorySegment</code> and all 3 <code>LLVMAddFunction</code> parameters are <code>MemorySegment</code>s.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [6,7,8,9,10,11,12]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

    // TODO: Add the code

  	var llvmIrCharPtr = LLVMPrintModuleToString(module);

    try {
      System.out.println(llvmIrCharPtr.getString(0));
    } catch (Exception e) {
      System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    }

	   // Clean up LLVM resources
     LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>If you execute this now you’ll see a declaration of the main function but it has no body so the LLVM interpreter will produce the same error:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App
; ModuleID = 'hello'
source_filename = &quot;hello&quot;

declare i32 @main()

$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App|lli
Symbols not found: &#x5B; main ]
</pre></div>


<p>Next we’ll add some instructions to the body of the function.</p>



<h2 class="wp-block-heading">Adding an entry basic block</h2>



<p>In order to add code to a function we need to add at least 1 basic block &#8211; the entry block. A basic block is a sequence of instructions within a function that executes straight through from start to finish, with no branches in the middle. These blocks form the nodes of the Control-Flow Graph (CFG), and they connect to each other based on how control flows between them.</p>



<p>Basic blocks can be added to a function with the <a href="https://llvm.org/doxygen/group__LLVMCCoreValueBasicBlock.html#gaf1760061b837b6b255224f243cfe94c8">LLVMAppendBasicBlock</a> function:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [12,13,14]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

	  var entry = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom(&quot;entry&quot;));
 
	  // TODO: Add the instructions

  	var llvmIrCharPtr = LLVMPrintModuleToString(module);

    try {
      System.out.println(llvmIrCharPtr.getString(0));
    } catch (Exception e) {
      System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    }

	   // Clean up LLVM resources
     LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>If you run the program through <code>lli</code> now, you’ll see a different error:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App | lli
lli: &lt;stdin&gt;:6:1: error: expected instruction opcode
}
</pre></div>


<p>That makes sense, we don’t yet have any instructions in our function!</p>



<h2 class="wp-block-heading">Building instructions</h2>



<p>To add instructions, we first create an instruction builder using the <a href="https://llvm.org/doxygen/group__LLVMCCoreInstructionBuilder.html#ga0b336d71db0aa80eef35fe0572ca69bb">LLVMCreateBuilder</a> function. This gives us an LLVMBuilder that we can use to insert new instructions into a basic block.</p>



<p>We’ll also use the <a href="https://llvm.org/doxygen/group__LLVMCCoreInstructionBuilder.html#gab9bdbf21d7fd0bc5a2ee669b333ced2a">LLVMPositionBuilderAtEnd</a> function to position the builder at the end of the entry block and <a href="https://llvm.org/doxygen/group__LLVMCCoreInstructionBuilder.html#gab246fd9294b3801060f0ceb972c262d0">LLVMBuildRet</a> to build a return instruction:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [13,14,15,16,17,18,19,31]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

	  var entry = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom(&quot;entry&quot;));
 	  var builder = LLVMCreateBuilder();
    LLVMPositionBuilderAtEnd(builder, entry);

    // TODO: Call puts “Hello, World!”

	  // Return 0
    LLVMBuildRet(builder, LLVMConstInt(int32Type, 0, 0));

  	var llvmIrCharPtr = LLVMPrintModuleToString(module);

    try {
      System.out.println(llvmIrCharPtr.getString(0));
    } catch (Exception e) {
      System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    }

	   // Clean up LLVM resources
     LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeBuilder(builder);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>If you run the program and pass the output through <code>lli</code> now, you’ll see nothing happen:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App | lli
</pre></div>


<p>Great news &#8211; the errors are gone! Checking the return code confirms the program exited successfully, returning 0.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
$ echo $?
0
</pre></div>


<p>Try changing the 0 to some other number to confirm that the value is indeed coming from the exit code returned by the LLVM IR program!</p>



<h2 class="wp-block-heading">Global variables</h2>



<p>A global variable, defined at the top-level in LLVM IR, defines a region of memory with a fixed address that is allocated when the program is loaded, rather than dynamically at runtime. Globals can be declared as constant if their values will never change.</p>



<p>We’ll add the string “Hello, World!” to our LLVM program as a global constant.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [16,17,18,19,20]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

	  var entry = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom(&quot;entry&quot;));
 	  var builder = LLVMCreateBuilder();
    LLVMPositionBuilderAtEnd(builder, entry);

	  // Create a global string constant containing &quot;Hello, World!&quot;
    var helloStr = 
           LLVMBuildGlobalStringPtr(builder,
                arena.allocateFrom(&quot;Hello, World!&quot;),
                arena.allocateFrom(&quot;hello_str&quot;));

    // TODO: Call puts “Hello, World!”

	  // Return 0
    LLVMBuildRet(builder, LLVMConstInt(int32Type, 0, 0));

  	var llvmIrCharPtr = LLVMPrintModuleToString(module);

    try {
      System.out.println(llvmIrCharPtr.getString(0));
    } catch (Exception e) {
      System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    }

	   // Clean up LLVM resources
     LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeBuilder(builder);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>We don’t use the <code>hello_str</code> yet so running <code>lli</code> would produce the same as before, but you can see the string is now declared in the LLVM IR (prefixed with @ because it is a global, like the main function):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App 
; ModuleID = 'hello'
source_filename = &quot;hello&quot;

@hello_str = private unnamed_addr constant &#x5B;14 x i8] c&quot;Hello, World!\00&quot;, align 1

define i32 @main() {
entry:
  ret i32 0
}
</pre></div>


<p>Let’s add the final instruction next &#8211; a call to <code>puts</code> to print the string.</p>



<h2 class="wp-block-heading">Calling functions</h2>



<p>Before we can call the libc puts function we must declare it in the module by first building the function type and then calling <code>LLVMAddFunction</code> to add it to the module:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [22,23,24,25,26,27,28]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

	  var entry = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom(&quot;entry&quot;));
 	  var builder = LLVMCreateBuilder();
    LLVMPositionBuilderAtEnd(builder, entry);

	  // Create a global string constant containing &quot;Hello, World!&quot;
    var helloStr = 
           LLVMBuildGlobalStringPtr(builder,
                arena.allocateFrom(&quot;Hello, World!&quot;),
                arena.allocateFrom(&quot;hello_str&quot;));

    // Create puts function type: int puts(char*)
    var putsParamTypes = arena.allocate(ADDRESS, 1);
    var charPtrType = LLVMPointerType(LLVMInt8Type(), 0);
    putsParamTypes.set(ADDRESS, 0, charPtrType);
    var putsType = LLVMFunctionType(int32Type, putsParamTypes, 1, 0);
    // Add puts function to the module
    var putsFunc = LLVMAddFunction(module, arena.allocateFrom(&quot;puts&quot;), putsType);

    // TODO: Call puts “Hello, World!”

	  // Return 0
    LLVMBuildRet(builder, LLVMConstInt(int32Type, 0, 0));

  	var llvmIrCharPtr = LLVMPrintModuleToString(module);

    try {
      System.out.println(llvmIrCharPtr.getString(0));
    } catch (Exception e) {
      System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    }

	   // Clean up LLVM resources
     LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeBuilder(builder);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>Now that we’ve declared the function we can call it with the <code>@hello_str</code> global as a parameter using the <a href="https://llvm.org/doxygen/group__LLVMCCoreInstructionBuilder.html#ga40cf5b22d9d28f1f82e76048a69d537a">LLVMBuildCall2</a> function:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [30,31,32,33]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

	  var entry = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom(&quot;entry&quot;));
 	  var builder = LLVMCreateBuilder();
    LLVMPositionBuilderAtEnd(builder, entry);

	  // Create a global string constant containing &quot;Hello, World!&quot;
    var helloStr = 
           LLVMBuildGlobalStringPtr(builder,
                arena.allocateFrom(&quot;Hello, World!&quot;),
                arena.allocateFrom(&quot;hello_str&quot;));

    // Create puts function type: int puts(char*)
    var putsParamTypes = arena.allocate(ADDRESS, 1);
    var charPtrType = LLVMPointerType(LLVMInt8Type(), 0);
    putsParamTypes.set(ADDRESS, 0, charPtrType);
    var putsType = LLVMFunctionType(int32Type, putsParamTypes, 1, 0);
    // Add puts function to the module
    var putsFunc = LLVMAddFunction(module, arena.allocateFrom(&quot;puts&quot;), putsType);

    // Create puts function call
    var callArgs = arena.allocate(ADDRESS, 1);
    callArgs.set(ADDRESS, 0, helloStr);
    LLVMBuildCall2(builder, putsType, putsFunc, callArgs, 1, arena.allocateFrom(&quot;puts&quot;));

	  // Return 0
    LLVMBuildRet(builder, LLVMConstInt(int32Type, 0, 0));

  	var llvmIrCharPtr = LLVMPrintModuleToString(module);

    try {
      System.out.println(llvmIrCharPtr.getString(0));
    } catch (Exception e) {
      System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    }

	   // Clean up LLVM resources
     LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeBuilder(builder);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>Running the program&#8217;s output through <code>lli</code> will finally display the expected result: &#8220;Hello, World!&#8221;:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App | lli
Hello, World!
</pre></div>


<p>Congratulations, you’ve successfully used the Java FFM API to call the LLVM C API to build an LLVM module that contains code to print “Hello, World!”.</p>



<h2 class="wp-block-heading">Just-in-time (JIT) Compilation</h2>



<p>So far, we’ve been printing LLVM IR and letting <code>lli</code> execute it. But LLVM also exposes a JIT compiler API, allowing us to generate and execute machine code in-memory. Let’s see how to JIT our “Hello, World!” directly from Java.</p>



<p>LLVM IR is target independent but once we start compiling to native code we must know which machine we are targeting. We’ll target x86 Linux in the following code; if you’re using ARM, Mac or Windows you&#8217;ll need to adjust the code for your machine.</p>



<p>The first step is to initialise and create an LLVM JIT compiler for the target machine:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; highlight: [38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,61]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

	  var entry = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom(&quot;entry&quot;));
 	  var builder = LLVMCreateBuilder();
    LLVMPositionBuilderAtEnd(builder, entry);

	  // Create a global string constant containing &quot;Hello, World!&quot;
    var helloStr = 
           LLVMBuildGlobalStringPtr(builder,
                arena.allocateFrom(&quot;Hello, World!&quot;),
                arena.allocateFrom(&quot;hello_str&quot;));

    // Create puts function type: int puts(char*)
    var putsParamTypes = arena.allocate(ADDRESS, 1);
    var charPtrType = LLVMPointerType(LLVMInt8Type(), 0);
    putsParamTypes.set(ADDRESS, 0, charPtrType);
    var putsType = LLVMFunctionType(int32Type, putsParamTypes, 1, 0);
    // Add puts function to the module
    var putsFunc = LLVMAddFunction(module, arena.allocateFrom(&quot;puts&quot;), putsType);

    // Create puts function call
    var callArgs = arena.allocate(ADDRESS, 1);
    callArgs.set(ADDRESS, 0, helloStr);
    LLVMBuildCall2(builder, putsType, putsFunc, callArgs, 1, arena.allocateFrom(&quot;puts&quot;));

	  // Return 0
    LLVMBuildRet(builder, LLVMConstInt(int32Type, 0, 0));

    // Initialize LLVM JIT + x86 Target
    LLVMLinkInMCJIT();
    LLVMInitializeX86Target();
    LLVMInitializeX86TargetInfo();
    LLVMInitializeX86TargetMC();
    LLVMInitializeX86AsmPrinter();
    LLVMInitializeX86AsmParser();

    // Create JIT execution engine
    var jitCompiler = arena.allocate(ADDRESS);
    var jitErrorMsgPtrPtr = arena.allocate(ADDRESS);
    LLVMCreateJITCompilerForModule(jitCompiler, module, /* optimization level = */ 2, jitErrorMsgPtrPtr);

    // Disable the IR printing now
  	// var llvmIrCharPtr = LLVMPrintModuleToString(module);
    //
    // try {
    //  System.out.println(llvmIrCharPtr.getString(0));
    // } catch (Exception e) {
    //   System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    // }

	   // Clean up LLVM resources
     // LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeBuilder(builder);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p><code>LLVMCreateJITCompilerForModule</code> sets up a JIT execution engine to compile an LLVM module to native machine code. <code>LLVMCreateJITCompilerForModule</code> will return a 1 upon failure and then we can check the error message string for more information but to simplify things we’ll ignore error handling for now. </p>



<p>Requesting the address of the main function triggers its compilation &#8211; LLVM generates the machine code only when it’s first needed, hence the name Just-In-Time compilation. We can retrieve a pointer to the compiled function using <code>LLVMGetPointerToGlobal</code>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [51,52]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

	  var entry = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom(&quot;entry&quot;));
 	  var builder = LLVMCreateBuilder();
    LLVMPositionBuilderAtEnd(builder, entry);

	  // Create a global string constant containing &quot;Hello, World!&quot;
    var helloStr = 
           LLVMBuildGlobalStringPtr(builder,
                arena.allocateFrom(&quot;Hello, World!&quot;),
                arena.allocateFrom(&quot;hello_str&quot;));

    // Create puts function type: int puts(char*)
    var putsParamTypes = arena.allocate(ADDRESS, 1);
    var charPtrType = LLVMPointerType(LLVMInt8Type(), 0);
    putsParamTypes.set(ADDRESS, 0, charPtrType);
    var putsType = LLVMFunctionType(int32Type, putsParamTypes, 1, 0);
    // Add puts function to the module
    var putsFunc = LLVMAddFunction(module, arena.allocateFrom(&quot;puts&quot;), putsType);

    // Create puts function call
    var callArgs = arena.allocate(ADDRESS, 1);
    callArgs.set(ADDRESS, 0, helloStr);
    LLVMBuildCall2(builder, putsType, putsFunc, callArgs, 1, arena.allocateFrom(&quot;puts&quot;));

	  // Return 0
    LLVMBuildRet(builder, LLVMConstInt(int32Type, 0, 0));

    // Initialize LLVM JIT + x86 Target
    LLVMLinkInMCJIT();
    LLVMInitializeX86Target();
    LLVMInitializeX86TargetInfo();
    LLVMInitializeX86TargetMC();
    LLVMInitializeX86AsmPrinter();
    LLVMInitializeX86AsmParser();

    // Create JIT execution engine
    var jitCompiler = arena.allocate(ADDRESS);
    var jitErrorMsgPtrPtr = arena.allocate(ADDRESS);
    LLVMCreateJITCompilerForModule(jitCompiler, module, /* optimization level = */ 2, jitErrorMsgPtrPtr);

    var executionEngine = jitCompiler.get(ADDRESS, 0);
    var addressOfMainFunc = LLVMGetPointerToGlobal(executionEngine, mainFunc);

    // Disable the IR printing now
  	// var llvmIrCharPtr = LLVMPrintModuleToString(module);
    //
    // try {
    //  System.out.println(llvmIrCharPtr.getString(0));
    // } catch (Exception e) {
    //   System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    // }

	   // Clean up LLVM resources
     // LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeBuilder(builder);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>Now that we’ve compiled the function, we need a way to invoke it from Java. To do this, we use the foreign linker to create a <code>MethodHandle</code> for the JIT-compiled main function. This handle acts as a callable reference to the native code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [56,57,58,59]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

	  var entry = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom(&quot;entry&quot;));
 	  var builder = LLVMCreateBuilder();
    LLVMPositionBuilderAtEnd(builder, entry);

	  // Create a global string constant containing &quot;Hello, World!&quot;
    var helloStr = 
           LLVMBuildGlobalStringPtr(builder,
                arena.allocateFrom(&quot;Hello, World!&quot;),
                arena.allocateFrom(&quot;hello_str&quot;));

    // Create puts function type: int puts(char*)
    var putsParamTypes = arena.allocate(ADDRESS, 1);
    var charPtrType = LLVMPointerType(LLVMInt8Type(), 0);
    putsParamTypes.set(ADDRESS, 0, charPtrType);
    var putsType = LLVMFunctionType(int32Type, putsParamTypes, 1, 0);
    // Add puts function to the module
    var putsFunc = LLVMAddFunction(module, arena.allocateFrom(&quot;puts&quot;), putsType);

    // Create puts function call
    var callArgs = arena.allocate(ADDRESS, 1);
    callArgs.set(ADDRESS, 0, helloStr);
    LLVMBuildCall2(builder, putsType, putsFunc, callArgs, 1, arena.allocateFrom(&quot;puts&quot;));

	  // Return 0
    LLVMBuildRet(builder, LLVMConstInt(int32Type, 0, 0));

    // Initialize LLVM JIT + x86 Target
    LLVMLinkInMCJIT();
    LLVMInitializeX86Target();
    LLVMInitializeX86TargetInfo();
    LLVMInitializeX86TargetMC();
    LLVMInitializeX86AsmPrinter();
    LLVMInitializeX86AsmParser();

    // Create JIT execution engine
    var jitCompiler = arena.allocate(ADDRESS);
    var jitErrorMsgPtrPtr = arena.allocate(ADDRESS);
    LLVMCreateJITCompilerForModule(jitCompiler, module, /* optimization level = */ 2, jitErrorMsgPtrPtr);

    var executionEngine = jitCompiler.get(ADDRESS, 0);
    var addressOfMainFunc = LLVMGetPointerToGlobal(executionEngine, mainFunc);

    // Create method handle to the int main() function that
    // we just created and compiled.
    var functionHandle = Linker.nativeLinker().downcallHandle(
        addressOfMainFunc,
        FunctionDescriptor.of(/* returnType = */ JAVA_INT)
    );

    // Disable the IR printing now
  	// var llvmIrCharPtr = LLVMPrintModuleToString(module);
    //
    // try {
    //  System.out.println(llvmIrCharPtr.getString(0));
    // } catch (Exception e) {
    //   System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    // }

	   // Clean up LLVM resources
     // LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeBuilder(builder);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>The <code>downcallHandle</code> method tells Java how to interpret the native function’s signature &#8211; in this case, a function that takes no arguments and returns an int.</p>



<p>Now we can invoke the compiled native function directly from Java, just like a regular method call:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [61,62,63,64,65,66,67]; title: ; notranslate">
public static void main(String&#x5B;] args)
{
  try (Arena arena = Arena.ofConfined()) {
    var module = LLVMModuleCreateWithName(arena.allocateFrom(&quot;hello&quot;));
          
    // Create main function signature: int main()
    var int32Type = LLVMInt32Type();
    var mainType = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainName = arena.allocateFrom(&quot;main&quot;);
    var mainFunc = LLVMAddFunction(module, mainName, mainType);

	  var entry = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom(&quot;entry&quot;));
 	  var builder = LLVMCreateBuilder();
    LLVMPositionBuilderAtEnd(builder, entry);

	  // Create a global string constant containing &quot;Hello, World!&quot;
    var helloStr = 
           LLVMBuildGlobalStringPtr(builder,
                arena.allocateFrom(&quot;Hello, World!&quot;),
                arena.allocateFrom(&quot;hello_str&quot;));

    // Create puts function type: int puts(char*)
    var putsParamTypes = arena.allocate(ADDRESS, 1);
    var charPtrType = LLVMPointerType(LLVMInt8Type(), 0);
    putsParamTypes.set(ADDRESS, 0, charPtrType);
    var putsType = LLVMFunctionType(int32Type, putsParamTypes, 1, 0);
    // Add puts function to the module
    var putsFunc = LLVMAddFunction(module, arena.allocateFrom(&quot;puts&quot;), putsType);

    // Create puts function call
    var callArgs = arena.allocate(ADDRESS, 1);
    callArgs.set(ADDRESS, 0, helloStr);
    LLVMBuildCall2(builder, putsType, putsFunc, callArgs, 1, arena.allocateFrom(&quot;puts&quot;));

	  // Return 0
    LLVMBuildRet(builder, LLVMConstInt(int32Type, 0, 0));

    // Initialize LLVM JIT + x86 Target
    LLVMLinkInMCJIT();
    LLVMInitializeX86Target();
    LLVMInitializeX86TargetInfo();
    LLVMInitializeX86TargetMC();
    LLVMInitializeX86AsmPrinter();
    LLVMInitializeX86AsmParser();

    // Create JIT execution engine
    var jitCompiler = arena.allocate(ADDRESS);
    var jitErrorMsgPtrPtr = arena.allocate(ADDRESS);
    LLVMCreateJITCompilerForModule(jitCompiler, module, /* optimization level = */ 2, jitErrorMsgPtrPtr);

    var executionEngine = jitCompiler.get(ADDRESS, 0);
    var addressOfMainFunc = LLVMGetPointerToGlobal(executionEngine, mainFunc);

    // Create method handle to the int main() function that
    // we just created and compiled.
    var functionHandle = Linker.nativeLinker().downcallHandle(
        addressOfMainFunc,
        FunctionDescriptor.of(/* returnType = */ JAVA_INT)
    );

    // Execute the main function via the method handle.
    try {
      int result = (int) functionHandle.invoke();
      System.out.println(&quot;main() returned: &quot; + result);
    } catch (Throwable e) {
      System.err.println(&quot;Error calling JIT function: &quot; + e.getMessage());
    }

    // Disable the IR printing now
  	// var llvmIrCharPtr = LLVMPrintModuleToString(module);
    //
    // try {
    //  System.out.println(llvmIrCharPtr.getString(0));
    // } catch (Exception e) {
    //   System.err.println(&quot;Failed to write LLVM IR: failed to get error message: &quot; + e.getMessage());
    // }

	   // Clean up LLVM resources
     // LLVMDisposeMessage(llvmIrCharPtr);
     LLVMDisposeBuilder(builder);
     LLVMDisposeModule(module);
  }
}
</pre></div>


<p>When <code>functionHandle.invoke()</code> runs, Java crosses into the native world and calls the machine code that was just compiled by the LLVM JIT compiler.</p>



<p>And that’s it, you can now run the Java application without the LLVM interpreter and see the resulting “Hello, World!”:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
$ java -cp target/jvm-llvm-helloworld-1.0-SNAPSHOT.jar com.example.App 
Hello, World!
</pre></div>


<p>Congratulations, you’ve now JIT-compiled Hello World, with the help of Java’s FFM API calling LLVM’s C API.</p>



<h2 class="wp-block-heading">Next steps</h2>



<p>In this Java advent we built and executed native machine code from pure Java and a little help from LLVM &#8211; no JNI, no C glue, just memory segments, method handles, and a modern FFI. By the end, we had just a simple program that prints “Hello, World!” but it shows the potential of the Java FFM API and the things you can do when Java and native code work together.</p>



<p>Now see what else you can do, for example, try generating other instructions: print more text, do simple calculations, or even build tiny programs entirely in LLVM from Java.</p>



<p>The full code for this post is available <a href="https://github.com/mrjameshamilton/java-llvm-helloworld">on GitHub over here</a>.</p>
<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fjava-hello-world-llvm-edition.html&amp;action_name=Java%20Hello%20World%2C%20LLVM%20Edition&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/java-hello-world-llvm-edition.html">Java Hello World, LLVM Edition</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/java-hello-world-llvm-edition.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5872</post-id>	</item>
		<item>
		<title>Comparing transitive dependency version resolution in Rust and Java</title>
		<link>https://www.javaadvent.com/2025/12/comparing-transitive-dependency-version-resolution-in-rust-and-java.html</link>
					<comments>https://www.javaadvent.com/2025/12/comparing-transitive-dependency-version-resolution-in-rust-and-java.html#respond</comments>
		
		<dc:creator><![CDATA[Nicolas Fränkel]]></dc:creator>
		<pubDate>Sat, 06 Dec 2025 02:02:51 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[dependency]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rust]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5834</guid>

					<description><![CDATA[<p>You learn by comparing to what you already know. I was recently bitten by assuming Rust worked as Java regarding transitive dependency version resolution. In this post, I want to compare the two. Dependencies, transitivity, and version resolution Before diving into the specifics of each stack, let&#8217;s describe the domain and the problems that come [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fcomparing-transitive-dependency-version-resolution-in-rust-and-java.html&amp;action_name=Comparing%20transitive%20dependency%20version%20resolution%20in%20Rust%20and%20Java&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/comparing-transitive-dependency-version-resolution-in-rust-and-java.html">Comparing transitive dependency version resolution in Rust and Java</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>You learn by comparing to what you already know. I was recently bitten by assuming Rust worked as Java regarding transitive dependency version resolution. In this post, I want to compare the two.</p>
<h2 id="h2-0--ependencies-transitivity-and-version-resolution">Dependencies, transitivity, and version resolution</h2>
<p>Before diving into the specifics of each stack, let&#8217;s describe the domain and the problems that come with it.</p>
<p>When developing any project above Hello World level, chances are you&#8217;ll face problems that others have faced before. If the problem is widespread, the probability is high that somebody was kind and civic-minded enough to have packaged the code that solves it, for others to re-use. Now you can use the package and focus on solving your core problem. It&#8217;s how industry builds most projects today, even if it brings <a href="https://en.wikipedia.org/wiki/Supply_chain_attack" target="_blank" rel="noopener">other problems</a>: you sit on the shoulders of giants.</p>
<p>Languages come with build tools that can add such packages to your project. Most of them refer to packages you add to your project as <em>dependencies</em>. In turn, projects&#8217; dependencies can have their own dependencies: the latter are called <em>transitive dependencies</em>.</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/transitive-dependencies.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="5838" data-permalink="https://www.javaadvent.com/2025/12/comparing-transitive-dependency-version-resolution-in-rust-and-java.html/transitive-dependencies" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/transitive-dependencies.png?fit=230%2C423&amp;ssl=1" data-orig-size="230,423" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="transitive-dependencies" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/transitive-dependencies.png?fit=163%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/transitive-dependencies.png?fit=230%2C423&amp;ssl=1" class="aligncenter wp-image-5838 size-full" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/transitive-dependencies.png?resize=230%2C423&#038;ssl=1" alt="Transitive dependencies" width="230" height="423" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/transitive-dependencies.png?w=230&amp;ssl=1 230w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/transitive-dependencies.png?resize=163%2C300&amp;ssl=1 163w" sizes="auto, (max-width: 230px) 100vw, 230px" /></a></p>
<p>In the above diagram, C and D are transitive dependencies.</p>
<p>Transitive dependencies have issues on their own. The biggest one is when a transitive dependency is required from different paths, but in different versions. In the diagram below, A and B both depend on C, but on different versions of it.</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="5839" data-permalink="https://www.javaadvent.com/2025/12/comparing-transitive-dependency-version-resolution-in-rust-and-java.html/maven-dependency-resolution" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution.png?fit=501%2C448&amp;ssl=1" data-orig-size="501,448" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="maven-dependency-resolution" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution.png?fit=300%2C268&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution.png?fit=501%2C448&amp;ssl=1" class="wp-image-5839 size-full aligncenter" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution.png?resize=501%2C448&#038;ssl=1" alt="Maven dependency resolution" width="501" height="448" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution.png?w=501&amp;ssl=1 501w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution.png?resize=300%2C268&amp;ssl=1 300w" sizes="auto, (max-width: 501px) 100vw, 501px" /></a></p>
<p>Which version of C should the build tool include in your project? Java and Rust have different answers. Let&#8217;s describe them in turn.</p>
<h2 id="h2-1--ava-transitive-dependency-version-resolution">Java transitive dependency version resolution</h2>
<p>Reminder: Java code compiles to <em>bytecode</em>, which is then interpreted at runtime (and sometimes compiled to native code, but this is outside of our current problem space). I&#8217;ll first describe <em>runtime</em> dependency resolution and <em>build time</em> dependency resolution.</p>
<p>At runtime, the <abbr title="Java Virtual Machine">JVM</abbr> offers the concept of a <em>classpath</em>. When having to load a class, the runtime searches through the configured classpath <strong>in order</strong>. Imagine the following class:</p>
<pre>public static Main {
    public static void main(String[] args) {
        Class.forName("ch.frankel.Dep");
    }
}</pre>
<p>Let&#8217;s compile it and execute it:</p>
<pre>java -cp ./foo.jar:./bar.jar Main</pre>
<p>The above will first look in the <code>foo.jar</code> for the <code>ch.frankel.Dep</code> class. If found, it stops there and loads the class, regardless of whether it might also be present in the <code>bar.jar</code>; if not, it looks further in the <code>bar.jar</code> class. If still not found, it fails with a <code>ClassNotFoundException</code>.</p>
<p>Java&#8217;s runtime dependency resolution mechanism is <em>ordered</em> and has a <strong>per class</strong> granularity. It applies whether you run a Java class and define the classpath on the command line as above, or whether you run a JAR that defines the classpath in its manifest.</p>
<p>Let&#8217;s change the above code to the following:</p>
<pre>public static Main {
    public static void main(String[] args) {
        var dep = new ch.frankel.Dep();
    }
}</pre>
<p>Because the new code references <code>Dep</code> directly, new code requires class resolution at compile-time. Classpath resolution works in the same way:</p>
<pre>javac -cp ./foo.jar:./bar.jar Main</pre>
<p>The compiler looks for <code>Dep</code> in <code>foo.jar</code>, then in <code>bar.jar</code> if not found. The above is what you learn at the beginning of your Java learning journey.</p>
<p>Afterwards, your unit of work is the Java Archive, known as the JAR, instead of the class. A JAR is a glorified ZIP archive, with an internal manifest that specifies its version.</p>
<p>Now, imagine that you&#8217;re a user of <code>foo.jar</code>. Developers of <code>foo.jar</code> set a specific classpath when compiling, possibly including other JARs. You&#8217;ll need this information to run your own command. How does a library developer pass this knowledge to downstream users?</p>
<p>The community came up with a few ideas to answer this question: The first response that stuck was Maven. Maven has the concept of <abbr title="Project Object Model">POM</abbr>, where you set your project&#8217;s metadata, as well as dependencies. Maven can easily resolve transitive dependencies because they also publish their POM, with their own dependencies. Hence, Maven can trace each dependency&#8217;s dependencies down to the leaf dependencies.</p>
<p>Now back to the problem statement: how does Maven resolve version conflicts? Which dependency version will Maven resolve for C, 1.0 or 2.0?</p>
<p>The documentation is clear: <a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Transitive_Dependencies" target="_blank" rel="noopener">the nearest</a>.</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution2.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="5841" data-permalink="https://www.javaadvent.com/2025/12/comparing-transitive-dependency-version-resolution-in-rust-and-java.html/maven-dependency-resolution2" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution2.png?fit=434%2C640&amp;ssl=1" data-orig-size="434,640" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="maven-dependency-resolution2" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution2.png?fit=203%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution2.png?fit=434%2C640&amp;ssl=1" class="aligncenter wp-image-5841 size-full" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution2.png?resize=434%2C640&#038;ssl=1" alt="Dependency resolution with the same dependency in different versions" width="434" height="640" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution2.png?w=434&amp;ssl=1 434w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/maven-dependency-resolution2.png?resize=203%2C300&amp;ssl=1 203w" sizes="auto, (max-width: 434px) 100vw, 434px" /></a></p>
<p>In the above diagram, the path to v1 has a distance of two, one to B, then one to C; meanwhile, the path to v2 has a distance of three, one to A, then one to D, then finally one to C. Thus, the shortest path points to v1.</p>
<p>However, in the initial diagram, both C versions are at the same distance from the root artifact. The documentation provides no answer. If you&#8217;re interested in it, <a href="https://jakewharton.com/nonsensical-maven-is-still-a-gradle-problem/#the-nonsense" target="_blank" rel="noopener">it depends</a> on the order of declaration of A and B in the POM! In summary, Maven returns a single version of a duplicated dependency to include it on the compile classpath.</p>
<p>If A can work with C v2.0 or B with C 1.0, great! If not, you&#8217;ll probably need to upgrade your version of A or downgrade your version of B, so that the resolved C version works with both. It&#8217;s a manual process that is painful–ask me how I know. Worse, you might find out there&#8217;s no C version that works with both A and B. Time to replace A or B.</p>
<h2 id="h2-2--ust-transitive-dependency-version-resolution">Rust transitive dependency version resolution</h2>
<p>Rust differs from Java in several aspects, but I think the following are the most relevant for the sake of our discussion:</p>
<ul>
<li>Rust has the same dependency tree at compile-time and at runtime</li>
<li>It provides a build tool out of the box, Cargo</li>
<li>Dependencies are <strong>resolved from source</strong></li>
</ul>
<p>Let&#8217;s examine them one by one.</p>
<p>Java compiles to _bytecode, then you run the latter. You need to set the classpath both at compilation time and at runtime. Compiling with a specific classpath and running with a different one can lead to errors. For example, imagine you compile with a class you depend on, but the class is absent at runtime. Or alternatively, it&#8217;s present, but in an incompatible version.</p>
<p>Contrary to this modular approach, Rust compiles to a unique native package the crate&#8217;s code and every dependency. Moreover, Rust provides its own build too, thus avoiding having to remember the quirks of different tools. I mentioned Maven, but other build tools likely have different rules to resolve the version in the use case above.</p>
<p>Finally, Java resolves dependencies from binaries: JARs. On the contrary, Rust resolves dependencies from sources. At build time, Cargo resolves the entire dependency tree, downloads all required sources, and compiles them in the correct order.</p>
<p>With this in mind, how does Rust resolve the version of the C dependency in the initial problem? The answer may seem strange if you come from a Java background, but <strong>Rust includes both</strong>. Indeed, in the above diagram, Rust will compile A with C v1.0 and compile B with C v2.0. Problem solved.</p>
<h2 id="h2-3--onclusion">Conclusion</h2>
<p>JVM languages, and Java in particular, offer both a compile-time classpath and a runtime classpath. It allows modularity and reusability, but opens the door to issues regarding classpath resolution. On the other hand, Rust builds your crate into a single self-contained binary, whether a library or an executable.</p>
<p><strong>To go further:</strong></p>
<ul>
<li><a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html" target="_blank" rel="noopener">Maven &#8211; Introduction to the Dependency Mechanism</a></li>
<li><a href="https://effective-rust.com/dep-graph.html" target="_blank" rel="noopener">Effective Rust &#8211; Item 25: Manage your dependency graph</a></li>
</ul>
<hr />
<p style="text-align: center;"><em>Originally published at <a href="https://blog.frankel.ch/dependency-version-resolution-rust-java/" target="_blank" rel="noopener">A Java Geek</a> on September 14<sup>th</sup>, 2025</em></p>
<p><img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fcomparing-transitive-dependency-version-resolution-in-rust-and-java.html&amp;action_name=Comparing%20transitive%20dependency%20version%20resolution%20in%20Rust%20and%20Java&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/comparing-transitive-dependency-version-resolution-in-rust-and-java.html">Comparing transitive dependency version resolution in Rust and Java</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/comparing-transitive-dependency-version-resolution-in-rust-and-java.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5834</post-id>	</item>
		<item>
		<title>Beyond x86: Java on ARM in 2025</title>
		<link>https://www.javaadvent.com/2025/12/beyond-x86-java-on-arm-in-2025.html</link>
					<comments>https://www.javaadvent.com/2025/12/beyond-x86-java-on-arm-in-2025.html#respond</comments>
		
		<dc:creator><![CDATA[Artur Skowronski]]></dc:creator>
		<pubDate>Fri, 05 Dec 2025 02:02:36 +0000</pubDate>
				<category><![CDATA[2017]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=6056</guid>

					<description><![CDATA[<p>We all know the mantra: “Write Once, Run Anywhere.” But for most of Java’s history, that “Anywhere” really meant “anywhere, as long as there’s Intel or AMD underneath.” When most of us were starting out with Java, ARM was something you associated with phones, a Raspberry Pi, or some mysterious “embedded” device – not with [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fbeyond-x86-java-on-arm-in-2025.html&amp;action_name=Beyond%20x86%3A%20Java%20on%20ARM%20in%202025&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/beyond-x86-java-on-arm-in-2025.html">Beyond x86: Java on ARM in 2025</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>We all know the mantra: “Write Once, Run Anywhere.” But for most of Java’s history, that “Anywhere” really meant “anywhere, as long as there’s Intel or AMD underneath.” When most of us were starting out with Java, ARM was something you associated with phones, a Raspberry Pi, or some mysterious “embedded” device – not with a serious backend carrying production traffic in a major cloud.</p>
<p>That’s why Java on ARM is still pretty niche today: hardly any backend developer seriously considered it, because for years… there just wasn’t much to talk about. The journey both ARM processors and the broader JVM ecosystem had to go through to catch up with the needs of the server world has been long and bumpy, full of ugly bugs only discovered in production, hurriedly rolled-back patches, and tons of “invisible” work happening inside virtual machines, JIT compilers, and garbage collectors under the hood.</p>
<p>However, to understand why <em>now</em> Java on ARM is finally starting to make sense – and why it’s worth paying attention to – we first need to look at the evolution that the ARM architecture itself has undergone in recent years.</p>
<hr />
<p>Although ARM has been with us for a long time and has powered phones, tablets, routers and millions of “smart” gadgets for years, it took two key events to really bring it into the halls of “serious” IT. First &#8211; Apple’s move to its own ARM chips in Macs. Overnight, a huge number of developers suddenly had ARM-based <em>primary</em> work machines on their desks, not just toy dev boards.</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6075" data-permalink="https://www.javaadvent.com/2025/12/beyond-x86-java-on-arm-in-2025.html/1-4" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?fit=1946%2C1164&amp;ssl=1" data-orig-size="1946,1164" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?fit=300%2C179&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?fit=600%2C359&amp;ssl=1" class="alignnone size-full wp-image-6075" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?resize=600%2C359&#038;ssl=1" alt="" width="600" height="359" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?w=1946&amp;ssl=1 1946w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?resize=300%2C179&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?resize=1024%2C613&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?resize=768%2C459&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?resize=1536%2C919&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?resize=1240%2C742&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?resize=508%2C304&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/1.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a>Second &#8211; the arrival of the <a href="https://en.wikipedia.org/wiki/ARM_Neoverse">Neoverse architecture</a>, a family of ARM cores designed from scratch with data centers in mind, not smartphones.</p>
<p>To understand Neoverse, it’s worth starting with Cortex. It’s Cortex cores – in their various A, R and M variants – that sit inside most consumer electronics: from phones and tablets to Raspberry Pi boards. They’re designed under very strict constraints on power, cost and die area, so that SoC vendors can build cheap, energy-efficient chips for battery-powered devices. They’re perfect for the “a few strong cores + GPU + modem on one chip” scenario, but much less suited to servers with hundreds of watts of socket power, massive amounts of memory and full-blown enterprise requirements.</p>
<p>Neoverse is ARM’s answer to the question: “what should a <em>server</em> ARM look like if we stop thinking like phone designers?”. It’s a separate IP line, built from the ground up for infrastructure: high core counts (N1/N2/V2 scaling to hundreds of cores per board), large caches, mesh interconnects, a strong focus on memory bandwidth, virtualization, and RAS (Reliability, Availability, Serviceability) features. In other words – Neoverse is to data centers what Cortex is to smartphones: the basic building block partners like AWS, Ampere and others can use to build their own server CPUs without the compromises typical of mobile cores.</p>
<p>It’s Neoverse that really brought ARM into the cloud. The first generation of AWS Graviton was still built on the well-known Cortex-A72 cores and targeted rather “lighter” workloads. The later generations – Graviton2 on Neoverse N1, Graviton3 on Neoverse V1 and Graviton4 on Neoverse V2 – are fully-fledged, high-performance server processors that in many scenarios beat x86 in terms of price/performance and energy efficiency. In practice, this means that for a large chunk of use cases, “EC2 on ARM” is no longer a curiosity but starts becoming the default option.</p>
<p>The second pillar of this revolution are independent vendors such as Ampere with its Altra family of processors, also built on Neoverse N1. These are the chips that power, among others, ARM instances in Oracle Cloud and many other data centers. SoftBank’s acquisition of Ampere Computing for 6.5 billion dollars is a clear signal that ARM CPUs are no longer a niche experiment but a strategic piece of infrastructure for AI and cloud – especially given that SoftBank also controls ARM itself.</p>
<p>The end result is that a huge portion of <em>new</em> cloud-native workloads now land on ARM by default: microservices in Kubernetes, backend services, event-driven systems, application servers like Spring Boot or Quarkus. Hyperscalers are aggressively promoting ARM instances with attractive pricing, while managing to deliver 20–40% better price/performance and significant energy savings compared to classic x86.</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6076" data-permalink="https://www.javaadvent.com/2025/12/beyond-x86-java-on-arm-in-2025.html/2-3" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?fit=1548%2C1142&amp;ssl=1" data-orig-size="1548,1142" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="2" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?fit=300%2C221&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?fit=600%2C442&amp;ssl=1" class="alignnone size-full wp-image-6076" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?resize=600%2C443&#038;ssl=1" alt="" width="600" height="443" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?w=1548&amp;ssl=1 1548w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?resize=300%2C221&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?resize=1024%2C755&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?resize=768%2C567&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?resize=1536%2C1133&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?resize=1240%2C915&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/2.png?resize=508%2C375&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p>For companies counting every watt and every dollar on their cloud bill, and at the same time building new services on top of containers and JVMs, the natural question is increasingly not “will ARM work?”, but “why are we <em>still</em> not using ARM instances?”.</p>
<p>And now it’s time to ask… why, exactly? Time to look back at the history of Java on ARM.</p>
<hr />
<p><a href="https://developers.redhat.com/blog/2021/02/01/how-red-hat-ported-openjdk-to-64-bit-arm-a-community-history">It’s 2011, Cambridge, UK</a>. Andrew Haley (Red Hat) and Jon Masters (Chief ARM Architect at Red Hat) are sitting in a Thai pub called “The Wrestlers.” At some point Masters drops a bomb: 64-bit ARM (AArch64) is coming, and Red Hat wants to bring Red Hat Enterprise Linux to it. There’s just one tiny problem &#8211; there’s no Java on this platform. And without Java there is no enterprise. Haley hears an initial estimate: porting OpenJDK will take two <em>experts</em> about a year of work. The catch? Those experts… don’t exist yet. The team has to learn the ARM architecture on the fly, writing code against simulators before any real silicon ever shows up in a data center.</p>
<p>Long before we could talk about performance, we lived in the age of <a href="https://openjdk.org/projects/zero/">OpenJDK Zero</a>. The idea was beautiful: write a JVM interpreter in pure C++, without a single line of assembly. That way Java would “run” on anything that had a GCC toolchain &#8211; from routers to exotic experimental chips. Reality was brutal: performance was awful. Zero had no JIT (Just-In-Time) compiler, so bytecode was interpreted instruction by instruction. It was like pushing a sports car up a hill – technically it moved, but no one wanted to run that on production.</p>
<p>The real breakthrough came with <a href="https://openjdk.org/jeps/237">JEP 237</a>, roughly around Java 9. That’s when engineers from Red Hat and <a href="https://www.linaro.org/">Linaro</a> rolled up their sleeves and aimed for a full-blown port with C1 and C2 compilers that truly “understand” ARM. The biggest challenge was changing the mental model. x86 (CISC) is brute force: complex instructions that do many things at once. ARM (RISC) is precision and simplicity, but with a different geometry of power. C2 had to learn how to use 31 general-purpose registers (x86 only has 16). That’s a massive difference – the JVM can keep most “hot” variables directly in CPU registers instead of constantly spilling them out to memory and loading them back, which dramatically changes the performance profile of the whole application.</p>
<p>However, the performance gain happens in intrinsics in <a href="https://openjdk.org/jeps/315">JEP 315</a>, where the JVM replaces selected Java methods with hand-written assembly. That’s where we saw both the biggest wins and the most painful failures. On the success side, you have cryptographic instructions (AES) from ARMv8 – suddenly GCM encryption in TLS sped up by 3.5–5x on Graviton2. Similarly, operations on strings (like <code>String.indexOf</code>) started using NEON vector instructions and stopped being such an obvious hotspot in many services. But there were dead ends, too: the attempt to speed up <code>String.equals</code> using NEON turned out to be worth it only for long strings; for the short ones that dominate typical business systems, the overhead of preparing vector registers simply killed the gain. Result: the code ended up in the “rolled back / Won’t Fix” bucket. Even more spectacular was the bug in the <code>Math.log</code> intrinsic (JDK-8210858): for extreme values, logarithms on ARM stopped being monotonic and produced different results than on Intel. In finance or scientific computing, that’s not a “minor discrepancy” – that’s a red alert. In the end, the intrinsic was removed – correctness beat performance.</p>
<article class="text-token-text-primary w-full focus:outline-none [--shadow-height:45px] has-data-writing-block:pointer-events-none has-data-writing-block:-mt-(--shadow-height) has-data-writing-block:pt-(--shadow-height) [&amp;:has([data-writing-block])&gt;*]:pointer-events-auto [content-visibility:auto] supports-[content-visibility:auto]:[contain-intrinsic-size:auto_100lvh] scroll-mt-[calc(var(--header-height)+min(200px,max(70px,20svh)))]" dir="auto" data-turn-id="e7dcd0ce-5570-4c1f-a691-9a5df794575c" data-testid="conversation-turn-2" data-scroll-anchor="true" data-turn="assistant">
<div class="text-base my-auto mx-auto pb-10 [--thread-content-margin:--spacing(4)] thread-sm:[--thread-content-margin:--spacing(6)] thread-lg:[--thread-content-margin:--spacing(16)] px-(--thread-content-margin)">
<div class="[--thread-content-max-width:40rem] thread-lg:[--thread-content-max-width:48rem] mx-auto max-w-(--thread-content-max-width) flex-1 group/turn-messages focus-visible:outline-hidden relative flex w-full min-w-0 flex-col agent-turn">
<div class="flex max-w-full flex-col grow">
<div class="min-h-8 text-message relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal [.text-message+&amp;]:mt-1" dir="auto" data-message-author-role="assistant" data-message-id="b4ee4b3a-56f3-47ad-98b9-a1559fff8b4a" data-message-model-slug="gpt-5-1-thinking">
<div class="flex w-full flex-col gap-1 empty:hidden first:pt-[1px]">
<div class="markdown prose dark:prose-invert w-full break-words light markdown-new-styling">
<p data-start="0" data-end="1249">Of course, it&#8217;s not all bells and whistles. For people close to the metal, the biggest shock was the memory model. On x86 we live in the relatively comfy world of TSO (Total Store Order): if one thread writes A and then B, other threads will see those writes in the same order. The processor “smooths over” a lot of concurrency bugs, and developers grew used to the fact that “it somehow works.” ARM plays a different game. It has a weak memory model: it can freely reorder loads and stores if it decides that this will be faster. The JVM had to take all that complexity on its shoulders, sprinkling the code with the right memory barriers – but doing it in a way that wouldn’t kill performance with heavy DMBs everywhere. Newer LSE (Large System Extensions) helped here: hardware-supported atomic instructions like CAS and LDADD, much cheaper than classic locks or thick barriers. Thanks to them, concurrent Java on ARM64 is in no way a “second-class citizen.” And this is not just theory – we <em data-start="995" data-end="1006">literally</em> ran into a bug caused by a missing barrier here recently: <a href="https://bugs.openjdk.org/browse/JDK-8369506">JDK-8369506 (“Bytecode rewriting causes Java heap corruption on AArch64”)</a>, a rare but nasty case where the weak memory model broke the illusion of safety until it was fixed in HotSpot.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</article>
<p>Today, when you run Java 21 on AWS Graviton4 or Google Axion, you’re benefiting from more than a decade of those experiments and missteps. Latency-sensitive workloads on ARM64 have improved by several hundred percent compared to baseline Java 8. A modern GC helps a lot (the generational ZGC loves ARM tricks like TBI – Top Byte Ignore), as do well-tuned intrinsics and native support for SVE2 vectors. Then there’s the character of the CPUs themselves: Ampere Altra, Graviton and friends don’t have Hyper-Threading, so one Java thread is one physical core. The garbage collector doesn’t have to fight your business code for ALUs, which translates into much calmer latency tails &#8211; on ARM, tail latency is often noticeably flatter and more predictable than on x86.</p>
<hr />
<p>The history of Java on ARM is, at its core, a story about how hardware is useless without brutally hard work on the software side. We’ve gone from a slow interpreter in pure C++, through logarithms that calculated “a bit differently,” all the way to virtual machines that, on chips like Google Axion, can squeeze out up to +150% performance in AI workloads compared to the latest Intels.</p>
<p>And while I know there are lies, bigger lies and benchmarks&#8230;</p>
<p>&#8230;if you’re sticking to x86 purely out of habit, you’re probably burning money. But before you rush to move production to ARM, do one thing: update your JDK. Java 8 on ARM <em>works</em>, but it’s like driving a Ferrari with the handbrake on. The real fun starts with Java 17, and ideally 21 &#8211; that’s where you finally see why someone spent all those years grinding away at the AArch64 port.</p>
<p>So if you look for the good business reason to migrate a new Java &#8211; I found you one <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" />. Of course, if you crave the change &#8211; not everybody&#8217;s do.</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="6077" data-permalink="https://www.javaadvent.com/2025/12/beyond-x86-java-on-arm-in-2025.html/5-2" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?fit=1018%2C1194&amp;ssl=1" data-orig-size="1018,1194" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="5" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?fit=256%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?fit=600%2C704&amp;ssl=1" class="alignnone size-full wp-image-6077" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?resize=600%2C704&#038;ssl=1" alt="" width="600" height="704" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?w=1018&amp;ssl=1 1018w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?resize=256%2C300&amp;ssl=1 256w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?resize=873%2C1024&amp;ssl=1 873w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?resize=768%2C901&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/5.png?resize=508%2C596&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p>&nbsp;<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fbeyond-x86-java-on-arm-in-2025.html&amp;action_name=Beyond%20x86%3A%20Java%20on%20ARM%20in%202025&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/beyond-x86-java-on-arm-in-2025.html">Beyond x86: Java on ARM in 2025</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/beyond-x86-java-on-arm-in-2025.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6056</post-id>	</item>
		<item>
		<title>Test Your Test</title>
		<link>https://www.javaadvent.com/2025/12/test-your-test.html</link>
					<comments>https://www.javaadvent.com/2025/12/test-your-test.html#respond</comments>
		
		<dc:creator><![CDATA[Andres Sacco]]></dc:creator>
		<pubDate>Thu, 04 Dec 2025 02:02:10 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[effective]]></category>
		<category><![CDATA[mutation]]></category>
		<category><![CDATA[testing]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5925</guid>

					<description><![CDATA[<p>Creating or modifying an application involves many aspects, such as following best practices and applying design patterns to solve everyday problems. After writing the code, developers usually add unit tests and rely on tools like Sonar to track metrics such as code coverage and highlight potentially untested areas. However, high test coverage does not guarantee [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Ftest-your-test.html&amp;action_name=Test%20Your%20Test&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/test-your-test.html">Test Your Test</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Creating or modifying an application involves many aspects, such as following best practices and applying design patterns to solve everyday problems. After writing the code, developers usually add unit tests and rely on tools like Sonar to track metrics such as code coverage and highlight potentially untested areas.</p>
<p>However, high test coverage does not guarantee low risk in production. A test may execute a line of code without actually verifying the outcome. A test may instantiate an object but never check all of its significant attributes. Coverage shows what code ran—not whether the tests would catch a fundamental defect.</p>
<p>This raises an important question: how can we measure not only how much code is tested, but how practical those tests really are?</p>

<h2><span style="font-weight: 400">context of the situation</span></h2>
<p>Imagine a team responsible for several microservices. The team is recognized as one of the best, with strong code coverage and consistent promotion of good practices, such as using Sonar to detect problems and creating integration tests to verify interactions between the application and external resources, such as databases.</p>
<p>One day, a new feature was deployed to production—just a slight change in a few classes. Nothing that appeared risky, considering the large number of existing tests. However, a few minutes later, a significant issue surfaced, affecting the entire platform rather than just the application involved.</p>
<p>Seconds after the problem appeared, someone on the team analyzed the code, detected the issue, and fixed it. During the investigation, it became clear that the tests were not reliable, as they neither failed before nor after the changes.</p>
<p>The following is the test that caused the problem:</p>
<pre>@Test<br />public void should_return_a_country() {<br />    when(countryRepository.findByCode("AR"))<br />         .thenReturn(getCountryModel());<br /><br />    CountryDTO response = countryService.getCountryByCode("AR");<br />}</pre>
<p>The test calls a method but overlooks crucial attributes, potentially causing errors in other applications.</p>
<h2><span style="font-weight: 400">What&#8217;s mutation testing?</span></h2>
<p>Mutation testing is a technique for evaluating the effectiveness of a test suite by introducing small, controlled changes into the code and verifying whether the tests detect them. Instead of <span style="margin-top: 0px;margin-right: 0px;margin-left: 0px;padding: 0px">measuring only which lines of code are executed, mutation testing focuses on the <em>quality</em> of </span>assertions and the ability of tests to catch meaningful defects.</p>
<h3>CORE CONCEPTS</h3>
<p>The heart of this technique implies a set of concepts:</p>
<ul>
<li><strong>Mutants</strong>:  A <em>mutation</em> is a minor, controlled modification made to source code. Examples include flipping a boolean condition, replacing an arithmetic operator, or removing a method call.</li>
<li><strong>Goal</strong>: Check if the existing tests can detect these changes. If a test fails when a mutant is introduced, it indicates that the test suite is sensitive enough to detect behavioral differences, a sign of strong, practical tests.</li>
</ul>
<p>After the creation of the mutants and executing all the tests, each mutation could stay in two states:</p>
<ul>
<li><strong>Killed</strong>: When at least one test fails after a change, it indicates that the test suite has effectively detected a behavioral difference.</li>
<li><strong>Survived</strong>: A mutant <em data-start="1717" data-end="1727">survives</em> when all tests pass despite the injected modification. Typically, this happens when an assertion is weak or when test cases are missing.</li>
</ul>
<h3>mUTATION SCORE</h3>
<p>Mutation testing provides a percentage indicating how practical the tests are. To calculate this, it&#8217;s necessary to use the following formula:</p>
<div id="attachment_6107" style="width: 571px" class="wp-caption aligncenter"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-score.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6107" data-attachment-id="6107" data-permalink="https://www.javaadvent.com/2025/12/test-your-test.html/mutation-score" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-score.png?fit=561%2C94&amp;ssl=1" data-orig-size="561,94" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Mutation Score" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-score.png?fit=300%2C50&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-score.png?fit=561%2C94&amp;ssl=1" class="wp-image-6107 size-full" title="Mutation Testing Score" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-score.png?resize=561%2C94&#038;ssl=1" alt="Mutation Testing Score" width="561" height="94" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-score.png?w=561&amp;ssl=1 561w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-score.png?resize=300%2C50&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-score.png?resize=508%2C85&amp;ssl=1 508w" sizes="auto, (max-width: 561px) 100vw, 561px" /></a><p id="caption-attachment-6107" class="wp-caption-text">Mutation Testing Score</p></div>
<p>The way to interpret the percentage is:</p>
<ul>
<li data-start="2734" data-end="2831">
<p data-start="2736" data-end="2831"><strong data-start="2736" data-end="2766">High scores (e.g., 80–95%)</strong> often suggest strong test coverage with meaningful assertions.</p>
</li>
<li data-start="2832" data-end="2913">
<p data-start="2834" data-end="2913"><strong data-start="2834" data-end="2851">Medium scores</strong> highlight opportunities to improve tests or simplify logic.</p>
</li>
<li data-start="2914" data-end="3029">
<p data-start="2916" data-end="3029"><strong data-start="2916" data-end="2943">Low scores (below ~50%)</strong> usually indicate insufficient or weak tests that may not protect against regressions.</p>
</li>
</ul>
<p>It&#8217;s important to note that a high mutation test score does not mean the application is bug-free.</p>
<h3>Types of Mutations</h3>
<p><span style="margin: 0px;padding: 0px">Mutation testing tools create various modifications, known as <em>mutations, </em>that simulate potential defects in the code. While the specific mutation operators can differ based on the programming language or library used, they generally fall into three main categories: <strong>Decision mutations</strong>, <strong>Statement mutations</strong>, and <strong>Value mutations</strong>. Each category focuses on different aspects of the program&#8217;s behavior, helping assess how effectively the test suite validates the code&#8217;s logic, control flow, and data integrity.</span></p>
<p>Let&#8217;s see a brief explanation about each of them:</p>
<ul>
<li><strong>Decision Mutations</strong>: Modify the conditions that control program flow. These mutations focus on expressions found in <code data-start="1057" data-end="1061">if</code>, <code data-start="1063" data-end="1071">switch</code>, <code data-start="1073" data-end="1080">while</code>, <code data-start="1082" data-end="1087">for</code>, and boolean-returning operations. </li>
<li><strong>Statement Mutations</strong>: Work by adding, removing, or altering entire statements. They test whether the test suite can detect situations where part of the logic disappears or behaves differently.</li>
<li><strong>Value Mutations:</strong> Value mutations target constants, literals, return values, and field assignments. They simulate defects caused by incorrect data produced or used by the program.</li>
</ul>
<h2><span style="font-weight: 400">How to IMPLEMENT IT ON AN APPLICATION?</span></h2>
<p>To implement mutation testing in a JVM ecosystem, several libraries are available, such as <a href="https://pitest.org/">Pitest</a>, <a href="https://mutation-testing.org/">Major</a>, and <a href="https://github.com/jeffoffutt/muJava">MuJava</a>. The first option is the best because it is actively maintained, highly performant, integrates seamlessly with Maven, Gradle, JUnit, and TestNG, supports incremental analysis with extensive configuration options, generates clear HTML reports of killed and surviving mutants, and even provides plugins for SonarQube and other tools.</p>
<p><span style="margin: 0px;padding: 0px">This article uses a source from a <a href="https://github.com/andres-sacco/testing-your-test">GitHub repository</a>; feel free to clone it and use it to learn about mutation testing.</span></p>
<p>To use this library, you first need to add the dependency to your application. The following block represents how to do it on a Maven project:</p>
<pre>&lt;!-- Mutation Test --&gt;<br />&lt;plugin&gt;<br />    &lt;groupId&gt;org.pitest&lt;/groupId&gt;<br />    &lt;artifactId&gt;pitest-maven&lt;/artifactId&gt;<br />    &lt;version&gt;${pitest-maven.version}&lt;/version&gt;<br /><br />    &lt;configuration&gt;<br />        &lt;outputFormats&gt;<br />            &lt;outputFormat&gt;HTML&lt;/outputFormat&gt;<br />            &lt;outputFormat&gt;XML&lt;/outputFormat&gt;<br />        &lt;/outputFormats&gt;<br />        &lt;targetClasses&gt;<br />            &lt;param&gt;com.twa.flights.api.catalog.*&lt;/param&gt;<br />        &lt;/targetClasses&gt;<br />        &lt;targetTests&gt;<br />            &lt;param&gt;com.twa.flights.api.catalog.*&lt;/param&gt;<br />        &lt;/targetTests&gt;<br />    &lt;/configuration&gt;<br />    &lt;dependencies&gt;<br />        &lt;dependency&gt;<br />            &lt;groupId&gt;org.pitest&lt;/groupId&gt;<br />            &lt;artifactId&gt;pitest-junit5-plugin&lt;/artifactId&gt;<br />            &lt;version&gt;${pitest-junit5-plugin.version}&lt;/version&gt;<br />        &lt;/dependency&gt;<br />    &lt;/dependencies&gt;<br />&lt;/plugin&gt;</pre>
<p><span style="margin: 0px;padding: 0px">As a recommendation, <span style="margin-top: 0px;margin-right: 0px;margin-left: 0px;padding: 0px">check the latest version of this library on the official webpage or a repository like <a href="https://mvnrepository.com/artifact/org.pitest/pitest-maven" target="_blank" rel="noopener">this regularly</a></span>.</span></p>
<p>Pitest allows users to export execution results in multiple formats, including HTML, CSV, and XML. The relevance of each format depends on the report&#8217;s purpose. For example, the HTML format is ideal for those who want a simple view of execution results, including the number of mutations used. In contrast, the XML format helps integrate this information with other tools, such as Sonar, which can display mutation execution results.</p>
<p>On this tool, it&#8217;s possible to indicate in the same way that appears on the previous code block, which packages or test classes will be mutated, and it&#8217;s possible to indicate the same about which package of the source code will suffer modifications.</p>
<p>Executing mutation testing implies just running a command like the following:</p>
<pre>$ mvn clean package org.pitest:pitest-maven:mutationCoverage<br />[INFO] --- pitest:1.7.6:mutationCoverage (default-cli) @ api-catalog ---<br />[INFO] Root dir is : /home/asacco/Code/testing-your-test/api-catalog<br />[INFO] Found plugin : Default csv report plugin<br />[INFO] Found plugin : Default xml report plugin<br />[INFO] Found plugin : Default html report plugin<br />......<br />[INFO] Found shared classpath plugin : Default mutation engine<br />[INFO] Found shared classpath plugin : JUnit 5 test framework support<br />[INFO] Found shared classpath plugin : JUnit plugin<br />[INFO] Available mutators : EXPERIMENTAL_ARGUMENT_PROPAGATION,FALSE_RETURNS,TRUE_RETURNS,CONDITIONALS_BOUNDARY,CONSTRUCTOR_CALLS,EMPTY_RETURNS,INCREMENTS,INLINE_CONSTS,INVERT_NEGS,MATH,NEGATE_CONDITIONALS,NON_VOID_METHOD_CALLS,NULL_RETURNS,PRIMITIVE_RETURNS,REMOVE_CONDITIONALS_EQUAL_IF,REMOVE_CONDITIONALS_EQUAL_ELSE,REMOVE_CONDITIONALS_ORDER_IF,REMOVE_CONDITIONALS_ORDER_ELSE,RETURN_VALS,VOID_METHOD_CALLS,EXPERIMENTAL_BIG_DECIMAL,EXPERIMENTAL_BIG_INTEGER,EXPERIMENTAL_MEMBER_VARIABLE,EXPERIMENTAL_NAKED_RECEIVER,REMOVE_INCREMENTS,EXPERIMENTAL_RETURN_VALUES_MUTATOR,EXPERIMENTAL_SWITCH,EXPERIMENTAL_BIG_DECIMAL,EXPERIMENTAL_BIG_INTEGER<br />......<br />......<br />================================================================================<br />- Timings<br />================================================================================<br />&gt; pre-scan for mutations : &lt; 1 second<br />&gt; scan classpath : &lt; 1 second<br />&gt; coverage and dependency analysis : &lt; 1 second<br />&gt; build mutation tests : &lt; 1 second<br />&gt; run mutation analysis : 4 seconds<br />--------------------------------------------------------------------------------<br />&gt; Total : 5 seconds<br />--------------------------------------------------------------------------------<br />================================================================================<br />- Statistics<br />================================================================================<br />&gt;&gt; Line Coverage: 63/195 (32%)<br />&gt;&gt; Generated 64 mutations Killed 7 (11%)<br />&gt;&gt; Mutations with no coverage 54. Test strength 70%<br />&gt;&gt; Ran 15 tests (0.23 tests per mutation)<br />Enhanced functionality available at https://www.arcmutate.com/<br />[INFO] ------------------------------------------------------------------------<br />[INFO] BUILD SUCCESS<br />[INFO] ------------------------------------------------------------------------<br />[INFO] Total time: 9.757 s<br />[INFO] Finished at: 2025-11-27T10:07:37-03:00<br />[INFO] ------------------------------------------------------------------------</pre>
<p>Execution time may vary depending on the size of the source code and the available resources on the machine where these tests are running.</p>
<p><span style="margin: 0px;padding: 0px">To see <span style="margin-top: 0px;margin-right: 0px;margin-left: 0px;padding: 0px">the HTML report graphically, open the <strong>target</strong> folder and look for the <strong>pit-reports</strong> folder</span>. The report will look like the following image:</span></p>
<div id="attachment_6101" style="width: 610px" class="wp-caption aligncenter"><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" aria-describedby="caption-attachment-6101" data-attachment-id="6101" data-permalink="https://www.javaadvent.com/2025/12/test-your-test.html/mutation-test-general" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?fit=1123%2C425&amp;ssl=1" data-orig-size="1123,425" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Mutation testing General Overview" data-image-description="&lt;p&gt;Mutation testing General Overview&lt;/p&gt;
" data-image-caption="&lt;p&gt;Mutation testing General Overview&lt;/p&gt;
" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?fit=300%2C114&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?fit=600%2C227&amp;ssl=1" class="size-large wp-image-6101" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?resize=600%2C227&#038;ssl=1" alt="Mutation testing General Overview" width="600" height="227" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?resize=1024%2C388&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?resize=300%2C114&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?resize=768%2C291&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?resize=508%2C192&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/mutation-test-general.png?w=1123&amp;ssl=1 1123w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a><p id="caption-attachment-6101" class="wp-caption-text">Mutation Testing: General Overview</p></div>
<p>To reduce execution time, you can use historical execution data to detect changes in code and tests. In concrete terms, the command implies adding only one parameter, as shown in the following block.</p>
<pre>$ mvn clean package org.pitest:pitest-maven:mutationCoverage -DwithHistory<br />......<br />.....<br />[INFO] ------------------------------------------------------------------------<br />[INFO] BUILD SUCCESS<br />[INFO] ------------------------------------------------------------------------<br />[INFO] Total time: 5.689 s<br />[INFO] Finished at: 2025-11-27T10:21:54-03:00<br />[INFO] ------------------------------------------------------------------------</pre>
<p>The execution time passes from 9,7 to 5,6 seconds in a small project with a few classes. The approach is beneficial when the applications have a lot of code and tests.</p>
<p>A critical aspect of mutation testing is the ability to use multiple mutation engines. An engine is responsible for modifying the source code; in some cases, changing all the logic inside a method or a class, rather than just adjusting the method&#8217;s parameters or its response. By default, Pitest uses Gregor, which introduces modifications to the different sentences of a technique, but it&#8217;s possible to use Descartes, which reduces the modifications. To use it, it&#8217;s necessary to introduce some changes, like the following:</p>
<div>
<pre>&lt;!-- Mutation Test --&gt;<br />&lt;plugin&gt;<br />    &lt;groupId&gt;org.pitest&lt;/groupId&gt;<br />    &lt;artifactId&gt;pitest-maven&lt;/artifactId&gt;<br />    &lt;version&gt;${pitest-maven.version}&lt;/version&gt;<br /><br />    &lt;configuration&gt;<br />       &lt;outputFormats&gt;<br />          &lt;outputFormat&gt;HTML&lt;/outputFormat&gt;<br />          &lt;outputFormat&gt;XML&lt;/outputFormat&gt;<br />       &lt;/outputFormats&gt;<br />       &lt;targetClasses&gt;<br />          &lt;param&gt;com.twa.flights.api.catalog.*&lt;/param&gt;<br />       &lt;/targetClasses&gt;<br />       &lt;targetTests&gt;<br />          &lt;param&gt;com.twa.flights.api.catalog.*&lt;/param&gt;<br />       &lt;/targetTests&gt;<br />       &lt;mutationEngine&gt;descartes&lt;/mutationEngine&gt;<br />    &lt;/configuration&gt;<br />    &lt;dependencies&gt;<br />       &lt;dependency&gt;<br />          &lt;groupId&gt;org.pitest&lt;/groupId&gt;<br />          &lt;artifactId&gt;pitest-junit5-plugin&lt;/artifactId&gt;<br />          &lt;version&gt;${pitest-junit5-plugin.version}&lt;/version&gt;<br />       &lt;/dependency&gt;<br />       &lt;dependency&gt;<br />          &lt;groupId&gt;eu.stamp-project&lt;/groupId&gt;<br />          &lt;artifactId&gt;descartes&lt;/artifactId&gt;<br />          &lt;version&gt;1.3.2&lt;/version&gt;<br />       &lt;/dependency&gt;<br />    &lt;/dependencies&gt;<br />&lt;/plugin&gt;</pre>
</div>
<p><span style="margin: 0px;padding: 0px">As a recommendation, check the latest version of this <a target="_blank" rel="noopener">library</a>, as new versions are released at regular intervals.</span></p>
<h2><span style="font-weight: 400">What are THE Challenges and costs?</span></h2>
<p>Introducing mutation testing into an existing application is not free of challenges, as it requires understanding its limitations and trade-offs. With this in mind, it&#8217;s crucial to set realistic expectations for this type of testing. Some of the most relevant issues are:</p>
<ul>
<li><span style="margin: 0px;padding: 0px"><strong>Execution time</strong>: Creating mutations and executing tests takes time because it involves generating source code variations and running tests to validate their effects. In large applications with hundreds of tests or large codebases, this could increase drastically. In some cases, this situation could be a barrier to implementing this type of testing in a CI pipeline.</span></li>
<li><strong>Flaky or unstable tests</strong>: In some cases, tests pass or fail sporadically due to issues with concurrent access to external resources. These scenarios could affect mutation execution, leading to false positives, so it&#8217;s essential to either exclude these tests or find a way to mitigate the problem.</li>
</ul>
<ul>
<li><span style="margin: 0px;padding: 0px"><strong>Resource consumption</strong>: In addition to the time required to execute the tests and create the mutation, there are other resource-related issues, such as CPU and memory usage. This can affect not only the pipeline running mutation tests but also other jobs running in the same CI environment. The big challenge here is to reduce resource consumption, limit mutations, or shorten the execution time of each pipeline.</span></li>
<li><strong>Complex configuration</strong>: Most tools or libraries offer many parameters to achieve the best configuration for each application. The first attempts to use these tools could lead to unrealistic expectations about the performance and results that are achievable. Finding the right balance between performance, accuracy, and execution time often requires several iterations.</li>
</ul>
<p>None of these issues invalidates the benefits of mutation testing, but it&#8217;s essential to develop a plan to mitigate or reduce their impact.</p>
<h2><span style="font-weight: 400">WHICH STRATEGIES EXIST for Adopting IT?</span></h2>
<p>Adopting a new technique or tool involves several considerations, especially in an existing application with many classes and tests. There is no magic formula for implementing mutation testing without pain, but there are different approaches to reduce the problems to a low level. Some of the most relevant strategies are:</p>
<ul>
<li><strong>Start small</strong>: This approach focuses on familiarizing with the tool and scanning just one package, module, or critical flow to identify potential implementation issues. It&#8217;s beneficial when there are too many unit tests on the application.</li>
<li><strong>Focus on high-risk code first</strong>: The critical code or flows are what anyone on a team or company wants to know whether it works or not. Adding mutation testing to a few classes that represent those flows can be implemented with minimal impact. Once everything is in order, we can incrementally update the configuration to scan more packages.</li>
<li><strong>Limiting the scope</strong>: At some point, execution time becomes a critical factor, so a possible approach is to limit the number or types of mutations that can be created. It could be a good starting point: only include the most relevant mutator during execution, and measure the impact before including all the mutations.</li>
<li><strong>Restrict execution</strong>: These tests may take longer to deploy, so a possible approach is to restrict their use locally or in the principal pipeline, which uses them to deploy to some productive environments.</li>
</ul>
<p>It is possible to use one of these strategies or combine them to achieve better results, but in all cases, the choice depends on the size of the application and the number of unit tests.</p>
<h2><span style="font-weight: 400">WHAT’S NEXT?</span></h2>
<p>There are many resources on unit testing and mutation testing. The following is just a short list of resources:</p>
<ul>
<li><a href="https://arxiv.org/html/2501.01873v1">Latent Mutants: A large-scale study on the Interplay between mutation testing and software evolution</a> by Jeongju Sohn</li>
<li><a href="https://ieeexplore.ieee.org/document/9524503">Practical Mutation Testing at Scale: A view from Google</a></li>
<li><a href="https://dl.acm.org/doi/10.1145/3530786">Mutation Testing in Evolving Systems: Studying the Relevance of Mutants to Code Evolution</a> by Milos Ojdanic</li>
<li><a href="https://homes.cs.washington.edu/~rjust/publ/mutation_testing_practices_icse_2021.pdf">Does mutation testing improve testing practices?</a> by Goran Petrovic</li>
</ul>
<p>Other resources could be great for understanding some concepts related to testing in depth:</p>
<ul>
<li><a href="https://www.manning.com/books/testing-web-apis">Testing Web APIs</a> by Mark Winteringham</li>
<li><a href="https://www.manning.com/books/unit-testing">Unit Testing Principles, Practices, and Patterns</a> by Vladimir Khorikov</li>
<li><a href="https://www.manning.com/books/software-testing-with-generative-ai">Software Testing with Generative AI</a> by Mark Winteringham</li>
</ul>
<p>Consider this just a small list of available resources. If something is unclear, find another video or resource.</p>
<h2><span style="font-weight: 400">CONCLUSION</span></h2>
<p class="isSelectedEnd">Creating tests for an application does not guarantee that nothing will go wrong, and mutation testing is not a silver bullet that can detect every possible issue. However, it provides a valuable and objective way to evaluate how practical existing tests really are.</p>
<p class="isSelectedEnd">A practical approach is to adopt it gradually: start with a small number of packages or a limited mutation scope, measure the effect on the build and pipeline, and then expand its use as appropriate.</p>
<p>Used pragmatically, mutation testing can significantly improve test quality and increase confidence in the application&#8217;s behavior without overwhelming the development process.</p>
<p></p><img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Ftest-your-test.html&amp;action_name=Test%20Your%20Test&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/test-your-test.html">Test Your Test</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/test-your-test.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5925</post-id>	</item>
		<item>
		<title>How Understanding Request Flow in Spring Boot Changed the Way I Code</title>
		<link>https://www.javaadvent.com/2025/12/how-understanding-request-flow-in-spring-boot-changed-the-way-i-code.html</link>
					<comments>https://www.javaadvent.com/2025/12/how-understanding-request-flow-in-spring-boot-changed-the-way-i-code.html#respond</comments>
		
		<dc:creator><![CDATA[Mohammed]]></dc:creator>
		<pubDate>Wed, 03 Dec 2025 02:02:20 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[Java Advent]]></category>
		<category><![CDATA[springboot]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5966</guid>

					<description><![CDATA[<p>When I first stepped into backend development, I believed programming was only about one thing: “If the output comes, the job is done.” I bundled everything into one giant file — controllers, logic, database access.I didn’t understand why Spring insisted on layers or why DI, IoC, and annotations were so important. The project structure looked [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fhow-understanding-request-flow-in-spring-boot-changed-the-way-i-code.html&amp;action_name=How%20Understanding%20Request%20Flow%20in%20Spring%20Boot%20Changed%20the%20Way%20I%20Code&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/how-understanding-request-flow-in-spring-boot-changed-the-way-i-code.html">How Understanding Request Flow in Spring Boot Changed the Way I Code</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>When I first stepped into backend development, I believed programming was only about one thing:</p>



<p><strong>“If the output comes, the job is done.”</strong></p>



<p>I bundled everything into one giant file — controllers, logic, database access.I didn’t understand why Spring insisted on layers or why DI, IoC, and annotations were so important. The project structure looked overwhelming, and everything felt abstract.</p>



<p>Everything changed the day I decided to <strong>trace my first Spring Boot request</strong> and watch how a simple API call traveled through the system. Suddenly, what looked like scattered pieces turned into a well-designed, meaningful architecture. It didn’t just teach me Spring Boot — it changed the way I think about writing software.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h3 class="wp-block-heading"><strong>What I Used to Think Backend Development Was</strong></h3>



<p>In the beginning, this was my reality:</p>



<ul class="wp-block-list">
<li>All classes dumped into one package</li>



<li>Endless, congested code inside one file</li>



<li>No idea what executes, what doesn’t, or why</li>



<li>Maven structure felt unnecessary</li>



<li>Layers didn’t make sense</li>



<li>Annotations were intimidating</li>



<li>No understanding of why Service existed between Controller and Repository</li>
</ul>



<p>It was like trying to build a house without knowing what bricks, beams, or foundations were.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h4 class="wp-block-heading"><strong>The Turning Point: Tracing My First Request</strong></h4>



<p>One day, I opened my console logs and started following the journey of an incoming request:</p>



<ul class="wp-block-list">
<li>It first hit the <strong>Controller</strong></li>



<li>Then passed into the <strong>Service</strong></li>



<li>Then reached the <strong>Repository</strong></li>



<li>Finally touched the <strong>Database</strong></li>



<li>And then returned the response through the same layers</li>
</ul>



<p>That visual flow — even just in the logs — changed everything for me.</p>



<p>I realized:</p>



<ul class="wp-block-list">
<li><strong>DI reduces tight coupling</strong></li>



<li><strong>IoC manages object lifecycles so you don’t</strong></li>



<li><strong>Layers exist to protect and organize the system</strong></li>



<li><strong>Spring Boot is powerful because of its structure, not despite it</strong></li>
</ul>



<p>Architecture became a living system, not a theory.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h4 class="wp-block-heading"><strong>Controller → Service → Repository: The Flow That Made Everything Click</strong></h4>



<h4 class="wp-block-heading"><strong>Controller — The Entry Point</strong></h4>



<p>The controller receives the request, interacts with the service, and returns the appropriate response or view.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
@Controller
@RequestMapping(&quot;/employees&quot;)
public class EmployeeController {

    private EmployeeService employeeService;

    public EmployeeController(EmployeeService theEmployeeService) {
        employeeService = theEmployeeService;
    }

    @GetMapping(&quot;/list&quot;)
    public String listEmployees(Model theModel) {
        List&lt;Employee&gt; theEmployees = employeeService.findAll();
        theModel.addAttribute(&quot;employees&quot;, theEmployees);
        return &quot;employees/list-employees&quot;;
    }
}


</pre></div>


<p>Controllers should be small and focused — no business logic inside them.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h4 class="wp-block-heading"><strong>Service — The Logic Layer</strong></h4>



<p>This is where business decisions happen. It protects the repository from being accessed directly.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
public interface EmployeeService {
    List&lt;Employee&gt; findAll();
    Employee findById(int theId);
    void save(Employee theEmployee);
    void deleteById(int theId);
    boolean existsByEmailId(Employee theEmployee);
}


</pre></div>


<p>Implementation:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
@Service
public class EmployeeServiceImpl implements EmployeeService {

    private EmployeeRepository employeeRepository;

    @Autowired
    public EmployeeServiceImpl(EmployeeRepository theEmployeeRepository) {
        employeeRepository = theEmployeeRepository;
    }

    @Override
    public List&lt;Employee&gt; findAll() {
        return employeeRepository.findAllByOrderByLastNameAsc();
    }
}


</pre></div>


<p>The service layer keeps your application clean, secure, and maintainable.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h4 class="wp-block-heading"><strong>Repository — Where Database Interactions Happen</strong></h4>



<p>With Spring Data JPA, repositories become incredibly simple:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
public interface EmployeeRepository extends JpaRepository&lt;Employee, Integer&gt; {
    boolean existsByEmail(String email);
    List&lt;Employee&gt; findAllByOrderByLastNameAsc();
}

</pre></div>


<p>The framework generates most queries automatically.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h4 class="wp-block-heading"><strong>Entity — Mapping Java Objects to Database Tables</strong></h4>



<p>Entities act as a bridge between the database and your Java application.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
@Entity
@Table(name=&quot;employee&quot;)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @NotBlank
    private String firstName;

    @NotBlank
    private String lastName;

    @NotBlank
    private String email;

    // constructors, getters, setters
}

</pre></div>


<p>Spring (plus Jackson) takes care of converting between JSON <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Java <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Database.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h4 class="wp-block-heading"><strong>When MVC Finally Made Sense</strong></h4>



<p>Moving into full MVC with Thymeleaf gave me clarity:</p>



<ul class="wp-block-list">
<li>Controllers handle the request</li>



<li>Services handle business logic</li>



<li>Repositories handle persistence</li>



<li>Templates display the data</li>
</ul>



<p>The project structure stopped looking like a burden and became something I respected. The flow felt natural and powerful.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h4 class="wp-block-heading"><strong>A Simple CRUD Example That Made It Real</strong></h4>



<p>The moment everything clicked was when I saw this flow in my own CRUD app:</p>



<ul class="wp-block-list">
<li>Client hits <strong>GET /employees/list</strong></li>



<li>Controller asks Service for data</li>



<li>Service queries the Repository</li>



<li>Repository returns data from the database</li>



<li>Controller adds data to Model</li>



<li>Thymeleaf displays it on the page</li>
</ul>



<p>Suddenly the architecture wasn’t an academic concept — it was working in front of me.</p>



<p><em>Here is the GitHub repository of the CRUD app I used in this article:</em></p>



<p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/MAffanG/employee-crud-api">**</a><a href="https://github.com/MAffanG/employee-crud-api**">https://github.com/MAffanG/employee-crud-api**</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><strong>What This Taught Me About Coding</strong></h2>



<p>Understanding the request flow transformed everything about my approach:</p>



<ul class="wp-block-list">
<li>I no longer chase outputs — I chase clarity</li>



<li>I think in terms of architecture, not hacks</li>



<li>Debugging became easier and faster</li>



<li>My code became cleaner and more intentional</li>



<li>I finally felt like a backend developer, not someone just trying things until they work</li>
</ul>



<p>This experience didn’t just teach me Spring Boot — it changed the way I respect software design as a whole.</p>



<p></p>
<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fhow-understanding-request-flow-in-spring-boot-changed-the-way-i-code.html&amp;action_name=How%20Understanding%20Request%20Flow%20in%20Spring%20Boot%20Changed%20the%20Way%20I%20Code&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /><p>The post <a href="https://www.javaadvent.com/2025/12/how-understanding-request-flow-in-spring-boot-changed-the-way-i-code.html">How Understanding Request Flow in Spring Boot Changed the Way I Code</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/how-understanding-request-flow-in-spring-boot-changed-the-way-i-code.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5966</post-id>	</item>
		<item>
		<title>Quarkus LangChain4j extension from grounds up</title>
		<link>https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html</link>
					<comments>https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html#respond</comments>
		
		<dc:creator><![CDATA[Martin Toshev]]></dc:creator>
		<pubDate>Tue, 02 Dec 2025 04:33:13 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[langchain4j]]></category>
		<category><![CDATA[quarkus]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5932</guid>

					<description><![CDATA[<p>LangChain4j is a top (if not the top) library in use for integrating Java applications with various LLMs (large language models). It provides a number of features such as a unified API for LLM integration, support for vector stores, prompt templates, RAG (retrievalaugmented generation) and more. While we can use it directly in popular frameworks [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fquarkus-langchain4j-extension-from-grounds-up.html&amp;action_name=Quarkus%20LangChain4j%20extension%20from%20grounds%20up&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html">Quarkus LangChain4j extension from grounds up</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>LangChain4j is a top (if not the top) library in use for integrating Java applications with various LLMs (large language models). It provides a number of features such as a unified API for LLM integration, support for vector stores, prompt templates, RAG (retrievalaugmented generation) and more. While we can use it directly in popular frameworks like Quarkus and Spring there are more specific extensions for those frameworks that provide for a simplified use of the various utilities provuded by the library.</p>
<p>The choice of Quarkus as a modern cloud-native framework in this article is not random: the framework already provides a huge ecosystem of extension and is being active used in more and more modern Java applicaations. And it really brings developer joy with all nice goodies like fast dev mode with hot reload, dev UI, dev services, unified configuration to name a few essential ones.</p>
<p>In this brief article we will discuss how the Quarkus LangChain4j extensions simplifies further use of the library and how we can use it to build an agentic application.</p>
<h3>So why a separate extension for LangChain4j ?</h3>
<p>There are several primary reasons to have a separate extension:</p>
<ul>
<li>the Quarkus architecture makes heavy use of build time optimizations including the extension mechanism so that extensions can be optimized for build time optimizations and native-image builds</li>
<li>better integration using CDI beans (Quarkus uses a deicated dependency injection mechanism called ArC as a CDI implementation) in the form of a additional @AiService annotation for autodiscovery of AI services</li>
<li>type-safe configuration for LangChain4j as supported by Quarkus using specific configuration classes for LangChain4j allowing user to define configuration such as:</li>
</ul>
<blockquote><p>quarkus.langchain4j.openai.api-key<br />
quarkus.langchain4j.chat-model</p></blockquote>
<ul>
<li>integration with Quarkus dev mode (i.e. for hot reload of configuration, Dev UI extension);</li>
<li>sensible default observability configuration, including one for OpenTelemetry and tracing of calls to LLMs</li>
</ul>
<p>All of this sounds really great, not only a simple plugin for Quarkus but a highly optimized and simplified use of the LangChain4j framework !</p>
<p>Now let&#8217;s just demonstrate how we can quick start using it with a practical example: a simple agentic healthcare application that given a description of medical conditions gives a prediction on which physician should be visited within a target hospital. The hospital stores an information in form of a table that maps a specific disease to a doctor that most expertise in that disease within the hospital. Let&#8217;s call it <strong>DocPredict</strong>.</p>
<h3>DocPredict: THE High-Level Architecture</h3>
<p>So straight to the point, let&#8217;s define how our simple healthcare assistant looks like from a high level perspective and build it:</p>
<p>&nbsp;</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/architecture.drawio-1.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="5944" data-permalink="https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html/architecture-drawio-2" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/architecture.drawio-1.png?fit=587%2C251&amp;ssl=1" data-orig-size="587,251" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="architecture.drawio" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/architecture.drawio-1.png?fit=300%2C128&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/architecture.drawio-1.png?fit=587%2C251&amp;ssl=1" class="aligncenter size-full wp-image-5944" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/architecture.drawio-1.png?resize=587%2C251&#038;ssl=1" alt="" width="587" height="251" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/architecture.drawio-1.png?w=587&amp;ssl=1 587w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/architecture.drawio-1.png?resize=300%2C128&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/architecture.drawio-1.png?resize=508%2C217&amp;ssl=1 508w" sizes="auto, (max-width: 587px) 100vw, 587px" /></a></p>
<p>Ideally if we want to build a complex frontend we would prefer to implement the DocPredict Web application using a modern favascript framework like React, Vue.js or AngularJS but in our case we will use a server-side rendering engine called <strong>Qute</strong> provided by Quarkus. Our service will interact with an OpenAI model to make the proper prediction based on a supplied definition and then try to map the respose to a proper doctor based on the existing hospital database. To not overcomplicate matters the database will be simply a CSV file with disease -&gt; doctor pairs.</p>
<h3>DocPredict: The code</h3>
<p>To quickly bootstrap our initial application we can navigate to <strong>https://code.quarkus.io/</strong> and supply proper configuration which is as simple as providing the basic Maven configuration and adding the following extensions:</p>
<ul>
<li>REST</li>
<li>REST Jackson</li>
<li>SmallRye OpenAPI (for Swagger)</li>
<li>LangChain4j OpenAI</li>
<li>Qute Web (for server-side templates)</li>
</ul>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="5978" data-permalink="https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html/quarkus_app-2" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?fit=1649%2C848&amp;ssl=1" data-orig-size="1649,848" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="quarkus_app" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?fit=300%2C154&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?fit=600%2C309&amp;ssl=1" class="aligncenter size-full wp-image-5978" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?resize=600%2C309&#038;ssl=1" alt="" width="600" height="309" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?w=1649&amp;ssl=1 1649w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?resize=300%2C154&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?resize=1024%2C527&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?resize=768%2C395&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?resize=1536%2C790&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?resize=1240%2C638&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/quarkus_app-1.png?resize=508%2C261&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p>Then download the generated application, unzip it and import it as a Maven project in your favourite IDE. In addition add the following library (we will use it to parse the CSV file with disease -&gt; doctor records):</p>
<blockquote><p>&lt;dependency&gt;<br />
&lt;groupId&gt;com.opencsv&lt;/groupId&gt;<br />
&lt;artifactId&gt;opencsv&lt;/artifactId&gt;<br />
&lt;version&gt;5.7.1&lt;/version&gt;<br />
&lt;/dependency&gt;</p></blockquote>
<p>Next generate an OpenAI key from <a href="https://platform.openai.com/api-keys">https://platform.openai.com/api-keys</a> if you don&#8217;t have one already and add it to the application.properties file of the project as follows:</p>
<blockquote><p><span style="background-color: #ffffff;padding: 0px 0px 0px 2px"><span style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt"><span style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">quarkus.langchain4j.chat-model.provider=openai</span><br />
quarkus.langchain4j.openai.api-key=&lt;key&gt;</span></span></p></blockquote>
<p>If we have multiple providers on the classpath (i.e. OpenPI and Gemini) we need to specify explicitly in the configuration which provider we want to use. In case you don&#8217;t have an API credit with a credit for OpenAI (unless you use a trial account with small initial credit) you can use a the free DeepSeek model via the OpenRouter platform for example: <a href="https://openrouter.ai/">https://openrouter.ai/ </a>Generate an API key there and use it as follows (the DeepSeek API is compatible with OpenAI):</p>
<blockquote>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<p style="margin: 0">quarkus.langchain4j.openai.api-key=&lt;openrouter_api_key&gt;</p>
<p style="margin: 0">quarkus.langchain4j.openai.base-url=https://openrouter.ai/api/v1</p>
<p style="margin: 0">quarkus.langchain4j.openai.model=deepseek/deepseek-r1:free</p>
<p style="margin: 0">
</div>
</div>
</div>
</div>
</blockquote>
<p>In the <strong>src/main/resources</strong> folder let&#8217;s create a <strong>doctors.csv</strong> file with the following content:</p>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<blockquote>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<p style="margin: 0">diabetes,<span style="text-decoration: underline">Dr</span>. Smith,<span style="text-decoration: underline">Endocrinologist</span></p>
<p style="margin: 0"><span style="text-decoration: underline">hypertension</span>,<span style="text-decoration: underline">Dr</span>. <span style="text-decoration: underline">Johnson</span>,<span style="text-decoration: underline">Cardiologist</span></p>
<p style="margin: 0">asthma,<span style="text-decoration: underline">Dr</span>. Lee,<span style="text-decoration: underline">Pulmonologist</span></p>
<p style="margin: 0"><span style="text-decoration: underline">migraine</span>,<span style="text-decoration: underline">Dr</span>. <span style="text-decoration: underline">Patel</span>,<span style="text-decoration: underline">Neurologist</span></p>
<p style="margin: 0">arthritis,<span style="text-decoration: underline">Dr</span>. Brown,<span style="text-decoration: underline">Rheumatologist</span></p>
<p style="margin: 0">pneumonia,<span style="text-decoration: underline">Dr</span>. <span style="text-decoration: underline">Davis</span>,<span style="text-decoration: underline">Internist</span></p>
<p style="margin: 0">depression,<span style="text-decoration: underline">Dr</span>. Wilson,Psychiatrist</p>
<p style="margin: 0">allergy,<span style="text-decoration: underline">Dr</span>. <span style="text-decoration: underline">Clark</span>,<span style="text-decoration: underline">Allergist</span></p>
<p style="margin: 0">kidney Stones,<span style="text-decoration: underline">Dr</span>. <span style="text-decoration: underline">Lewis</span>,<span style="text-decoration: underline">Urologist</span></p>
<p style="margin: 0">anemia,<span style="text-decoration: underline">Dr</span>. Hall,<span style="text-decoration: underline">Hematologist</span></p>
</div>
</div>
</blockquote>
</div>
<p>Now let&#8217;s add logic for our application. First we will create an AI service with a prompt template that is used to interact with the LLM:</p>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<blockquote>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">package</span><span style="color: #000000"> com.javaadvent.docpredict;</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> dev.langchain4j.service.SystemMessage;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> dev.langchain4j.service.UserMessage;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> io.quarkiverse.langchain4j.RegisterAiService;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> jakarta.enterprise.context.ApplicationScoped;</span></p>
<p style="margin: 0"><span style="color: #646464">@RegisterAiService</span></p>
<p style="margin: 0"><span style="color: #646464">@SystemMessage</span><span style="color: #000000">(</span><span style="color: #2a00ff">&#8220;You are a professional doctor&#8221;</span><span style="color: #000000">) </span></p>
<p style="margin: 0"><span style="color: #646464">@ApplicationScoped</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">public</span> <span style="color: #7f0055;font-weight: bold">interface</span><span style="color: #000000"> DiseasePredictionService {</span></p>
<p style="margin: 0"><span style="color: #646464">@UserMessage</span><span style="color: #000000">(</span><span style="color: #2a00ff">&#8220;&#8221;&#8221;</span></p>
<p style="margin: 0"><span style="color: #2a00ff"> Try to predict disease from the following list of symptoms: {symptoms}.</span></p>
<p style="margin: 0"><span style="color: #2a00ff"> Return only one predicted disease as a single word without extra symbols in lowercase.</span></p>
<p style="margin: 0"><span style="color: #2a00ff"> &#8220;&#8221;&#8221;</span><span style="color: #000000">)</span></p>
<p style="margin: 0"><span style="color: #000000;background-color: #d4d4d4">String</span><span style="color: #000000"> predictDisease(</span><span style="color: #000000;background-color: #d4d4d4">String</span> <span style="color: #6a3e3e">symptoms</span><span style="color: #000000">); </span></p>
<p style="margin: 0"><span style="color: #000000">}</span></p>
<p style="margin: 0">
</div>
</div>
</blockquote>
</div>
<p>The <span style="color: #646464">@RegisterAiService is the essential annotation provided by the Quarkus LangChain4j extension that defines a service that interacts with the LLM. We also provide a system message to the LLM using the @SystemMessage annotation as an additional context. We define the predictDisease method that uses a prompt template defined by the @UserMessage annotation.</span></p>
</div>
<p>Using that service now let&#8217;s create a REST resource that is able to make proper predictions:</p>
<p>&nbsp;</p>
</div>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<blockquote>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">package</span><span style="color: #000000"> com.javaadvent.docpredict;</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.io.BufferedReader;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.io.IOException;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.io.InputStream;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.io.InputStreamReader;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.util.HashMap;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> org.jboss.logging.Logger;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> com.opencsv.CSVReader;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> com.opencsv.exceptions.CsvValidationException;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> jakarta.inject.Inject;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> jakarta.ws.rs.POST;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> jakarta.ws.rs.Path;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> jakarta.ws.rs.Produces;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> jakarta.ws.rs.core.MediaType;</span></p>
<p style="margin: 0"><span style="color: #646464">@Path</span><span style="color: #000000">(</span><span style="color: #2a00ff">&#8220;/predict&#8221;</span><span style="color: #000000">)</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">public</span> <span style="color: #7f0055;font-weight: bold">class</span><span style="color: #000000"> PredictionResource {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"><br />
private</span> <span style="color: #7f0055;font-weight: bold">static</span> <span style="color: #7f0055;font-weight: bold">final</span><span style="color: #000000"> Logger </span><span style="color: #0000c0;font-style: italic;font-weight: bold">LOGGER</span><span style="color: #000000"> = Logger.</span><span style="color: #000000;font-style: italic">getLogger</span><span style="color: #000000">(PredictionResource.</span><span style="color: #7f0055;font-weight: bold">class</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #646464"> @Inject</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> private</span><span style="color: #000000"> DiseasePredictionService </span><span style="color: #0000c0">predictionService</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #646464"> @POST</span></p>
<p style="margin: 0"><span style="color: #646464"> @Produces</span><span style="color: #000000">(MediaType.</span><span style="color: #0000c0;font-style: italic;font-weight: bold">APPLICATION_JSON</span><span style="color: #000000">)</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> public</span><span style="color: #000000"> PredictionResponse predictDisease(PredictionRequest </span><span style="color: #6a3e3e">request</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #000000"> PredictionResponse </span><span style="color: #6a3e3e">response</span><span style="color: #000000"> = </span><span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> PredictionResponse();</span></p>
<p style="margin: 0"><span style="color: #000000"> String </span><span style="color: #6a3e3e">predictedDisease</span><span style="color: #000000"> = </span><span style="color: #0000c0">predictionService</span><span style="color: #000000">.predictDisease(</span><span style="color: #6a3e3e">request</span><span style="color: #000000">.getSymptoms());</span></p>
<p style="margin: 0"><span style="color: #6a3e3e"> response</span><span style="color: #000000">.setDisease(</span><span style="color: #6a3e3e">predictedDisease</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #6a3e3e"> response</span><span style="color: #000000">.setDoctor(determineDoctor(</span><span style="color: #6a3e3e">predictedDisease</span><span style="color: #000000">));</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> return</span> <span style="color: #6a3e3e">response</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> private</span><span style="color: #000000"> String determineDoctor(String </span><span style="color: #6a3e3e">predictedDisease</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #000000"> InputStream </span><span style="color: #6a3e3e">is</span><span style="color: #000000"> = PredictionResource.</span><span style="color: #7f0055;font-weight: bold">class</span><span style="color: #000000">.getResourceAsStream(</span><span style="color: #2a00ff">&#8220;/doctors.csv&#8221;</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> if</span><span style="color: #000000"> (</span><span style="color: #6a3e3e">is</span><span style="color: #000000"> == </span><span style="color: #7f0055;font-weight: bold">null</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> throw</span> <span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> IllegalStateException(</span><span style="color: #2a00ff">&#8220;CSV file not found&#8221;</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #000000"> HashMap&lt;String, String&gt; </span><span style="color: #6a3e3e">diseaseToDoctor</span><span style="color: #000000"> = </span><span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> HashMap&lt;&gt;();</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> try</span><span style="color: #000000"> (BufferedReader </span><span style="color: #6a3e3e">br</span><span style="color: #000000"> = </span><span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> BufferedReader(</span><span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> InputStreamReader(</span><span style="color: #6a3e3e">is</span><span style="color: #000000">));</span></p>
<p style="margin: 0"><span style="color: #000000"> CSVReader </span><span style="color: #6a3e3e">csvReader</span><span style="color: #000000"> = </span><span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> CSVReader(</span><span style="color: #6a3e3e">br</span><span style="color: #000000">)) {</span></p>
<p style="margin: 0"><span style="color: #000000"> String[] </span><span style="color: #6a3e3e">row</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> while</span><span style="color: #000000"> ((</span><span style="color: #6a3e3e">row</span><span style="color: #000000"> = </span><span style="color: #6a3e3e">csvReader</span><span style="color: #000000">.</span><span style="color: #000000;background-color: #d4d4d4">readNext</span><span style="color: #000000">()) != </span><span style="color: #7f0055;font-weight: bold">null</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #6a3e3e"> diseaseToDoctor</span><span style="color: #000000">.put(</span><span style="color: #6a3e3e">row</span><span style="color: #000000">[0], String.</span><span style="color: #000000;font-style: italic">format</span><span style="color: #000000">(</span><span style="color: #2a00ff">&#8220;%s (%s)&#8221;</span><span style="color: #000000">, </span><span style="color: #6a3e3e">row</span><span style="color: #000000">[1], </span><span style="color: #6a3e3e">row</span><span style="color: #000000">[2]));</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #000000"> } </span><span style="color: #7f0055;font-weight: bold">catch</span><span style="color: #000000"> (IOException | </span><span style="color: #000000;background-color: #d4d4d4">CsvValidationException</span> <span style="color: #6a3e3e">e</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #0000c0;font-style: italic;font-weight: bold"> LOGGER</span><span style="color: #000000">.error(</span><span style="color: #6a3e3e">e</span><span style="color: #000000">.getMessage(), </span><span style="color: #6a3e3e">e</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #000000"> String </span><span style="color: #6a3e3e">doctor</span><span style="color: #000000"> = </span><span style="color: #6a3e3e">diseaseToDoctor</span><span style="color: #000000">.get(</span><span style="color: #6a3e3e">predictedDisease</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> if</span><span style="color: #000000">(</span><span style="color: #6a3e3e">doctor</span><span style="color: #000000"> == </span><span style="color: #7f0055;font-weight: bold">null</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #6a3e3e"> doctor</span><span style="color: #000000"> = </span><span style="color: #2a00ff">&#8220;Visit general practitioner&#8221;</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> return</span> <span style="color: #6a3e3e">doctor</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #000000">}</span></p>
</blockquote>
</div>
</div>
<blockquote><p>&nbsp;</p>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">package</span><span style="color: #000000"> com.javaadvent.docpredict;</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">public</span> <span style="color: #7f0055;font-weight: bold">class</span><span style="color: #000000"> PredictionRequest {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> private</span><span style="color: #000000"> String </span><span style="color: #0000c0">symptoms</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"><br />
public</span><span style="color: #000000"> String getSymptoms() {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> return</span> <span style="color: #0000c0">symptoms</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"><br />
public</span> <span style="color: #7f0055;font-weight: bold">void</span><span style="color: #000000"> setSymptoms(String </span><span style="color: #6a3e3e">symptoms</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> this</span><span style="color: #000000">.</span><span style="color: #0000c0">symptoms</span><span style="color: #000000"> = </span><span style="color: #6a3e3e">symptoms</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #000000">}</span></p>
<p style="margin: 0">
</div>
</div>
<p>&nbsp;</p></blockquote>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<blockquote>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">package</span><span style="color: #000000"> com.javaadvent.docpredict;</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">public</span> <span style="color: #7f0055;font-weight: bold">class</span><span style="color: #000000"> PredictionResponse {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"><br />
private</span><span style="color: #000000"> String </span><span style="color: #0000c0">disease</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"><br />
private</span><span style="color: #000000"> String </span><span style="color: #0000c0">doctor</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"><br />
public</span><span style="color: #000000"> String getDisease() {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> return</span> <span style="color: #0000c0">disease</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"><br />
public</span> <span style="color: #7f0055;font-weight: bold">void</span> <span style="color: #000000;background-color: #d4d4d4">setDisease</span><span style="color: #000000">(String </span><span style="color: #6a3e3e">disease</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> this</span><span style="color: #000000">.</span><span style="color: #0000c0">disease</span><span style="color: #000000"> = </span><span style="color: #6a3e3e">disease</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> public</span><span style="color: #000000"> String getDoctor() {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> return</span> <span style="color: #0000c0">doctor</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> public</span> <span style="color: #7f0055;font-weight: bold">void</span><span style="color: #000000"> setDoctor(String </span><span style="color: #6a3e3e">doctor</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> this</span><span style="color: #000000">.</span><span style="color: #0000c0">doctor</span><span style="color: #000000"> = </span><span style="color: #6a3e3e">doctor</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #000000">}</span></p>
</blockquote>
</div>
</div>
<p>We inject the AI service that makes the call to the model API to make the prediction and then based on the result and the CSV file with disease -&gt; doctor mappings tries to determine which practitioner is best suited for the described symptoms. If a practitioner cannot be suggested the application recommends visiting the general practitioner.</p>
<p>At that point we are ready to test the endpoint using Swagger when we start the Quarkus application and navigate to http://localhost:8080/q/swagger-ui/</p>
</div>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="5979" data-permalink="https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html/docpredict_swagger_1" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?fit=1859%2C787&amp;ssl=1" data-orig-size="1859,787" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="docpredict_swagger_1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?fit=300%2C127&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?fit=600%2C254&amp;ssl=1" class="aligncenter size-full wp-image-5979" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?resize=600%2C254&#038;ssl=1" alt="" width="600" height="254" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?w=1859&amp;ssl=1 1859w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?resize=300%2C127&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?resize=1024%2C434&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?resize=768%2C325&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?resize=1536%2C650&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?resize=1240%2C525&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?resize=508%2C215&amp;ssl=1 508w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_1.png?w=1800&amp;ssl=1 1800w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="5980" data-permalink="https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html/docpredict_swagger_2" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?fit=1822%2C367&amp;ssl=1" data-orig-size="1822,367" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="docpredict_swagger_2" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?fit=300%2C60&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?fit=600%2C121&amp;ssl=1" class="aligncenter size-full wp-image-5980" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?resize=600%2C121&#038;ssl=1" alt="" width="600" height="121" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?w=1822&amp;ssl=1 1822w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?resize=300%2C60&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?resize=1024%2C206&amp;ssl=1 1024w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?resize=768%2C155&amp;ssl=1 768w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?resize=1536%2C309&amp;ssl=1 1536w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?resize=1240%2C250&amp;ssl=1 1240w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/docpredict_swagger_2.png?resize=508%2C102&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a></p>
<p>Finally let&#8217;s make this a bit more convenient by adding a simple UI around it using Quarkus Qute template engine.</p>
<p>Create the following templates under src/main/resources/templates:</p>
<blockquote><p>index.qute.html</p>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<p style="margin: 0"><span style="color: #93a1a1">&lt;!</span><span style="color: #268bd2">DOCTYPE</span> <span style="color: #93a1a1">html&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">html</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">head</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">title</span><span style="color: #93a1a1">&gt;</span>DocPredict<span style="color: #93a1a1">&lt;/</span><span style="color: #268bd2">title</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;/</span><span style="color: #268bd2">head</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">body</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">h1</span><span style="color: #93a1a1">&gt;</span>Enter your symptoms<span style="color: #93a1a1">&lt;/</span><span style="color: #268bd2">h1</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">form</span> <span style="color: #93a1a1">id</span>=<span style="color: #93a1a1">&#8220;</span><span style="color: #2aa198">symptomsForm</span><span style="color: #93a1a1">&#8220;&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">input</span> <span style="color: #93a1a1">type</span>=<span style="color: #93a1a1">&#8220;</span><span style="color: #2aa198">text</span><span style="color: #93a1a1">&#8220;</span> <span style="color: #93a1a1">name</span>=<span style="color: #93a1a1">&#8220;</span><span style="color: #2aa198">symptoms</span><span style="color: #93a1a1">&#8220;</span> <span style="color: #93a1a1">placeholder</span>=<span style="color: #93a1a1">&#8220;</span><span style="color: #2aa198">Your symptoms</span><span style="color: #93a1a1">&#8220;</span> <span style="color: #93a1a1">required&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">button</span> <span style="color: #93a1a1">type</span>=<span style="color: #93a1a1">&#8220;</span><span style="color: #2aa198">submit</span><span style="color: #93a1a1">&#8220;&gt;</span>Suggest specialist<span style="color: #93a1a1">&lt;/</span><span style="color: #268bd2">button</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;/</span><span style="color: #268bd2">form</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">div</span> <span style="color: #93a1a1">id</span>=<span style="color: #93a1a1">&#8220;</span><span style="color: #2aa198">prediction</span><span style="color: #93a1a1">&#8220;&gt;&lt;/</span><span style="color: #268bd2">div</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">script</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #073642;font-weight: bold">const</span> <span style="color: #cb4b16">form</span> <span style="color: #859900">=</span> <span style="color: #268bd2">document</span>.<span style="color: #268bd2">getElementById</span>(<span style="color: #2aa198">&#8216;symptomsForm&#8217;</span>);</p>
<p style="margin: 0"><span style="color: #073642;font-weight: bold">const</span> <span style="color: #cb4b16">resultDiv</span> <span style="color: #859900">=</span> <span style="color: #268bd2">document</span>.<span style="color: #268bd2">getElementById</span>(<span style="color: #2aa198">&#8216;prediction&#8217;</span>);</p>
<p style="margin: 0"><span style="color: #268bd2">form</span>.<span style="color: #268bd2">addEventListener</span>(<span style="color: #2aa198">&#8216;submit&#8217;</span>, <span style="color: #073642;font-weight: bold">async</span> (e) <span style="color: #073642;font-weight: bold">=&gt;</span> {</p>
<p style="margin: 0"><span style="color: #268bd2">e</span>.<span style="color: #268bd2">preventDefault</span>(); <span style="color: #93a1a1">// prevent full page reload</span></p>
<p style="margin: 0"><span style="color: #073642;font-weight: bold">const</span> <span style="color: #cb4b16">formData</span> <span style="color: #859900">=</span> <span style="color: #859900">new</span> <span style="color: #268bd2">FormData</span>(<span style="color: #268bd2">form</span>);</p>
<p style="margin: 0"><span style="color: #073642;font-weight: bold">const</span> <span style="color: #cb4b16">response</span> <span style="color: #859900">=</span> <span style="color: #859900">await</span> <span style="color: #268bd2">fetch</span>(<span style="color: #2aa198">&#8216;/index/submit&#8217;</span>, {</p>
<p style="margin: 0">method: <span style="color: #2aa198">&#8216;POST&#8217;</span>,</p>
<p style="margin: 0">body: <span style="color: #859900">new</span> <span style="color: #268bd2">URLSearchParams</span>(<span style="color: #268bd2">formData</span>)</p>
<p style="margin: 0">});</p>
<p style="margin: 0"><span style="color: #93a1a1"> // insert HTML fragment into page</span></p>
<p style="margin: 0"><span style="color: #073642;font-weight: bold">const</span> <span style="color: #cb4b16">html</span> <span style="color: #859900">=</span> <span style="color: #859900">await</span> <span style="color: #268bd2">response</span>.<span style="color: #268bd2">text</span>();</p>
<p style="margin: 0"><span style="color: #268bd2">resultDiv</span>.<span style="color: #268bd2">innerHTML</span> <span style="color: #859900">=</span> <span style="color: #268bd2">html</span>;</p>
<p style="margin: 0">});</p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;/</span><span style="color: #268bd2">script</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;/</span><span style="color: #268bd2">body</span><span style="color: #93a1a1">&gt;</span></p>
<p style="margin: 0"><span style="color: #93a1a1">&lt;/</span><span style="color: #268bd2">html</span><span style="color: #93a1a1">&gt;</span></p>
</div>
</div>
<p>prediction.qute.html<br />
<span style="background-color: #ffffff;padding: 0px 0px 0px 2px"><span style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt"><span style="color: #93a1a1">&lt;</span><span style="color: #268bd2">p</span><span style="color: #93a1a1">&gt;</span>{doctor} !<span style="color: #93a1a1">&lt;/</span><span style="color: #268bd2">p</span><span style="color: #93a1a1">&gt;</span></span></span></p></blockquote>
<p>Add the following endpoint to handle the template rendering:</p>
<div style="background-color: #ffffff;padding: 0px 0px 0px 2px">
<div style="color: #000000;background-color: #ffffff;font-family: 'Consolas';font-size: 10pt">
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">package</span><span style="color: #000000"> com.javaadvent.docpredict;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.io.BufferedReader;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.io.IOException;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.io.InputStream;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.io.InputStreamReader;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> java.util.HashMap;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> org.jboss.logging.Logger;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> com.opencsv.CSVReader;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> com.opencsv.exceptions.CsvValidationException;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> io.quarkus.qute.Template;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> io.quarkus.qute.TemplateInstance;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> jakarta.inject.Inject;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> jakarta.ws.rs.*;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">import</span><span style="color: #000000"> jakarta.ws.rs.core.MediaType;</span></p>
<p style="margin: 0"><span style="color: #646464">@Path</span><span style="color: #000000">(</span><span style="color: #2a00ff">&#8220;index&#8221;</span><span style="color: #000000">)</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold">public</span> <span style="color: #7f0055;font-weight: bold">class</span><span style="color: #000000"> IndexResource {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> private</span> <span style="color: #7f0055;font-weight: bold">static</span> <span style="color: #7f0055;font-weight: bold">final</span><span style="color: #000000"> Logger </span><span style="color: #0000c0;font-style: italic;font-weight: bold">LOGGER</span><span style="color: #000000"> = Logger.</span><span style="color: #000000;font-style: italic">getLogger</span><span style="color: #000000">(IndexResource.</span><span style="color: #7f0055;font-weight: bold">class</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #646464"> @Inject</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> private</span><span style="color: #000000"> Template </span><span style="color: #0000c0">index</span><span style="color: #000000">; </span><span style="color: #3f7f5f">// index.qute.html</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #646464"> @Inject</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> private</span><span style="color: #000000"> Template </span><span style="color: #0000c0">prediction</span><span style="color: #000000">; </span><span style="color: #3f7f5f">// prediction.qute.html</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #646464"> @Inject</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> private</span><span style="color: #000000"> DiseasePredictionService </span><span style="color: #0000c0">predictionService</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #646464"> @GET</span></p>
<p style="margin: 0"><span style="color: #646464"> @Produces</span><span style="color: #000000">(MediaType.</span><span style="color: #0000c0;font-style: italic;font-weight: bold">TEXT_HTML</span><span style="color: #000000">)</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> public</span><span style="color: #000000"> TemplateInstance getForm() {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> return</span> <span style="color: #0000c0">index</span><span style="color: #000000">.instance();</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #646464"> @POST</span></p>
<p style="margin: 0"><span style="color: #646464"> @Path</span><span style="color: #000000">(</span><span style="color: #2a00ff">&#8220;submit&#8221;</span><span style="color: #000000">)</span></p>
<p style="margin: 0"><span style="color: #646464"> @Consumes</span><span style="color: #000000">(MediaType.</span><span style="color: #0000c0;font-style: italic;font-weight: bold">APPLICATION_FORM_URLENCODED</span><span style="color: #000000">)</span></p>
<p style="margin: 0"><span style="color: #646464"> @Produces</span><span style="color: #000000">(MediaType.</span><span style="color: #0000c0;font-style: italic;font-weight: bold">TEXT_HTML</span><span style="color: #000000">)</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> public</span><span style="color: #000000"> TemplateInstance submit(</span><span style="color: #646464">@FormParam</span><span style="color: #000000">(</span><span style="color: #2a00ff">&#8220;symptoms&#8221;</span><span style="color: #000000">) String </span><span style="color: #6a3e3e">symptoms</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #000000"> String </span><span style="color: #6a3e3e">predictedDisease</span><span style="color: #000000"> = </span><span style="color: #0000c0">predictionService</span><span style="color: #000000">.predictDisease(</span><span style="color: #6a3e3e">symptoms</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> return</span> <span style="color: #0000c0">prediction</span><span style="color: #000000">.data(</span><span style="color: #2a00ff">&#8220;doctor&#8221;</span><span style="color: #000000">, determineDoctor(</span><span style="color: #6a3e3e">predictedDisease</span><span style="color: #000000">));</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> private</span><span style="color: #000000"> String determineDoctor(String </span><span style="color: #6a3e3e">predictedDisease</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #000000"> InputStream </span><span style="color: #6a3e3e">is</span><span style="color: #000000"> = PredictionResource.</span><span style="color: #7f0055;font-weight: bold">class</span><span style="color: #000000">.getResourceAsStream(</span><span style="color: #2a00ff">&#8220;/doctors.csv&#8221;</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> if</span><span style="color: #000000"> (</span><span style="color: #6a3e3e">is</span><span style="color: #000000"> == </span><span style="color: #7f0055;font-weight: bold">null</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> throw</span> <span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> IllegalStateException(</span><span style="color: #2a00ff">&#8220;CSV file not found&#8221;</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #000000"> HashMap&lt;String, String&gt; </span><span style="color: #6a3e3e">diseaseToDoctor</span><span style="color: #000000"> = </span><span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> HashMap&lt;&gt;();</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> try</span><span style="color: #000000"> (BufferedReader </span><span style="color: #6a3e3e">br</span><span style="color: #000000"> = </span><span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> BufferedReader(</span><span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> InputStreamReader(</span><span style="color: #6a3e3e">is</span><span style="color: #000000">));</span></p>
<p style="margin: 0"><span style="color: #000000"> CSVReader </span><span style="color: #6a3e3e">csvReader</span><span style="color: #000000"> = </span><span style="color: #7f0055;font-weight: bold">new</span><span style="color: #000000"> CSVReader(</span><span style="color: #6a3e3e">br</span><span style="color: #000000">)) {</span></p>
<p style="margin: 0"><span style="color: #000000"> String[] </span><span style="color: #6a3e3e">row</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> while</span><span style="color: #000000"> ((</span><span style="color: #6a3e3e">row</span><span style="color: #000000"> = </span><span style="color: #6a3e3e">csvReader</span><span style="color: #000000">.</span><span style="color: #000000;background-color: #d4d4d4">readNext</span><span style="color: #000000">()) != </span><span style="color: #7f0055;font-weight: bold">null</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #6a3e3e"> diseaseToDoctor</span><span style="color: #000000">.put(</span><span style="color: #6a3e3e">row</span><span style="color: #000000">[0], String.</span><span style="color: #000000;font-style: italic">format</span><span style="color: #000000">(</span><span style="color: #2a00ff">&#8220;%s (%s)&#8221;</span><span style="color: #000000">, </span><span style="color: #6a3e3e">row</span><span style="color: #000000">[1], </span><span style="color: #6a3e3e">row</span><span style="color: #000000">[2]));</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #000000"> } </span><span style="color: #7f0055;font-weight: bold">catch</span><span style="color: #000000"> (IOException | </span><span style="color: #000000;background-color: #d4d4d4">CsvValidationException</span> <span style="color: #6a3e3e">e</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #0000c0;font-style: italic;font-weight: bold"> LOGGER</span><span style="color: #000000">.error(</span><span style="color: #6a3e3e">e</span><span style="color: #000000">.getMessage(), </span><span style="color: #6a3e3e">e</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #000000"> String </span><span style="color: #6a3e3e">doctor</span><span style="color: #000000"> = </span><span style="color: #6a3e3e">diseaseToDoctor</span><span style="color: #000000">.get(</span><span style="color: #6a3e3e">predictedDisease</span><span style="color: #000000">);</span></p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> if</span><span style="color: #000000">(</span><span style="color: #6a3e3e">doctor</span><span style="color: #000000"> == </span><span style="color: #7f0055;font-weight: bold">null</span><span style="color: #000000">) {</span></p>
<p style="margin: 0"><span style="color: #6a3e3e"> doctor</span><span style="color: #000000"> = </span><span style="color: #2a00ff">&#8220;Visit general practitioner&#8221;</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p>&nbsp;</p>
<p style="margin: 0"><span style="color: #7f0055;font-weight: bold"> return</span> <span style="color: #6a3e3e">doctor</span><span style="color: #000000">;</span></p>
<p style="margin: 0"><span style="color: #000000"> }</span></p>
<p style="margin: 0"><span style="color: #000000">}</span></p>
</div>
</div>
<p>And there we go once we navigate to <a href="http://localhost:8080/index">http://localhost:8080/indexlocalhost:8080/index</a>:</p>
<p><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_1.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="5982" data-permalink="https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html/qute_1" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_1.png?fit=680%2C242&amp;ssl=1" data-orig-size="680,242" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="qute_1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_1.png?fit=300%2C107&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_1.png?fit=600%2C214&amp;ssl=1" class="size-full wp-image-5982 alignnone" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_1.png?resize=600%2C214&#038;ssl=1" alt="" width="600" height="214" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_1.png?w=680&amp;ssl=1 680w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_1.png?resize=300%2C107&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_1.png?resize=508%2C181&amp;ssl=1 508w" sizes="auto, (max-width: 600px) 100vw, 600px" /></a><a href="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_2.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="5983" data-permalink="https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html/qute_2" data-orig-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_2.png?fit=593%2C248&amp;ssl=1" data-orig-size="593,248" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="qute_2" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_2.png?fit=300%2C125&amp;ssl=1" data-large-file="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_2.png?fit=593%2C248&amp;ssl=1" class="size-full wp-image-5983 aligncenter" src="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_2.png?resize=593%2C248&#038;ssl=1" alt="" width="593" height="248" srcset="https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_2.png?w=593&amp;ssl=1 593w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_2.png?resize=300%2C125&amp;ssl=1 300w, https://i0.wp.com/www.javaadvent.com/content/uploads/2025/11/qute_2.png?resize=508%2C212&amp;ssl=1 508w" sizes="auto, (max-width: 593px) 100vw, 593px" /></a></p>
<h3>Summary</h3>
<p>As you can see it is quite straight-forward to get started with Quarkus LangChain4j extension and furthermore you can start more complex capabilities in your application like RAG, context memory, tool support etc.<img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Fquarkus-langchain4j-extension-from-grounds-up.html&amp;action_name=Quarkus%20LangChain4j%20extension%20from%20grounds%20up&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html">Quarkus LangChain4j extension from grounds up</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/quarkus-langchain4j-extension-from-grounds-up.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5932</post-id>	</item>
		<item>
		<title>Lighting the Way: Java, AI, and a Season of New Ideas</title>
		<link>https://www.javaadvent.com/2025/12/lighting-the-way-java-ai-and-a-season-of-new-ideas.html</link>
					<comments>https://www.javaadvent.com/2025/12/lighting-the-way-java-ai-and-a-season-of-new-ideas.html#respond</comments>
		
		<dc:creator><![CDATA[Markus Eisele]]></dc:creator>
		<pubDate>Mon, 01 Dec 2025 01:00:25 +0000</pubDate>
				<category><![CDATA[2025]]></category>
		<guid isPermaLink="false">https://www.javaadvent.com/?p=5922</guid>

					<description><![CDATA[<p>When December arrives, I always feel the same mix of nostalgia and excitement. The year starts to slow down, calendars fill with end-of-year meetings, and yet this is when the Java community does something uniquely joyful. We show up every day for 24 days and share what we’ve learned, built, discovered, and struggled with. It’s [&#8230;]<img src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Flighting-the-way-java-ai-and-a-season-of-new-ideas.html&amp;action_name=Lighting%20the%20Way%3A%20Java%2C%20AI%2C%20and%20a%20Season%20of%20New%20Ideas&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/lighting-the-way-java-ai-and-a-season-of-new-ideas.html">Lighting the Way: Java, AI, and a Season of New Ideas</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p class="p1">When December arrives, I always feel the same mix of nostalgia and excitement. The year starts to slow down, calendars fill with end-of-year meetings, and yet this is when the Java community does something uniquely joyful. We show up every day for 24 days and share what we’ve learned, built, discovered, and struggled with. It’s one of my favorite traditions, because it feels like a collective “closing of the year” where we all learn from each other one last time before the holidays.</p>
<p class="p1">This year feels particularly special for me. I spent most of the past months writing and publishing <a href="https://www.the-main-thread.com/p/applied-ai-for-enterprise-java-field-guide"><i>Applied AI for Enterprise Java Development</i></a>, a book that tries to make sense of this AI wave from a developer’s point of view. Not the hype, not the hand-waving, but the real work we do when we integrate LLMs into production systems. It’s been a year of experiments, late-night debugging, mistakes, surprises, and quite a few breakthroughs. And now, opening this Advent Calendar, I can’t help but see how far the Java ecosystem has already come.</p>
<p class="p1">Java developers didn’t sit back and wait for AI to “happen to them.”</p>
<p class="p1">We did what we always do: we tested, we validated, we measured, we built frameworks, we added structure, and we created patterns that teams can actually use in the real world. Today, <a href="http://quarkus.io/ai">Quarkus</a>, <a href="https://docs.langchain4j.dev/">LangChain4j</a>, and the broader Java ecosystem make it possible to build AI-infused systems without giving up the reliability and discipline we depend on.</p>
<p class="p1">And that’s why I’m thrilled to start this calendar with you.</p>
<p class="p1">For the next 24 days, you’ll see creative ideas, deep dives, practical guides, experiments, and some truly unexpected topics. And all for them written by people who care about this craft as much as you do.</p>
<p class="p1">So grab your favorite hot drink, take a breath, and enjoy the first door of the 2025 Java Advent Calendar.</p>
<p class="p1">There’s a lot of good stuff waiting behind the others.</p>
<p class="p1"><b>Models as Services. The Simplest Pattern Still Matters</b><b></b></p>
<p class="p1">The official LangChain4j tutorials start with the simplest possible shape: a <b>chat model that you call like a remote service</b>. That’s intentional. It reinforces the pattern Java developers already understand: the model is an external dependency with its own behavior, latency, and failure modes.</p>
<p class="p1">Here is the <i>Quarkus version</i> of the official chat example (adapted from the “Chat with LangChain4j” and “AI Services” tutorials):</p>
<pre class="p3">import dev.langchain4j.service.AiService;

@AiService
public interface Assistant {
<span class="Apple-converted-space">    </span>String chat(String message);
}</pre>
<p class="p1">Quarkus wires the model for you through configuration:</p>
<pre class="p3"># application.properties
quarkus.langchain4j.openai.api-key=${OPENAI_API_KEY}
quarkus.langchain4j.openai.chat-model.model-name=gpt-4o-mini</pre>
<p class="p1">You inject it exactly like you inject any other CDI bean:</p>
<pre class="p3">@Path("/chat")
public class ChatResource {

<span class="Apple-converted-space">    </span>@Inject
<span class="Apple-converted-space">    </span>Assistant assistant;

<span class="Apple-converted-space">    </span>@GET
<span class="Apple-converted-space">    </span>public String chat(@QueryParam("q") String q) {
<span class="Apple-converted-space">        </span>return assistant.chat(q);
<span class="Apple-converted-space">    </span>}
}</pre>
<p class="p1">This example looks trivial, but it captures a foundational rule:</p>
<p class="p1"><b>the model is not embedded; the model is a service.</b><b></b></p>
<p class="p1">And this is where Java already shines: retries, timeouts, circuit breakers, metrics, structured logs. All of those all apply cleanly to LLM calls.</p>
<p class="p1"><b>Practical Retrieval-Augmented Generation </b><b></b></p>
<p class="p1">The official LangChain4j RAG tutorial shows the basic pattern:</p>
<ul>
<li class="p1">1.Split documents into text segments</li>
<li class="p1">2.Embed those segments</li>
<li class="p1">3.Store them in an embedding store</li>
<li class="p1">4.At query time:
<ul>
<li class="p1">embed the question</li>
<li class="p1">retrieve relevant segments</li>
<li class="p1">combine into a prompt</li>
<li class="p1">send to the model</li>
</ul>
</li>
</ul>
<p class="p1">Here is the <i>Quarkus version</i> of that exact flow:</p>
<pre class="p3">import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.model.embedding.EmbeddingModel;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class RagService {

<span class="Apple-converted-space">    </span>@Inject
<span class="Apple-converted-space">    </span>EmbeddingStore&lt;TextSegment&gt; store;

<span class="Apple-converted-space">    </span>@Inject
<span class="Apple-converted-space">    </span>EmbeddingModel embeddingModel;

<span class="Apple-converted-space">    </span>public String buildPrompt(String question) {

<span class="Apple-converted-space">        </span>// 1. Embed the question
<span class="Apple-converted-space">        </span>Embedding queryEmbedding = embeddingModel.embed(question).content();

<span class="Apple-converted-space">        </span>// 2. Retrieve relevant context
<span class="Apple-converted-space">        </span>var matches = store.findRelevant(queryEmbedding, 3);

<span class="Apple-converted-space">        </span>// 3. Combine retrieved text into a simple prompt
<span class="Apple-converted-space">        </span>StringBuilder sb = new StringBuilder();

<span class="Apple-converted-space">        </span>for (var match : matches) {
<span class="Apple-converted-space">            </span>sb.append(match.embedded().text()).append("\n\n");
<span class="Apple-converted-space">        </span>}

<span class="Apple-converted-space">        </span>sb.append("Question: ").append(question);
<span class="Apple-converted-space">        </span>return sb.toString();
<span class="Apple-converted-space">    </span>}

}</pre>
<p class="p1">And the usage in a resource:</p>
<pre class="p3">@Path("/help")

public class HelpResource {

<span class="Apple-converted-space">    </span>@Inject
<span class="Apple-converted-space">    </span>RagService rag;

<span class="Apple-converted-space">    </span>@Inject
<span class="Apple-converted-space">    </span>Assistant assistant;

<span class="Apple-converted-space">    </span>@GET
<span class="Apple-converted-space">    </span>public String help(@QueryParam("q") String question) {
<span class="Apple-converted-space">        </span>String prompt = rag.buildPrompt(question);
<span class="Apple-converted-space">        </span>return assistant.chat(prompt);
<span class="Apple-converted-space">    </span>}

}</pre>
<p class="p1">This pattern is simple retrieval, simple prompt construction, no ceremony.</p>
<p class="p1">And importantly: <b>you now have a deterministic pipeline around the model. </b><b></b>That pipeline is what you test, observe, and control.</p>
<p class="p1"><b>Guardrails Safeguarding Input and Output</b><b></b></p>
<p class="p1">LangChain4j provides two clear mechanisms:</p>
<ul>
<li class="p1">@InputGuardrails</li>
<li class="p1">@OutputGuardrails</li>
</ul>
<p class="p1">Both integrate directly with Quarkus.</p>
<p class="p1">Here’s the <i>Quarkus version</i>:</p>
<p class="p1"><b>Input Guardrail</b><b></b></p>
<pre class="p3">import dev.langchain4j.guardrail.InputGuardrail;

public class NoEmptyInputGuardrail implements InputGuardrail {

<span class="Apple-converted-space">    </span>@Override
<span class="Apple-converted-space">    </span>public void validate(String input) {
<span class="Apple-converted-space">        </span>if (input == null || input.isBlank()) {
<span class="Apple-converted-space">            </span>throw new IllegalArgumentException("Input must not be empty");
<span class="Apple-converted-space">        </span>}
<span class="Apple-converted-space">    </span>}
}</pre>
<p class="p1"><b>Output Guardrail</b><b></b></p>
<pre class="p3">import dev.langchain4j.guardrail.OutputGuardrail;

public class JsonMustContainSummary implements OutputGuardrail&lt;String&gt; {

<span class="Apple-converted-space">    </span>@Override
<span class="Apple-converted-space">    </span>public void validate(String output) {
<span class="Apple-converted-space">        </span>if (!output.contains("summary")) {
<span class="Apple-converted-space">            </span>throw new IllegalStateException("Model output missing 'summary' field");
<span class="Apple-converted-space">        </span>}
<span class="Apple-converted-space">    </span>}
}</pre>
<p class="p1"><b>Wiring them into the AI service</b><b></b></p>
<pre class="p3">@AiService

public interface StructuredAssistant {

<span class="Apple-converted-space">    </span>@InputGuardrails(NoEmptyInputGuardrail.class)
<span class="Apple-converted-space">    </span>@OutputGuardrails(JsonMustContainSummary.class)
<span class="Apple-converted-space">    </span>String answer(String question);

}</pre>
<p class="p1"><b>And Quarkus adds configuration-driven retries:</b><b></b></p>
<pre class="p5">quarkus.langchain4j.guardrails.max-retries=2</pre>
<p class="p1">Guardrails feel like an entirely new concept for many Java developers, but the structure is familiar: <b>It’s validation, just on the other side of the API boundary.</b><b></b></p>
<p class="p1"><b>Testing &amp; Evaluation </b><b></b></p>
<p class="p1">If  we think about Testing in general, and merge this with requirements coming in for large language models, we need to think about:</p>
<ul>
<li class="p1">testing deterministic components</li>
<li class="p1">testing guardrails</li>
<li class="p1">testing model interaction in a black-box fashion</li>
<li class="p1">using curated prompt sets</li>
<li class="p1">evaluating output structure, not exact wording</li>
</ul>
<p>Here is a <i>Quarkus version</i> :</p>
<p class="p1"><b>Testing Guardrails</b><b></b></p>
<pre class="p3">@QuarkusTest

public class GuardrailTest {

<span class="Apple-converted-space">    </span>@Inject
<span class="Apple-converted-space">    </span>StructuredAssistant assistant;

<span class="Apple-converted-space">    </span>@Test
<span class="Apple-converted-space">    </span>void emptyInputShouldBeRejected() {
<span class="Apple-converted-space">        </span>assertThrows(IllegalArgumentException.class, () -&gt; {
<span class="Apple-converted-space">            </span>assistant.answer(" ");
<span class="Apple-converted-space">        </span>});
<span class="Apple-converted-space">    </span>}

}</pre>
<p class="p1"><b>Testing RAG logic (deterministic)</b><b></b></p>
<pre class="p3">@QuarkusTest

public class RagTest {

<span class="Apple-converted-space">    </span>@Inject
<span class="Apple-converted-space">    </span>RagService rag;

<span class="Apple-converted-space">    </span>@Inject
<span class="Apple-converted-space">    </span>EmbeddingStore&lt;TextSegment&gt; store;

<span class="Apple-converted-space">    </span>@Test
<span class="Apple-converted-space">    </span>void retrievalShouldReturnRelevantText() {
<span class="Apple-converted-space">        </span>store.add(TextSegment.from("Java 21 is the current LTS release."),
<span class="Apple-converted-space">                  </span>EmbeddingModel.miniLm().embed("Java").content());

<span class="Apple-converted-space">        </span>String prompt = rag.buildPrompt("What is the current Java LTS?");
<span class="Apple-converted-space">        </span>assertTrue(prompt.contains("Java 21"));
<span class="Apple-converted-space">    </span>}

}</pre>
<p class="p1"><b>Opaque-box testing of the assistant</b><b></b></p>
<pre class="p3">@QuarkusTest

public class AssistantIT {

<span class="Apple-converted-space">    </span>@Inject
<span class="Apple-converted-space">    </span>Assistant assistant;

<span class="Apple-converted-space">    </span>@Test
<span class="Apple-converted-space">    </span>void modelShouldProduceNonEmptyAnswer() {

<span class="Apple-converted-space">        </span>String response = assistant.chat("Hello!");
<span class="Apple-converted-space">        </span>assertFalse(response.isBlank());
<span class="Apple-converted-space">    </span>}

}</pre>
<p class="p1"><b>Don’t test phrasing; test structure and expectations!</b></p>
<p class="p1">This keeps tests stable while still verifying quality and behavior.</p>
<p class="p1"><b>A Festive Opening for the 24 Days Ahead</b><b></b></p>
<p class="p1">That brings us to why this article exists: to open another year of the Java Advent Calendar.</p>
<p class="p1">December has a special rhythm in the Java community. The year is winding down, code freezes are happening, and teams start reflecting on what actually mattered. And into that atmosphere comes a wave of articles. 24 voices, 24 perspectives, 24 stories from across our ecosystem.</p>
<ul>
<li class="p1">Some will dive deep into AI.</li>
<li class="p1">Some will explore the core of the JVM.</li>
<li class="p1">Some will share practical lessons from the year’s real-world battles.</li>
<li class="p1">Some will remind us why Java continues to thrive, evolve, and surprise us.</li>
</ul>
<p class="p1">This opening post is just the prologue.</p>
<p class="p1">Over the next three weeks, you’ll see what people across the community are experimenting with, breaking, fixing, and learning. You’ll see new tools, new techniques, old wisdom rediscovered, and perhaps a few things you’ll want to try during the quieter days between the holidays.</p>
<p class="p1">Whatever this season means to you, whether it’s festive, reflective, or simply a much-needed breather, I hope these articles offer inspiration and maybe a spark for your next project. Java has always been about community, and the Advent Calendar remains one of the warmest reminders of that.</p>
<p class="p1">So let’s open the first door together.</p>
<p class="p1">Twenty-three more to go.</p>
<p class="p1">Let’s enjoy the season, and write some good Java along the way.</p>
<p><img loading="lazy" decoding="async" src="https://analytics.e-mehlbox.eu/piwik.php?idsite=2&amp;rec=1&amp;url=https%3A%2F%2Fwww.javaadvent.com%2F2025%2F12%2Flighting-the-way-java-ai-and-a-season-of-new-ideas.html&amp;action_name=Lighting%20the%20Way%3A%20Java%2C%20AI%2C%20and%20a%20Season%20of%20New%20Ideas&amp;urlref=http%3A%2F%2Ffeeds.feedburner.com%2FJavaAdventCalendar" style="border:0;width:0;height:0" width="0" height="0" alt="" /></p>
<p>The post <a href="https://www.javaadvent.com/2025/12/lighting-the-way-java-ai-and-a-season-of-new-ideas.html">Lighting the Way: Java, AI, and a Season of New Ideas</a> appeared first on <a href="https://www.javaadvent.com">JVM Advent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.javaadvent.com/2025/12/lighting-the-way-java-ai-and-a-season-of-new-ideas.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5922</post-id>	</item>
	</channel>
</rss>
