<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://feeds.feedblitz.com/feedblitz_rss.xslt"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:webfeeds="http://webfeeds.org/rss/1.0"  xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
<channel>
	<title>Baeldung</title>
	<atom:link href="https://www.baeldung.com/feed" rel="self" type="application/rss+xml" />
	<link>https://www.baeldung.com</link>
	<description>Java, Spring and Web Development tutorials</description>
	<lastBuildDate>Sun, 27 Sep 2026 13:04:27 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
<meta xmlns="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-665</feedburner:origLink>
		<title>Java Weekly, Issue 665</title>
		<link>https://feeds.feedblitz.com/~/970561523/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/970561523/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sun, 27 Sep 2026 13:04:27 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=205082</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" fetchpriority="high" /><p>Performance in Java is always getting better.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/970561523/0/baeldung">Java Weekly, Issue 665</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970561523/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-665#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-665/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="(max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/21/jit-for-java-performance/">&gt;&gt; Just-In-Time Compilation for Java Performance: Recent and Ongoing Improvements</a></strong> [<span style="color: #993300;">inside.java</span>]</p>
<p>The JVM&#8217;s JIT compilers keep adapting &#8211; a quick survey of recent performance improvements and the work underway around Valhalla, Leyden, and Panama.</p>
<p>A very useful look at where Java&#8217;s runtime speed comes from and where its compilers are headed.</p>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/09/21/spring-ai-typesafe-structured-judgment/">&gt;&gt; Spring AI and TypeSafe Jev: Fast, Cheap, Structured Decisions</a></strong> [<span style="color: #993300;">spring.io</span>]</p>
<p>Sometimes an AI application needs a decision, not more text. Jev is for that <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (when it opens up agian).</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/23/quality-heads-up/" target="_blank" rel="noopener"><strong>Quality Outreach Heads-up &#8211; JDK 28: Rich JavaDoc Notes</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/20/jep401-target-jdk28/" target="_blank" rel="noopener"><strong>JEP targeted to JDK 28: 401: Value Objects (Preview)</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/jekyll-to-roq/" target="_blank" rel="noopener"><strong>The quarkus.io site is now built with Quarkus Roq</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/mcp-stateless/" target="_blank" rel="noopener"><strong>MCP goes stateless, and Quarkus already implements it</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/09/21/releasing-spring-for-modern-challenges/" target="_blank" rel="noopener"><strong>Releasing Spring for Modern Challenges</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://netflixtechblog.com/leave-the-class-path-in-the-rearview-mirror-67a85b15b6be" target="_blank" rel="noopener"><strong>Leave the Class Path in the Rearview Mirror</strong></a> [<span style="color: #800000;">netflixtechblog.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://marcphilipp.de/blog/2026/09/19/mirroring-the-junit-repository-to-codeberg/" target="_blank" rel="noopener"><strong>Mirroring the JUnit repository to Codeberg</strong></a> [<span style="color: #800000;">marcphilipp.de</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/17/podcast-070/" target="_blank" rel="noopener"><strong>Episode 70 “AOT Caching &#8211; Netflix&#8217; Practice vs OpenJDK&#8217;s Theory” [I/O]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-insights-260-cli-tui-java/" target="_blank" rel="noopener"><strong>Quarkus Insights #260: CLI and TUI Applications — A Terminal Renaissance in Java</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/09/21/spring-office-hours-podcast-S5E24" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E24 &#8211; Jev, Java and Spring</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/09/17/a-bootiful-podcast-martin-lippert/" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Spring Tools lead Martin Lippert</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/boxlang-1-17-0-released-module-inception-cli-checker-jar-loading-and-much-more/" target="_blank" rel="noopener"><strong>BoxLang 1.17.0 Released: Module Inception, CLI Checker, Jar Loading and much more!</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://in.relation.to/2026/09/23/orm-80-beta2/" target="_blank" rel="noopener"><strong>Hibernate 8.0.0.Beta2</strong></a> [<span style="color: #800000;">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/22/helidon27-release/" target="_blank" rel="noopener"><strong>Helidon 27 Released</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-39-5-released/" target="_blank" rel="noopener"><strong>Quarkus 3.39.5</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/quarkusio/quarkus/releases/tag/3.33.4" target="_blank" rel="noopener"><strong>3.33.4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-33-3-3-released/" target="_blank" rel="noopener"><strong>3.33.3.3</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-27-5-3-released/" target="_blank" rel="noopener"><strong>3.27.5.3</strong></a> [<span style="color: #800000;">quarkus.io / github.com/quarkusio</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.azul.com/blog/whats-new-in-the-september-2026-azul-payara-release/" target="_blank" rel="noopener"><strong>What&#8217;s New in the September 2026 Azul Payara Release?</strong></a> [<span style="color: #800000;">azul.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/ktor/2026/09/18/ktor-3-6-0-is-now-available/" target="_blank" rel="noopener"><strong>Ktor 3.6.0 Is Now Available!</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/graalvm/graalvm-ce-builds/releases/tag/graal-25.4.4.1.1" target="_blank" rel="noopener"><strong>GraalVM Community 25 Innovation 4 (graal 25.4.4.1.1, jdk 25.0.4.1.1)</strong></a> [<span style="color: #800000;">github.com/graalvm</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/jhipster/generator-jhipster/releases/tag/v9.4.0" target="_blank" rel="noopener"><strong>JHipster 9.4.0</strong></a> [<span style="color: #800000;">github.com/jhipster</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/5.2.0" target="_blank" rel="noopener"><strong>Vert.x 5.2.0</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v8.19.22" target="_blank" rel="noopener"><strong>Elasticsearch 8.19.22</strong></a> [<span style="color: #800000;">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.9" target="_blank" rel="noopener"><strong>Zuul 4.1.9</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.8" target="_blank" rel="noopener"><strong>4.1.8</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.5" target="_blank" rel="noopener"><strong>4.1.5</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.2.4" target="_blank" rel="noopener"><strong>Grails 7.2.4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.1.7" target="_blank" rel="noopener"><strong>7.1.7</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/grails-core/releases/tag/v7.0.17" target="_blank" rel="noopener"><strong>7.0.17</strong></a> [<span style="color: #800000;">github.com/apache</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.5" target="_blank" rel="noopener"><strong>Micronaut Core 5.2.5</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.4" target="_blank" rel="noopener"><strong>5.2.4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.29" target="_blank" rel="noopener"><strong>4.10.29</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.3" target="_blank" rel="noopener"><strong>5.2.3</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/diagrams-as-code-mermaid-support-on-foojay/">&gt;&gt; Diagrams as Code: Mermaid Support on the new Foojay</a></strong> [<span style="color: #993300;">foojay.io</span>]</p>
<p>Foojay now renders Mermaid diagrams directly from fenced code blocks. Frank Delporte walks through sequence, class, state, ER, and Gantt examples, then covers theme-aware rendering, syntax checks, and accessibility. A practical way to keep diagrams for Java APIs and workflows in the same reviewable source as the article.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.frankel.ch/ai-assisted-genealogy-follow-up/" target="_blank" rel="noopener"><strong>AI-assisted genealogy, a follow-up</strong></a> [<span style="color: #800000;">frankel.ch</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/asciidoc-support-on-foojay/" target="_blank" rel="noopener"><strong>AsciiDoc Support on the new Foojay</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://melix.github.io/blog//2026/09/22-amateur-helioseismology.html" target="_blank" rel="noopener"><strong>Measuring the Sun’s oscillations with an amateur spectroheliograph</strong></a> [<span style="color: #800000;">melix.github.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/2026-dont-like-llms.html" target="_blank" rel="noopener"><strong>I don&#8217;t like LLMs</strong></a> [<span style="color: #800000;">martinfowler.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.scottlogic.com/2026/09/21/building-the-business-case-for-agentic-software-development.html" target="_blank" rel="noopener"><strong>Building the business case for agentic software development</strong></a> [<span style="color: #800000;">scottlogic.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.satisfice.com/blog/archives/488069" target="_blank" rel="noopener"><strong>Responsible Quality Engineering</strong></a> [<span style="color: #800000;">satisfice.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wildfly.org/news/2026/09/23/Introducing-WildFly-Themes-Community-Driven-Planning-for-WildFly/" target="_blank" rel="noopener"><strong>Introducing WildFly Themes — Community-Driven Planning for WildFly</strong></a> [<span style="color: #800000;">wildfly.org</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://ai.baeldung.com/build-your-agentic-ai-harness-course/">&gt;&gt; Build your Agentic Harness is Out</a></strong></p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-665">Java Weekly, Issue 665</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/970561523/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970561523/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970561523/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-665#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-665/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/970561523/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-hibernate-find-annotation</feedburner:origLink>
		<title>The @Find Annotation in Hibernate</title>
		<link>https://feeds.feedblitz.com/~/970277321/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/970277321/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Graham Cox]]></dc:creator>
		<pubDate>Sat, 26 Sep 2026 04:20:54 +0000</pubDate>
				<category><![CDATA[JPA]]></category>
		<category><![CDATA[Hibernate]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-hibernate-find-annotation</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" /><p>Learn about how to use the @Find annotation in Hibernate to generate a repository implementation using method query naming conventions.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/970277321/0/baeldung">The @Find Annotation in Hibernate</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970277321/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-06-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-hibernate-find-annotation#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-hibernate-find-annotation/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction"><strong>1. Introduction</strong></h2>
<div class="bd-anchor" id="introduction"></div>
<p>In this article, we’re going to look at how to use the <em>@Find </em>annotation in Hibernate. We’ll see what it is, what it&#8217;s used for, and how to use it.</p>
<h2 id="bd-setting-up-hibernate" data-id="setting-up-hibernate"><strong>2. Setting up Hibernate</strong></h2>
<div class="bd-anchor" id="setting-up-hibernate"></div>
<p>Before we can use the <em>@Find </em>annotation, we need to set up Hibernate and our database.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies"><strong>2.1. Dependencies</strong></h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p><strong>To use <em>@Find</em> in <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core">Hibernate</a>, we need to use version 6.3 or newer. The newest release at the time of writing is <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core/7.4.6.Final">7.4.6.Final</a>:</strong></p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.hibernate.orm&lt;/groupId&gt;
    &lt;artifactId&gt;hibernate-core&lt;/artifactId&gt;
    &lt;version&gt;7.4.6.Final&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p><strong>We also need the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/jakarta.data/jakarta.data-api">Jakarta Data Core API</a>. The newest release at the time of writing is <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/jakarta.data/jakarta.data-api/1.0.2">1.0.2</a>:</strong></p>
<pre><code class="language-">&lt;dependency&gt;
    &lt;groupId&gt;jakarta.data&lt;/groupId&gt;
    &lt;artifactId&gt;jakarta.data-api&lt;/artifactId&gt;
    &lt;version&gt;1.0.2&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>This gives us everything we need to write our repository code.</p>
<h3 id="bd-2-hibernate-processor" data-id="2-hibernate-processor"><strong>2.2. Hibernate Processor</strong></h3>
<div class="bd-anchor" id="2-hibernate-processor"></div>
<p><strong>In addition to the dependencies for writing our repositories, we need to configure the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://hibernate.org/orm/processor/">Hibernate Processor</a>.</strong> This will generate concrete classes for our repository interfaces at compile time.</p>
<p>When using Hibernate 7, this is configured by adding an annotation processor to the <em>maven-compiler-plugin</em>:</p>
<pre><code class="language-xml">&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;annotationProcessorPaths&gt;
                    &lt;path&gt;
                        &lt;groupId&gt;org.hibernate.orm&lt;/groupId&gt;
                        &lt;artifactId&gt;hibernate-processor&lt;/artifactId&gt;
                        &lt;version&gt;7.4.6.Final&lt;/version&gt;
                    &lt;/path&gt;
                &lt;/annotationProcessorPaths&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;
</code></pre>
<p>This needs to match the version of the <em>hibernate-core </em>dependency we specified earlier.</p>
<p>Once the plugin is added, the Maven build will automatically generate additional classes for us, including <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hibernate-criteria-queries-metamodel">Hibernate Metamodel</a> classes for our entities, and concrete classes that implement our repository interfaces, as we&#8217;ll see later.</p>
<h3 id="bd-3-database" data-id="3-database"><strong>2.3. Database</strong></h3>
<div class="bd-anchor" id="3-database"></div>
<p><strong>For this article, we need to set up a database.</strong> Our tables will look as follows:</p>
<pre><code class="language-sql">CREATE TABLE authors (
  author_id   BIGINT PRIMARY KEY,
  name        TEXT NOT NULL
);
CREATE TABLE books (
  book_id     BIGINT PRIMARY KEY,
  title       TEXT NOT NULL,
  author_id   BIGINT NOT NULL,
  FOREIGN KEY (author_id) REFERENCES authors(author_id)
);</code></pre>
<p>This gives us two tables &#8211; <em>books </em>and <em>authors </em>&#8211; such that there&#8217;s a foreign key between them.</p>
<p>We then need some data in our database:</p>
<pre><code class="language-sql">INSERT INTO authors (author_id, name) VALUES
  (1, 'George Orwell'),
  (2, 'Haruki Murakami'),
  (3, 'Agatha Christie'),
  (4, 'Ursula K. Le Guin');
INSERT INTO books (book_id, title, author_id) VALUES
  (101, '1984', 1),
  (102, 'Animal Farm', 1),
  (103, 'Norwegian Wood', 2),
  (104, 'Kafka on the Shore', 2),
  (105, 'Murder on the Orient Express', 3),
  (106, 'And Then There Were None', 3),
  (107, 'The Left Hand of Darkness', 4);</code></pre>
<h3 id="bd-4-jpa-entity" data-id="4-jpa-entity"><strong>2.4. JPA Entity</strong></h3>
<div class="bd-anchor" id="4-jpa-entity"></div>
<p><strong>Finally, since we’ll be working with Hibernate, we also need Hibernate entities to represent our data.</strong></p>
<p>First, our <em>Author </em>entity:</p>
<pre><code class="language-java">@Entity
@Table(name = "authors")
public class Author {
    @Id
    @Column(name = "author_id")
    private Long authorId;
    private String name;
    public Long getAuthorId() {
        return authorId;
    }
    public String getName() {
        return name;
    }
}</code></pre>
<p>And then the<em> Book </em>entity:</p>
<pre><code class="language-java">@Entity
@Table(name = "books")
public class Book {
    @Id
    @Column(name = "book_id")
    private Long bookId;
    private String title;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id", nullable = false)
    private Author author;
    public Long getBookId() {
        return bookId;
    }
    public String getTitle() {
        return title;
    }
    public Author getAuthor() {
        return author;
    }
}</code></pre>
<p>This references our <em>Author </em>entity so we can follow those links in our queries.</p>
<h2 id="bd-repository-interfaces" data-id="repository-interfaces"><strong>3. Repository Interfaces</strong></h2>
<div class="bd-anchor" id="repository-interfaces"></div>
<p>Once everything is set up, we&#8217;re ready to start writing our repositories. <strong>These are written as Java interfaces and annotated with the <em>@jakarta.data.repository.Repository </em>annotation:</strong></p>
<pre><code class="language-java">@Repository
public interface BookRepository {
}
</code></pre>
<p>On its own, this is enough for the Hibernate Processor to discover and generate a concrete implementation &#8211; in this case, called <em>BookRepository_.</em></p>
<pre><code class="language-java">// Generated code
public class BookRepository_ implements BookRepository {
    protected StatelessSession session;
    public BookRepository_(StatelessSession session) {
        this.session = session;
    }
    public StatelessSession session() {
        return session;
    }
}</code></pre>
<p>This generates an implementation constructed with a <em>StatelessSession</em> instance. We can then create instances of this as needed when we want to query our data.</p>
<h3 id="bd-1-injecting-an-entitymanager" data-id="1-injecting-an-entitymanager"><strong>3.1. Injecting an EntityManager</strong></h3>
<div class="bd-anchor" id="1-injecting-an-entitymanager"></div>
<p>We often don&#8217;t want to create repository instances on demand. Instead, we&#8217;d like to create them once at the start of our application and then pass them around. For example, we might want to create them in our Spring context.</p>
<p><strong>Fortunately, Hibernate supports this too. All we need to do is add a special method to our interface that returns an <em>EntityManager</em>:</strong></p>
<pre><code class="language-java">@Repository
public interface AuthorRepository {
    EntityManager entityManager();
}
</code></pre>
<p>If we do this, Hibernate will construct an alternative form of our repository that is constructed using our <em>EntityManager </em>instead:</p>
<pre><code class="language-java">// Generated code
public class AuthorRepository_ implements AuthorRepository {
    protected EntityManager entityManager;
    public AuthorRepository_(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
    @Override
    public EntityManager entityManager() {
        return entityManager;
    }
}</code></pre>
<p>We can then safely construct this once and reuse it as much as needed.</p>
<h2 id="bd-finding-by-fields" data-id="finding-by-fields"><strong>4. Finding By Fields</strong></h2>
<div class="bd-anchor" id="finding-by-fields"></div>
<p>Once we&#8217;ve got our repository, we need to be able to do something with it. <strong>We can add methods to find entities using the <em>@Find </em> annotation, with special conventions for parameter names and return types.</strong></p>
<pre><code class="language-java">@Repository
public interface BookRepository {
    @Find
    List&lt;Book&gt; getAllBooks();
}
</code></pre>
<p>Here we have a method called <em>getAllBooks</em>. The method name is unimportant. However, the fact that it returns a <em>List&lt;Book&gt; </em>means that the generated code will understand that we&#8217;re working with <em>Book </em>entities, and that we&#8217;re returning all of the ones that match our query.</p>
<p>This will equate to running the following query:</p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0</code></pre>
<p>We can go a step further with this by actually filtering our results:</p>
<pre><code class="language-java">@Find
Book getBookWithTitle(String title);
</code></pre>
<p><strong>Again, the method title can be anything. However, the parameter name must exactly match a field in our <em>Book </em>entity.</strong> As such, Hibernate generates code that will filter by that field:</p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0 
WHERE b1_0.title = ?</code></pre>
<p>This time, we return only a single <em>Book </em>entity. As such, Hibernate knows to return a single matching entity. If there isn&#8217;t one, we&#8217;ll get an <em>EmptyResultException</em> thrown instead. Alternatively, if there are multiple records that match, we&#8217;ll get a <em>NonUniqueResultException </em>thrown.</p>
<h2 id="bd-optional-results" data-id="optional-results"><strong>5. Optional Results</strong></h2>
<div class="bd-anchor" id="optional-results"></div>
<p>Sometimes we want to search for a single record that may not exist. We can handle the <em>EmptyResultException</em> that gets thrown, but this isn&#8217;t ideal.</p>
<p>Hibernate supports a few ways to handle this automatically. <strong>The most obvious is to update our method to return <em>Optional&lt;Book&gt;</em> instead:</strong></p>
<pre><code class="language-java">@Find
Optional&lt;Book&gt; getOptionalBookWithTitle(String title);
</code></pre>
<p>In this case, an unknown record returns <em>Optional.empty()</em> instead.</p>
<p><strong>Alternatively, if we don&#8217;t want to deal with this, we can annotate our method with <em>jakarta.annotation.Nullable.</em></strong></p>
<pre><code class="language-java">@Find
@Nullable
Book getNullableBookWithTitle(String title);
</code></pre>
<p>This tells the generated code to return <em>null </em>instead of throwing an exception.</p>
<p>In both cases, the database queries are identical. The only difference is what the generated code does with the results after executing the query. Both cases also still throw a <em>NonUniqueResultException </em>if the query returns more than one result.</p>
<h2 id="bd-finding-by-nested-fields" data-id="finding-by-nested-fields"><strong>6. Finding By Nested Fields</strong></h2>
<div class="bd-anchor" id="finding-by-nested-fields"></div>
<p><strong>So far we&#8217;ve seen how to find records based on data directly in that record. However, often we want to search based on related data too.</strong> Hibernate can do this based on how we name our method parameters. If we use a &#8220;$&#8221; symbol, this is interpreted as linking fields through related entities:</p>
<pre><code class="language-java">@Find
List&lt;Book&gt; getAllBooksByAuthorName(String author$name);</code></pre>
<p>Here, our query will join from <em>Book</em> through the <em>Book.author </em>field, and then query based on <em>Author.name:</em></p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0 
  JOIN authors a1_0 ON a1_0.author_id = b1_0.author_id 
WHERE a1_0.name = ?</code></pre>
<p>We can use this technique for as many steps as we want. However, we can only follow relationships that use<em> @ManyToOne </em>or <em>@OneToOne</em>. Relationships using <em>@OneToMany </em>or <em>@ManyToMany </em>won&#8217;t work since the generated query would need to link to multiple records.</p>
<h2 id="bd-multiple-results" data-id="multiple-results"><strong>7. Multiple Results</strong></h2>
<div class="bd-anchor" id="multiple-results"></div>
<p>So far we&#8217;ve seen how to return either a single result or all matching results. However, sometimes we need more control. Hibernate can manage all of this for us.</p>
<h3 id="bd-1-sorting" data-id="1-sorting"><strong>7.1. Sorting</strong></h3>
<div class="bd-anchor" id="1-sorting"></div>
<p>By default, queries that return lists of records return them in the order the database returns them. This can vary based on many factors, so we can&#8217;t rely on it for consistent ordering.</p>
<p><strong>If we add the <em>@OrderBy</em> annotation to our method, Hibernate will sort our results by the specified fields:</strong></p>
<pre><code class="language-java">@Find
@OrderBy("title")
List&lt;Book&gt; getAllBooks();
</code></pre>
<p>This equates to executing the following query:</p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0 
ORDER BY b1_0.title</code></pre>
<p>With the addition of the <em>ORDER BY</em> clause for consistent sorting.</p>
<p>If necessary, we can repeat the annotation to allow sorting by multiple fields:</p>
<pre><code class="language-java">@Find
@OrderBy("title")
@OrderBy("author$name")
List&lt;Book&gt; getAllBooks();
</code></pre>
<p>Note that we can also sort by fields on joined entities using exactly the same syntax as we saw earlier. Doing this generates a query like this:</p>
<pre><code class="language-sql">SELECT b1_0.book_id, b1_0.author_id, b1_0.title 
FROM books b1_0 
  JOIN authors a1_0 ON a1_0.author_id = b1_0.author_id 
ORDER BY b1_0.title desc, a1_0.name</code></pre>
<p>We can also specify the sort direction using the <em>descending </em>parameter. This defaults to <em>false</em>, but we can change this to indicate descending sorts:</p>
<pre><code class="language-java">@Find
@OrderBy(value = "title", descending = true)
List&lt;Book&gt; getAllBooks();
</code></pre>
<p>Unsurprisingly, we can mix all of this as needed, allowing for sorts on multiple fields in different directions.</p>
<p><strong>We can also allow dynamic sorting by accepting a parameter of type <em>Order&lt;Book&gt;</em>.</strong></p>
<pre><code class="language-java">@Find
List&lt;Book&gt; getAllBooks(Order&lt;Book&gt; sort);
</code></pre>
<p>This is generic over the entity that we&#8217;re working on. If we use the wrong generic type, Hibernate will throw an error at compile time. We can now call this and define the sorts at runtime:</p>
<pre><code class="language-java">List&lt;Book&gt; books = repository.getAllBooks(Order.by(
  Sort.asc("title"),
  Sort.desc("author.name")
));
</code></pre>
<p>Note that here we need to specify nested fields using dotted syntax instead of the &#8220;$&#8221; symbol, but otherwise it all works the same.</p>
<h3 id="bd-2-pagination" data-id="2-pagination"><strong>7.2. Pagination</strong></h3>
<div class="bd-anchor" id="2-pagination"></div>
<p><strong>In addition to sorting our results, we can also request specific pages of results. We can do this by adding a parameter of type <em>PageRequest</em> to our method:</strong></p>
<pre><code class="language-java">@Find
@OrderBy("title")
List&lt;Book&gt; getBooksPage(PageRequest pageRequest);
</code></pre>
<p>We don&#8217;t technically need to enforce ordering on these queries, but it’s usually a good idea so results stay consistent across pages.</p>
<p>We can then call this specifying the desired page and page size:</p>
<pre><code class="language-java">List&lt;Book&gt; books = repository.getBooksPage(PageRequest.ofPage(1, 3, true));
</code></pre>
<p>Note that the page is 1-indexed, so this will return the first page of 3 records.</p>
<p><strong>If we want to know more about the details of the page, we need to change our return type to <em>Page&lt;Book&gt;</em> instead of <em>List&lt;Book&gt;</em>:</strong></p>
<pre><code class="language-java">@Find
@OrderBy("title")
Page&lt;Book&gt; getBooksPage(PageRequest pageRequest);</code></pre>
<p>We now have access to not only the page contents, but also page details like the total number of elements and whether there are next and previous pages:</p>
<pre><code class="language-java">long total = books.totalElements();
if (books.hasNext()) {
    // Do something
}
if (books.hasPrevious()) {
    // Do something
}</code></pre>
<p>Note that the third parameter to our <em>PageRequest.ofPage()</em> call tells Hibernate whether we want a total count of the elements across all pages. If we pass <em>true</em>, Hibernate executes an additional query to count matching elements. If we pass in <em>false, </em>Hibernate doesn&#8217;t execute this additional query, and the total number of elements won&#8217;t be available.</p>
<h2 id="bd-summary" data-id="summary"><strong>8. Summary</strong></h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we’ve had a very brief look at using the <em>@Find </em>annotation in Hibernate. We’ve seen what it is, how it works, and how we can use it. Next time you need to generate repositories like this, why not give it a go?</p>
<p>As always, all the code from this article is available over on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate-queries-3">GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-hibernate-find-annotation">The @Find Annotation in Hibernate</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/970277321/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970277321/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fData-Featured-Image-06-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970277321/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-hibernate-find-annotation#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-hibernate-find-annotation/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/970277321/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Data-Featured-Image-06-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-jdk-26-performance-improvements</feedburner:origLink>
		<title>Performance Improvements in JDK 26</title>
		<link>https://feeds.feedblitz.com/~/970060151/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/970060151/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Bhaskar Ghosh]]></dc:creator>
		<pubDate>Fri, 25 Sep 2026 05:21:31 +0000</pubDate>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[>= Java 26]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=205054</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Explore the most important performance improvements in JDK 26.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/970060151/0/baeldung">Performance Improvements in JDK 26</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970060151/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970060151/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970060151/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970060151/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970060151/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-jdk-26-performance-improvements#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-jdk-26-performance-improvements/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2><div class="bd-anchor" id="introduction"></div>
<p>JDK 26 contains multiple resolved issues and over 1,000 enhancements. A major part of this work focuses on performance across the JDK libraries, garbage collectors, compiler, and runtime.</p>
<p>Some improvements require an API change or a JVM option. Others benefit existing applications as soon as they move to JDK 26. Together, these changes target faster startup, higher throughput, and better scalability.</p>
<p>In this tutorial, we&#8217;ll explore the most important performance improvements in JDK 26. We&#8217;ll also see how they affect application code and deployment choices.</p>
<h2 id="bd-jdk-library-improvements" data-id="jdk-library-improvements">2. JDK Library Improvements</h2><div class="bd-anchor" id="jdk-library-improvements"></div>
<p>We&#8217;ll start with the changes that very closely relate to application library code.</p>
<h3 id="bd-1-lazy-constants" data-id="1-lazy-constants">2.1. Lazy Constants</h3><div class="bd-anchor" id="1-lazy-constants"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/526">JEP 526</a> introduces <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-lazy-constants">Lazy Constants</a> as a second preview. The <em>LazyConstant</em> API holds an immutable value that is computed only when it&#8217;s first requested.</p>
<p>Before JDK 26, delaying the creation of an expensive object often required a nullable field, a null check, and synchronization. For example, we could initialize a service only when it was first requested:</p>
<pre><code class="language-java">public final class Application {
    private static Service service;
    static synchronized Service service() {
        if (service == null) {
	    service = new Service();
	}
	return service;
    }
	private static final class Service {}
}</code></pre>
<p>&nbsp;</p>
<p>We can now express the same intent directly:</p>
<pre dir="ltr"><code>public final class Application {
    private static final LazyConstant&lt;Service&gt; SERVICE = LazyConstant.of(Service::new);
    static Service service() {
        return SERVICE.get();
    }
    private static final class Service {}
}</code></pre>
<p>The first call to <em>get()</em> creates the service. Later calls return the same value. Initialization occurs at most once and remains safe when several threads race to access it. <strong>When a lazy constant is stored in a <em>final</em> field, the <em>JVM</em> can optimize repeated access in a way similar to a final constant.</strong> This gives us deferred work without permanently paying the usual synchronization cost.</p>
<p>Since this is a preview API, we need preview features at compile time and runtime:</p>
<pre dir="ltr"><code>javac --enable-preview --release 26 Application.java
java --enable-preview Application</code>
<br>
<br></pre>
<h3 id="bd-2-strings-records-and-cryptography" data-id="2-strings-records-and-cryptography">2.2. Strings, Records, and Cryptography</h3><div class="bd-anchor" id="2-strings-records-and-cryptography"></div>
<p>JDK 26 <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://bugs.openjdk.org/browse/JDK-8362893">reduces intermediate allocation and copying</a> inside <em>MemorySegment.getString()</em>. This matters when an application frequently converts native or off-heap data into Java strings. Early benchmarks showed lower latency for all tested sizes, with the largest improvement for short strings.</p>
<p>Generated <em>hashCode()</em> methods for records now receive <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://bugs.openjdk.org/browse/JDK-8366424">better type profiling</a>. <strong>Record-heavy maps, sets, grouping operations, and deduplication can therefore gain throughput without source changes. </strong>The release also optimizes <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-aes-encryption-decryption">AES</a>, ML-DSA, and elliptic-curve P-256 operations. These changes improve key setup, low-level arithmetic, and hardware-specific execution on supported processors.</p>
<p>There are smaller improvements as well. <em>GZIPInputStream</em> reads single compressed streams more efficiently, while <em>Method.equals()</em> it immediately succeeds when both references point to the same instance. The latter can help dynamic proxies, where method comparisons occur frequently during dispatch.</p>
<h2 id="bd-garbage-collection-and-startup-improvements" data-id="garbage-collection-and-startup-improvements">3. Garbage Collection and Startup Improvements</h2><div class="bd-anchor" id="garbage-collection-and-startup-improvements"></div>
<p>Garbage collection runs automatically, but it isn&#8217;t free. The JVM performs extra work whenever an application changes object references. It also prepares heap structures, classes, and commonly used objects during startup.</p>
<p>JDK 26 reduces both kinds of overhead. It makes G1 reference tracking less expensive, extends ahead-of-time caching to every garbage collector, and avoids preparing an unnecessarily large initial heap.</p>
<h3 id="bd-1-lower-g1-synchronization-overhead" data-id="1-lower-g1-synchronization-overhead">3.1. Lower G1 Synchronization Overhead</h3><div class="bd-anchor" id="1-lower-g1-synchronization-overhead"></div>
<p><strong>G1 divides the heap into many regions. During a collection, it can reclaim selected regions instead of processing the entire heap.</strong></p>
<p>Objects in different regions can still reference one another. For example, an <em><code dir="ltr">Order</code></em> object in one region may reference a <em><code dir="ltr">Customer</code></em> object in another. G1 must remember this connection so that it doesn&#8217;t reclaim the customer while the order is still reachable.</p>
<p><strong>G1 tracks these changes using a card table. The card table represents the heap as a collection of small areas called cards. When application code changes an object reference, a write barrier marks the corresponding card as dirty.</strong></p>
<p>This write barrier runs for every relevant reference update:</p>
<pre dir="ltr"><code>order.setCustomer(customer);</code></pre>
<p>Background refinement threads inspect dirty cards and record the references that cross region boundaries. Previously, application threads and refinement threads worked with the same card table. <strong>They needed additional synchronization to avoid interfering with one another. This synchronization made each write barrier more expensive</strong>. The cost became noticeable in applications that frequently create objects or update fields, such as caches, in-memory data stores, and request-processing systems.</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/522">JEP 522</a> introduces a second card table. Application threads write to the active table, while refinement threads process the other table. G1 swaps the tables when the active table needs refinement.</p>
<p><strong>The two groups of threads can now perform most of their work independently.</strong> This reduces synchronization and makes object-reference updates cheaper. Published benchmarks showed throughput improvements of 5–15% in reference-heavy workloads. Workloads with fewer reference updates gained up to approximately 5%.</p>
<p>The second table requires additional native memory equal to roughly 0.2% of the heap. This is about 2 MB for every 1 GB of heap space. <strong>Applications already using G1 receive the improvement without code or configuration changes.</strong> However, the result depends on how frequently an application updates references. We should therefore verify the gain with a representative workload.</p>
<h3 id="bd-2-aot-object-caching-with-any-gc" data-id="2-aot-object-caching-with-any-gc">3.2. AOT Object Caching with Any GC</h3><div class="bd-anchor" id="2-aot-object-caching-with-any-gc"></div>
<p>A Java application performs several operations before it can handle useful work. The JVM loads and links classes, verifies bytecode, and creates frequently used objects. Framework-based applications may repeat a large amount of this work on every start.</p>
<p><strong>The ahead-of-time cache moves some of that work to an earlier training run. </strong>During training, the JVM records classes and heap objects used by the application. Later executions can load those prepared artifacts instead of creating everything again.</p>
<p>For example, the cache may contain <em>Class</em> objects together with their related strings and byte arrays. Reusing these objects can reduce both startup time and the time required to reach peak performance.</p>
<p>The difficulty is that garbage collectors don&#8217;t always represent object references in the same way. A cached object prepared for one collector may contain references that another collector can&#8217;t use directly. This previously limited AOT object caching to compatible garbage collectors.</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/516">JEP 516</a> adds a collector-independent representation for cached objects. Instead of storing references in a format tied to one collector, the cache can store objects in a form that the JVM converts while loading them. JDK 26 can therefore use two approaches. A GC-specific cache can be mapped directly into memory for a fast warm start. A GC-independent cache can be streamed into the heap and converted to the selected collector&#8217;s object format.</p>
<p><strong>AOT object caching now works with every garbage collector, including ZGC.</strong> This allows applications to combine faster startup and warmup with ZGC&#8217;s low-pause behavior.</p>
<h3 id="bd-3-smaller-default-initial-heap" data-id="3-smaller-default-initial-heap">3.3. Smaller Default Initial Heap</h3><div class="bd-anchor" id="3-smaller-default-initial-heap"></div>
<p>The JVM also prepares an initial amount of heap memory at startup. We can set this value explicitly with <em>-Xms</em> or <em>-XX:InitialHeapSize</em>. When neither option is present, the JVM calculates a default.</p>
<p>Earlier releases based the default on 1.5625% of the machine&#8217;s physical memory. This is approximately one sixty-fourth of the available RAM.</p>
<p>Consequently, a high-memory server could receive a surprisingly large initial heap. On a machine with 256 GB of memory, 1.5625% represents roughly 4 GB before other JVM sizing rules are considered. A small service may not need anything close to that amount during startup. Preparing a larger heap involves additional initialization and metadata work. This can delay startup even though most of the initial space remains unused.</p>
<p>JDK 26 now uses <em>MinHeapSize</em> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://bugs.openjdk.org/browse/JDK-8371986"> as the default initial heap</a> when no explicit size is configured. The JVM starts with a smaller heap and expands it as the application requires more memory.</p>
<p><strong>This change reduces unnecessary startup work for applications that rely on the default heap settings. </strong>Applications that already provide &#8211;<em>Xms</em> or &#8211;<em>XX:InitialHeapSize</em> retain their configured behavior.</p>
<h2 id="bd-compiler-and-runtime-improvements" data-id="compiler-and-runtime-improvements">4. Compiler and Runtime Improvements</h2><div class="bd-anchor" id="compiler-and-runtime-improvements"></div>
<p>The C2 compiler can now <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://bugs.openjdk.org/browse/JDK-8325467">optimize methods with very large parameter lists</a>. Such methods previously remained on C1-compiled or interpreted paths. This change mainly helps generated code and frameworks that create unusually wide method signatures.</p>
<p>C2 also has a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://bugs.openjdk.org/browse/JDK-8340093">better cost model for SuperWord loop vectorization</a>. Processing several values with SIMD instructions can be faster than scalar execution. However, packing, shuffling, and combining vectors also cost CPU time. <strong>The improved model helps C2 vectorize a loop only when the expected gain outweighs that extra work.</strong> No application changes are required.</p>
<p>Finally, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://bugs.openjdk.org/browse/JDK-8369238">virtual threads can unmount from their carrier threads</a> while waiting for class initialization in common paths. Before JDK 26, this wait could pin a carrier and prevent it from running other virtual threads. The change improves scalability during bursts of class loading and reduces the risk of carrier starvation.</p>
<h2 id="bd-measuring-the-upgrade" data-id="measuring-the-upgrade">5. Measuring the Upgrade</h2><div class="bd-anchor" id="measuring-the-upgrade"></div>
<p>Performance changes depend on allocation patterns, reference updates, startup behaviour, hardware, and the selected garbage collector. Therefore, an upgrade test should compare the same workload, JVM options, heap limits, and traffic profile on both JDK versions.</p>
<p>We can record Java Flight Recorder data during a representative run:</p>
<pre dir="ltr"><code>java -XX:StartFlightRecording=filename=jdk26.jfr,settings=profile \
	-jar application.jar</code></pre>
<p>Useful comparisons include startup time, request throughput, allocation rate, GC pause distribution, and CPU consumption. For an AOT cache, the training workload should also cover the application&#8217;s normal startup path.</p>
<p><strong>The best result is a repeatable improvement in the application&#8217;s own service-level metrics.</strong> A synthetic benchmark is useful for isolating a feature, but it doesn&#8217;t replace an end-to-end measurement.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2><div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored the main <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/06/09/jdk-26-performance-improvements/">performance improvements in JDK 26</a>. We looked at lazy constants, lower-allocation library code, G1&#8217;s reduced synchronization, broader AOT caching, smarter C2 decisions, and better virtual-thread behavior.</p>
<p>Most of these optimizations require little or no source change. By testing JDK 26 with production-like workloads, we can determine which improvements translate into meaningful gains for our applications.</p>
<p></p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jdk-26-performance-improvements">Performance Improvements in JDK 26</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/970060151/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970060151/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970060151/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970060151/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970060151/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970060151/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-jdk-26-performance-improvements#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-jdk-26-performance-improvements/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/970060151/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-structured-output</feedburner:origLink>
		<title>A Guide to Structured Output in Spring AI</title>
		<link>https://feeds.feedblitz.com/~/970058585/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/970058585/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Hardik Singh Behl]]></dc:creator>
		<pubDate>Fri, 25 Sep 2026 05:15:12 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=205052</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to receive structured output from LLMs with Spring AI.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/970058585/0/baeldung">A Guide to Structured Output in Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970058585/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970058585/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970058585/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970058585/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970058585/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-structured-output#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-structured-output/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>When interacting with chatbots, we&#8217;re used to the plain text responses from <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/large-language-models">Large Language Models (LLMs)</a>. However, when integrating these LLMs into our application, it becomes a problem to use these responses programmatically.</p>
<p>Spring AI solves this problem with its structured output support. Instead of dealing with raw strings, we can instruct the model to return data that maps directly to our Java classes, collections, and other types.</p>
<p>In this tutorial, we&#8217;ll explore receiving structured output from LLMs using Spring AI.</p>
<h2 id="bd-setting-up-the-project" data-id="setting-up-the-project">2. Setting up the Project</h2>
<div class="bd-anchor" id="setting-up-the-project"></div>
<p>Before we dive into the implementation, let&#8217;s set up our project.</p>
<h3 id="bd-1-configuring-a-chat-model" data-id="1-configuring-a-chat-model">2.1. Configuring a Chat Model</h3>
<div class="bd-anchor" id="1-configuring-a-chat-model"></div>
<p>Let’s start by adding the necessary dependency to our project’s <em>pom.xml</em> file:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-starter-model-openai&lt;/artifactId&gt;
    &lt;version&gt;2.0.1&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Here, we import <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-openai">Spring AI&#8217;s OpenAI starter dependency</a>, which we&#8217;ll use to interact with a chat model.</p>
<p>Next, let’s configure our <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://platform.openai.com/api-keys">OpenAI API key</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://developers.openai.com/api/docs/models">chat model</a> in the <em>application.yaml</em> file:</p>
<pre><code class="language-yaml">spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        model: gpt-5.6-luna</code></pre>
<p>Here, we specify OpenAI&#8217;s <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://developers.openai.com/api/docs/models/gpt-5.6-luna">GPT 5.6 Luna</a> model using the <em>gpt-5.6-luna</em> model ID. Alternatively, we can use a different chat model, as the specific AI model or provider is irrelevant for this demonstration.</p>
<p>With these two properties set, <strong>Spring AI automatically creates a bean of type <em>ChatModel</em></strong>, which we&#8217;ll use to build a <em>ChatClient</em> bean:</p>
<pre><code class="language-java">@Bean
ChatClient chatClient(ChatModel chatModel) {
    return ChatClient
      .builder(chatModel)
      .build();
}</code></pre>
<p><strong>The <em>ChatClient</em> class acts as the main entry point for interacting with our configured chat completion model</strong>.</p>
<h3 id="bd-2-defining-our-domain-entity" data-id="2-defining-our-domain-entity">2.2. Defining Our Domain Entity</h3>
<div class="bd-anchor" id="2-defining-our-domain-entity"></div>
<p>Next, let&#8217;s define a domain entity that we&#8217;ll convert our model&#8217;s responses into:</p>
<pre><code class="language-java">record Recipe(
  String name,
  String cuisine,
  Difficulty difficulty,
  int prepTimeMinutes,
  List&lt;Ingredient&gt; ingredients,
  List&lt;String&gt; steps
) {
    record Ingredient(
      String name,
      String quantity
    ) {}
    enum Difficulty { EASY, MEDIUM, HARD }
}</code></pre>
<p>Here we define a <em>Recipe</em> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-record-keyword">record</a> with a nested <em>Ingredient</em> record and a <em>Difficulty</em> enum.</p>
<p>When defining domain entities, <strong>we should pick descriptive field names as it helps the LLM to populate them correctly</strong>. <strong>For ambiguous fields, we can annotate them with <em>@JsonPropertyDescription</em></strong> to give the model additional context.</p>
<h2 id="bd-converting-the-response-into-our-domain-entity" data-id="converting-the-response-into-our-domain-entity">3. Converting the Response Into Our Domain Entity</h2>
<div class="bd-anchor" id="converting-the-response-into-our-domain-entity"></div>
<p>With our setup in place, let&#8217;s use the <em>ChatClient</em> bean to ask the model for a recipe and convert its response into our record:</p>
<pre><code class="language-java">Recipe recipe = chatClient
  .prompt("Generate a recipe for a vegetarian lasagna.")
  .call()
  .entity(Recipe.class);
assertThat(recipe)
  .hasNoNullFieldsOrProperties()
  .satisfies(r -&gt; assertThat(r.ingredients())
    .hasSizeGreaterThan(1)
  );</code></pre>
<p>Here, we pass an instruction to generate a recipe and then invoke the <em>entity()</em> method with the <em>Recipe</em> class instead of calling <em>content()</em>, which would have given us a raw text response.</p>
<p><strong>The <em>entity()</em> method hands us a fully populated <em>Recipe</em> instance</strong>, without us writing a single line of parsing logic. Behind the scenes, Spring AI performs the following steps:</p>
<ul>
<li>First, generates a JSON schema from the target type we pass in.</li>
<li>Then, it appends this schema to our user prompt along with a set of format instructions.</li>
<li>Finally, once the model replies, it deserializes the response text into an instance of our target type</li>
</ul>
<h2 id="bd-self-correcting-structured-output" data-id="self-correcting-structured-output">4. Self-Correcting Structured Output</h2>
<div class="bd-anchor" id="self-correcting-structured-output"></div>
<p><strong>Even with a clear schema and formatting instructions in the prompt, a chat model can still return a response that doesn&#8217;t conform to it</strong>. Let&#8217;s look at some ways in which we can recover from such failures.</p>
<h3 id="bd-1-client-side-validation-with-validateschema" data-id="1-client-side-validation-with-validateschema">4.1. Client-Side Validation With <em>validateSchema()</em></h3>
<div class="bd-anchor" id="1-client-side-validation-with-validateschema"></div>
<p>In the first approach, the response is validated on our side before deserialization:</p>
<pre><code class="language-java">Recipe recipe = chatClient
  .prompt("Generate a recipe for a high protein dessert.")
  .call()
  .entity(Recipe.class, spec -&gt; spec.validateSchema());</code></pre>
<p>Here, we pass an additional lambda to <em>entity()</em> and call the <em>validateSchema()</em> method.</p>
<p>With this enabled, <strong>Spring AI validates the model&#8217;s response against the generated JSON schema before deserializing it</strong>. <strong>In case of failure, it sends the validation errors back to the model</strong>. This process repeats until the output becomes valid or the retry limit is exhausted, which defaults to three.</p>
<p>Alternatively, we can register the <em>StructuredOutputValidationAdvisor</em> while building our <em>ChatClient</em> bean. Using this approach, we can override the default settings and apply the client-side validation to every call:</p>
<pre><code class="language-java">@Bean
ChatClient validatingChatClient(ChatModel chatModel) {
    return ChatClient
      .builder(chatModel)
      .defaultAdvisors(StructuredOutputValidationAdvisor.builder()
        .maxRepeatAttempts(5)
        .outputType(Recipe.class)
        .jsonMapper(JsonMapper.builder().build())
        .build())
      .build();
}</code></pre>
<p>Here, we raise the retry limit to five and declare <em>Recipe</em> as the default output type. Additionally, we supply a custom <em>JsonMapper</em> instance that performs the validation and the deserialization tasks. Here, we simply configure one with the default settings, but we can customize as per requirements.</p>
<h3 id="bd-2-server-side-validation-with-useproviderstructuredoutput" data-id="2-server-side-validation-with-useproviderstructuredoutput">4.2. Server-Side Validation With <em>useProviderStructuredOutput()</em></h3>
<div class="bd-anchor" id="2-server-side-validation-with-useproviderstructuredoutput"></div>
<p>Alternatively, <strong>instead of validating the response ourselves, we can delegate the job to the model provider</strong>. Most modern providers such as OpenAI, Anthropic, Google, and Mistral accept a schema as part of the API request and guarantee that the response conforms to it.</p>
<p>We can use this capability through another method on the same lambda:</p>
<pre><code class="language-java">Recipe recipe = chatClient
  .prompt("Generate a recipe for a gluten-free breakfast.")
  .call()
  .entity(Recipe.class, spec -&gt; spec.useProviderStructuredOutput());</code></pre>
<p>With this enabled, Spring AI sends the schema to the provider as a dedicated API field instead of appending instructions to the user prompt.</p>
<p>However, before relying on this, we should confirm that both our model and our provider support it. <strong>We can even use it along with <em>validateSchema()</em> to have a more resilient setup</strong>.</p>
<h2 id="bd-converting-the-response-into-a-list" data-id="converting-the-response-into-a-list">5. Converting the Response Into a <em>List</em></h2>
<div class="bd-anchor" id="converting-the-response-into-a-list"></div>
<p>Sometimes we might want the model to return a collection of results instead of a single object:</p>
<pre><code class="language-java">List&lt;Recipe&gt; recipes = chatClient
  .prompt("Generate 3 recipes for vegetarian dishes.")
  .call()
  .entity(new ParameterizedTypeReference&lt;List&lt;Recipe&gt;&gt;() {});
assertThat(recipes)
  .hasSize(3)
  .allSatisfy(recipe -&gt; assertThat(recipe)
    .hasNoNullFieldsOrProperties()
  );</code></pre>
<p>Here, <strong>instead of passing a class to the <em>entity()</em> method, we pass a <em>ParameterizedTypeReference</em> instance</strong>. <strong>This wrapper preserves the generic type information about our <em>Recipe</em> record</strong> and allows Spring AI to generate a JSON schema accordingly.</p>
<p>Alternatively, when we only need a list of plain strings, we can pass a <em>ListOutputConverter</em> instance to the <em>entity()</em> method:</p>
<pre><code class="language-java">List&lt;String&gt; dishes = chatClient
  .prompt("List 5 popular vegetarian dishes.")
  .call()
  .entity(new ListOutputConverter());
assertThat(dishes)
  .hasSize(5)
  .allSatisfy(dish -&gt; assertThat(dish)
    .isNotBlank()
  );</code></pre>
<p>In this lightweight option, the converter asks the model for a simple comma separated list instead of JSON and then splits the reply into a <em>List</em> of <em>String</em> values.</p>
<h2 id="bd-converting-the-response-into-a-map" data-id="converting-the-response-into-a-map">6. Converting the Response Into a <em>Map</em></h2>
<div class="bd-anchor" id="converting-the-response-into-a-map"></div>
<p>In scenarios where we don&#8217;t know the shape of the response in advance, we can pass a <em>MapOutputConverter</em> instance to the <em>entity()</em> method:</p>
<pre><code class="language-java">Map&lt;String, Object&gt; nutritionFacts = chatClient
  .prompt("Provide the nutrition facts per serving for a vegetarian lasagna.")
  .call()
  .entity(new MapOutputConverter());
assertThat(nutritionFacts)
  .isNotEmpty()
  .allSatisfy((nutrient, value) -&gt; {
    assertThat(nutrient).isNotBlank();
    assertThat(value).isNotNull();
  });</code></pre>
<p>Here, we receive a <em>Map</em> with <em>String</em> keys and <em>Object</em> values. The converter instructs the model to reply with a JSON object. In the absence of a fixed schema, the model decides the keys to return.</p>
<p>However, <strong>this flexibility costs us type safety</strong>, so we should still prefer a dedicated domain entity whenever we know the structure we expect.</p>
<h2 id="bd-converting-responses-manually-using-chatmodel" data-id="converting-responses-manually-using-chatmodel">7. Converting Responses Manually Using <em>ChatModel</em></h2>
<div class="bd-anchor" id="converting-responses-manually-using-chatmodel"></div>
<p>Until now, we&#8217;ve let the <em>ChatClient</em> bean handle everything for us. However, when we work directly with the lower level <em>ChatModel</em> abstraction, the <em>entity()</em> method isn&#8217;t available to us.</p>
<p>In such cases, we can use the converter classes ourselves:</p>
<pre><code class="language-java">BeanOutputConverter&lt;Recipe&gt; outputConverter = new BeanOutputConverter&lt;&gt;(Recipe.class);
String response = chatModel
  .call("Generate a recipe for a vegetarian lasagna. " + outputConverter.getFormat());
Recipe recipe = outputConverter.convert(response);
assertThat(recipe)
  .hasNoNullFieldsOrProperties();</code></pre>
<p>Here, we create a <em>BeanOutputConverter</em> instance for our <em>Recipe</em> record. This is the same converter Spring AI uses internally for our domain entities.</p>
<p>Then, we append the output of its <em>getFormat()</em> method to our prompt, which contains the generated JSON schema and formatting instructions. Finally, we pass the model&#8217;s response to its <em>convert()</em> method to obtain our <em>Recipe</em> instance.</p>
<p><strong>This manual approach isn&#8217;t limited to domain entities</strong>. <strong>We can follow the exact same process for lists and maps as well</strong>. <strong>The only thing we need to change is the converter instance we create</strong>.</p>
<h2 id="bd-creating-a-custom-output-converter" data-id="creating-a-custom-output-converter">8. Creating a Custom Output Converter</h2>
<div class="bd-anchor" id="creating-a-custom-output-converter"></div>
<p>So far, we&#8217;ve relied on the converters that Spring AI exposes, which address most of our requirements. However, <strong>we can implement a converter ourselves simply by implementing the <em>StructuredOutputConverter</em> interface</strong>:</p>
<pre><code class="language-java">class YamlOutputConverter&lt;T&gt; implements StructuredOutputConverter&lt;T&gt; {
    private final YAMLMapper yamlMapper = YAMLMapper.builder().build();
    private final Class&lt;T&gt; targetType;
    YamlOutputConverter(Class&lt;T&gt; targetType) {
        this.targetType = targetType;
    }
    @Override
    public String getFormat() {
        String schema = new BeanOutputConverter&lt;&gt;(targetType).getJsonSchema();
        return """
          Return a YAML response that matches this JSON schema: %s
          Do not include any explanations or markdown code fences.
          """.formatted(schema);
    }
    @Override
    public T convert(String source) {
        return yamlMapper.readValue(source, targetType);
    }
}</code></pre>
<p>Here, we create a converter to work with YAML responses.</p>
<p>We reuse <em>BeanOutputConverter</em> to generate a JSON schema for the target type and write the instructions to append to the user prompt in the <em>getFormat()</em> method. Then, in <em>convert()</em>, we deserialize the model&#8217;s response into our target type using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jackson-3-updates#3-support-for-alternative-data-formats"><em>YAMLMapper</em></a>.</p>
<p>Now, let&#8217;s verify that our converter deserializes a response correctly:</p>
<pre><code class="language-java">String yamlResponse = """
name: "Mediterranean Veggie Salad"
cuisine: "Mediterranean"
difficulty: "EASY"
prepTimeMinutes: 15
ingredients:
  - name: "Cucumber"
    quantity: "1 medium"
  - name: "Cherry tomatoes"
    quantity: "1 cup"
  - name: "Extra virgin olive oil"
    quantity: "2 tbsp"
steps:
  - "Step 1: Chop the cucumber and halve the cherry tomatoes."
  - "Step 2: Drizzle with olive oil and toss everything together."
""";
Recipe recipe = new YamlOutputConverter&lt;&gt;(Recipe.class)
  .convert(yamlResponse);
assertThat(recipe)
  .hasNoNullFieldsOrProperties();</code></pre>
<p>Here, we pass a sample model response to the <em>convert()</em> method of our converter and confirm that the <em>Recipe</em> record gets populated.</p>
<p>To use it against a live model, <strong>we can simply pass an instance of <em>YamlOutputConverter</em> to the <em>entity()</em> method</strong>.</p>
<h2 id="bd-conclusion" data-id="conclusion">9. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we&#8217;ve explored the structured output support in Spring AI.</p>
<p>We walked through converting a chat model&#8217;s response into a custom domain entity, a list, and a map. Additionally, we discussed the self-correcting features that help us recover when a model returns a response that doesn&#8217;t match our schema. Finally, we explored implementing a custom converter.</p>
<p>As always, all the code examples used in this article are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-structured-output">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-structured-output">A Guide to Structured Output in Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/970058585/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/970058585/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/970058585/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-10-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/970058585/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/970058585/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/970058585/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-structured-output#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-structured-output/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/970058585/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-fitnesse-acceptance-testing-framework</feedburner:origLink>
		<title>Introduction to FitNesse &#8211; An Acceptance Testing Framework</title>
		<link>https://feeds.feedblitz.com/~/969527999/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/969527999/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Olayemi Michael]]></dc:creator>
		<pubDate>Wed, 23 Sep 2026 06:58:59 +0000</pubDate>
				<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204954</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use FitNesse to write an acceptance test for your Java application.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/969527999/0/baeldung">Introduction to FitNesse – An Acceptance Testing Framework</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969527999/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969527999/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969527999/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969527999/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969527999/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-fitnesse-acceptance-testing-framework#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-fitnesse-acceptance-testing-framework/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>While <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/unit-vs-integration-testing">unit and integration tests</a> help developers verify that an application behaves as expected, they typically require programming knowledge to write and maintain. Acceptance tests, on the other hand, allow testers to verify an application&#8217;s behavior against its requirements without writing test code.</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://fitnesse.org/">FitNesse</a> is a popular acceptance testing framework for Java applications.</p>
<p>In this tutorial, we&#8217;ll see how to use FitNesse to write an acceptance test for a simple banking application.</p>
<h2 id="bd-understanding-the-fitnesse-framework" data-id="understanding-the-fitnesse-framework">2. Understanding the FitNesse Framework</h2>
<div class="bd-anchor" id="understanding-the-fitnesse-framework"></div>
<p>FitNesse is an acceptance testing framework that can be used with Java applications. It provides a web-based wiki interface where we can create, edit, and run acceptance tests without writing them in a traditional programming language. FitNesse uses its own wiki-style syntax rather than standard Markdown.</p>
<p>Furthermore, it supports two test systems: Fit and Slim. Slim is a commonly used option in modern FitNesse tests. <strong>With Slim, FitNesse communicates with fixture code through the Slim protocol, allowing our Java code to be invoked as part of an acceptance test</strong>.</p>
<p>To use FitNesse, we can download its executable JAR and run it as a Java application. Alternatively, we can add FitNesse as a test dependency to a Maven or Gradle project when we want to manage it as part of the project&#8217;s build configuration.</p>
<h2 id="bd-maven-dependency" data-id="maven-dependency">3. Maven Dependency</h2>
<div class="bd-anchor" id="maven-dependency"></div>
<p>Moving on, let&#8217;s add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.fitnesse/fitnesse"><em>fitnesse</em></a> dependency to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.fitnesse&lt;/groupId&gt;
    &lt;artifactId&gt;fitnesse&lt;/artifactId&gt;
    &lt;version&gt;20260313&lt;/version&gt;
    &lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;</code></pre>
<p>The <em>fitnesse</em> dependency includes the FitNesse application and its embedded web server. <strong>Once we start FitNesse, it exposes a web interface that we can access from a browser to create and run acceptance tests</strong>.</p>
<h2 id="bd-example-project" data-id="example-project">4. Example Project</h2>
<div class="bd-anchor" id="example-project"></div>
<p>To demonstrate how it works, let&#8217;s create a simple banking application.</p>
<h3 id="bd-1-bankaccount-class" data-id="1-bankaccount-class">4.1. <em>BankAccount</em> Class</h3>
<div class="bd-anchor" id="1-bankaccount-class"></div>
<p>First, let&#8217;s create a class named <em>BankAccount</em>:</p>
<pre><code class="language-java">public class BankAccount {
    private double balance;
    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }
    public void deposit(double amount) {
        balance += amount;
    }
    public void withdraw(double amount) {
        balance -= amount;
    }
    public double getBalance() {
        return balance;
    }
}
</code></pre>
<p>In the class above, we initialize the account with a balance provided through the constructor. Then, we define methods for depositing money, withdrawing money, and retrieving the current balance. For simplicity, we used the <em>double</em> type to represent monetary value.</p>
<h3 id="bd-2-bankaccountfixture-class" data-id="2-bankaccountfixture-class">4.2. <em>BankAccountFixture</em> Class</h3>
<div class="bd-anchor" id="2-bankaccountfixture-class"></div>
<p>Next, let&#8217;s create a fixture class that exposes the banking operations to FitNesse:</p>
<pre><code class="language-java">public class BankAccountFixture {
    private BankAccount account;
    private double depositAmount;
    private double withdrawAmount;
    public void setInitialBalance(double balance) {
        account = new BankAccount(balance);
    }
    public void setDeposit(double amount) {   
        this.depositAmount = amount;
    }
    public void setWithdraw(double amount) {
        this.withdrawAmount = amount;
    }
    public void execute() {          
        if (depositAmount &gt; 0) {
            account.deposit(depositAmount);
        }
        if (withdrawAmount &gt; 0) {
            account.withdraw(withdrawAmount);
        }
    }
    public double balance() {
        return account.getBalance();
    }
}</code></pre>
<p>The class above acts as a bridge between FitNesse and our <em>BankAccount </em>class. It exposes methods that FitNesse can invoke as part of an acceptance test.</p>
<p>The <em>setInitialBalance()</em>, <em>setDeposit()</em>, and <em>setWithdraw()</em> methods configure the test data, while <em>execute()</em> performs the requested operations on the account. Finally, the <em>balance()</em> method returns the resulting balance.</p>
<p><strong>These operations represent the behavior that we want to verify through our acceptance tests.</strong></p>
<h2 id="bd-writing-an-acceptance-test" data-id="writing-an-acceptance-test">5. Writing an Acceptance Test</h2>
<div class="bd-anchor" id="writing-an-acceptance-test"></div>
<p>To begin, let&#8217;s create a launcher class with a <em>main()</em> method to start the FitNesse server:</p>
<pre><code class="language-java">public class FitNesseLauncher {
    public static void main(String[] args) throws Exception {
        FitNesseMain.main(new String[] { "-p", "8080" });
    }
}
</code></pre>
<p>Next, let&#8217;s run the <em>main()</em> method and open the landing page by visiting it in our browser:</p>
<p>&nbsp;</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_landing_page.png"><img loading="lazy" decoding="async" class="alignnone wp-image-204955" src="https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_landing_page-300x185.png" alt="" width="1153" height="711" srcset="https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_landing_page-300x185.png 300w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_landing_page-768x475.png 768w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_landing_page-100x62.png 100w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_landing_page-600x371.png 600w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_landing_page.png 1123w" sizes="auto, (max-width: 1153px) 100vw, 1153px" /></a></p>
<p><strong>Here&#8217;s the default FitNesse landing page</strong>. Since we don&#8217;t need the default content for our example, let&#8217;s edit the page by clicking <em>Edit</em> and create our acceptance test:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/09/writing_fitnesse_test_wiki.png"><img loading="lazy" decoding="async" width="1366" height="699" class="alignnone size-full wp-image-205015" src="https://www.baeldung.com/wp-content/uploads/2026/09/writing_fitnesse_test_wiki.png" alt="creating an acceptance test" srcset="https://www.baeldung.com/wp-content/uploads/2026/09/writing_fitnesse_test_wiki.png 1366w, https://www.baeldung.com/wp-content/uploads/2026/09/writing_fitnesse_test_wiki-300x154.png 300w, https://www.baeldung.com/wp-content/uploads/2026/09/writing_fitnesse_test_wiki-1024x524.png 1024w, https://www.baeldung.com/wp-content/uploads/2026/09/writing_fitnesse_test_wiki-768x393.png 768w, https://www.baeldung.com/wp-content/uploads/2026/09/writing_fitnesse_test_wiki-100x51.png 100w, https://www.baeldung.com/wp-content/uploads/2026/09/writing_fitnesse_test_wiki-600x307.png 600w" sizes="auto, (max-width: 1366px) 100vw, 1366px" /></a></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>Here, we delete the original content of the page and add our test. First, we configure FitNesse to use the <em>Slim</em> test system. Next, we add the directory containing our compiled Java classes to the FitNesse classpath. Then, we import the package containing our fixture.</p>
<p>Finally, we define the acceptance test using FitNesse&#8217;s wiki table syntax.</p>
<p>After clicking the <em>Save</em> button, here&#8217;s the new page:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_test_page_wikistyle.png"><img loading="lazy" decoding="async" width="1366" height="698" class="alignnone size-full wp-image-205016" src="https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_test_page_wikistyle.png" alt="Defining the acceptance test using FitNesse's wiki table syntax." srcset="https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_test_page_wikistyle.png 1366w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_test_page_wikistyle-300x153.png 300w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_test_page_wikistyle-1024x523.png 1024w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_test_page_wikistyle-768x392.png 768w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_test_page_wikistyle-100x51.png 100w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_test_page_wikistyle-600x307.png 600w" sizes="auto, (max-width: 1366px) 100vw, 1366px" /></a></p>
<p>&nbsp;</p>
<p>The saved page now displays our acceptance test. However, the <em>Test</em> button isn&#8217;t enabled yet. Let&#8217;s configure the page properties to enable test execution.</p>
<p>To do this, let&#8217;s click on <em>Tools</em> and select <em>Properties</em>:</p>
<p>&nbsp;</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_properties_page.png"><img loading="lazy" decoding="async" class="alignnone wp-image-204958 size-full" src="https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_properties_page.png" alt="" width="1366" height="697" srcset="https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_properties_page.png 1366w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_properties_page-300x153.png 300w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_properties_page-1024x522.png 1024w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_properties_page-768x392.png 768w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_properties_page-100x51.png 100w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_properties_page-600x306.png 600w" sizes="auto, (max-width: 1366px) 100vw, 1366px" /></a></p>
<p>The page properties have different options. Under the <em>Page type</em>, let&#8217;s select <em>Test</em> and click the <em>Save Properties</em> button. After saving the properties, the Test button becomes available on the test page.</p>
<p>Next, let&#8217;s click <em>Test</em> to execute the acceptance test:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_acceptance_test_result.png"><img loading="lazy" decoding="async" width="1366" height="697" class="alignnone size-full wp-image-205017" src="https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_acceptance_test_result.png" alt="acceptance test result" srcset="https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_acceptance_test_result.png 1366w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_acceptance_test_result-300x153.png 300w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_acceptance_test_result-1024x522.png 1024w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_acceptance_test_result-768x392.png 768w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_acceptance_test_result-100x51.png 100w, https://www.baeldung.com/wp-content/uploads/2026/09/fitnesse_acceptance_test_result-600x306.png 600w" sizes="auto, (max-width: 1366px) 100vw, 1366px" /></a></p>
<p>&nbsp;</p>
<p>The test results show that all five assertions passed successfully, with 0 wrong assertions, 0 ignored assertions, and 0 exceptions. The resulting balances also match the expected values in each test case.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this lesson, we learned how to use FitNesse to write and execute acceptance tests for a Java application. Also, we built a simple banking application and created a Slim fixture that allowed FitNesse to interact with the application&#8217;s business logic.</p>
<p>Additionally, we saw how to configure and start the FitNesse server, configure FitNesse to use the Slim test system, and add compiled Java classes to the FitNesse classpath.</p>
<p>As always, the source code for the examples is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/testing-modules/testing-libraries-3">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-fitnesse-acceptance-testing-framework">Introduction to FitNesse – An Acceptance Testing Framework</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/969527999/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969527999/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969527999/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969527999/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969527999/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969527999/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-fitnesse-acceptance-testing-framework#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-fitnesse-acceptance-testing-framework/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/969527999/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-triton-api</feedburner:origLink>
		<title>Introduction to Triton Java API</title>
		<link>https://feeds.feedblitz.com/~/969527177/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/969527177/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Nikhil Bhargav]]></dc:creator>
		<pubDate>Wed, 23 Sep 2026 06:48:14 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204962</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn to use NVIDIA's Triton Inference Server in Java to do object detection in images.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/969527177/0/baeldung">Introduction to Triton Java API</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969527177/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969527177/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969527177/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969527177/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969527177/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-triton-api#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-triton-api/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>We use clever engineering to deploy complex machine learning models into production. While model research, training, and fine-tuning are predominantly performed in <em>Python</em> with frameworks such as <em>PyTorch</em> or <em>TensorFlow</em>, enterprise backends are often built in <em>Java</em>. Hence, we need a robust, scalable serving infrastructure to bridge this gap.</p>
<p><strong>NVIDIA’s Triton Inference Server is an open-source model serving software that standardizes model deployment and execution</strong>. It provides a unified architecture capable of hosting models trained in almost any framework, such as ONNX, TensorFlow, PyTorch, and NVIDIA’s highly optimized TensorRT. Triton provides us with dynamic batching, concurrent model execution, and memory management while exposing a clean API for client applications.</p>
<p>In this tutorial, we&#8217;ll learn to interact with Triton Inference Server using <em>Java</em> to demonstrate <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/object-detection-ssd-yolo">object detection</a> on images. We begin by reviewing the available APIs, then set up a local Triton instance using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/containers-vs-virtual-machines">Docker</a>, and finally write a concise <em>Java</em> client to execute a pre-trained <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/cs/yolo-algorithm">YOLO model</a>.</p>
<h2 id="bd-overview-of-the-java-api" data-id="overview-of-the-java-api">2. Overview of the Java API</h2>
<div class="bd-anchor" id="overview-of-the-java-api"></div>
<p>We have two primary pathways for integrating Triton with <em>Java</em> applications.</p>
<h3 id="bd-1-the-in-process-java-api" data-id="1-the-in-process-java-api">2.1. The In-Process Java API</h3>
<div class="bd-anchor" id="1-the-in-process-java-api"></div>
<p><strong>The In-Process <em>Java</em> API utilizes <em>Java Native Interface (JNI)</em> bindings to communicate directly with the underlying <em>libtritonserver</em> C library.</strong> Here, we bypass network protocols to embed the Triton server instance directly within the JVM process. We use this approach for low-latency edge deployments or network-starved environments.</p>
<p>Some of the important classes in this API are:</p>
<ul>
<li><em>TritonServer</em> that represents the embedded server instance.</li>
<li><em>TritonModel</em> that represents a loaded model ready for inference.</li>
<li><em>TritonRequest</em> and <em>TritonResponse</em> that help us pass tensors back and forth in memory.</li>
</ul>
<h3 id="bd-2-the-java-client-api-grpchttp" data-id="2-the-java-client-api-grpchttp">2.2. The Java Client API (gRPC/HTTP)</h3>
<div class="bd-anchor" id="2-the-java-client-api-grpchttp"></div>
<p>For most enterprise microservice architectures, we have models hosted on centralized CPU or GPU clusters. In such cases, we use the <em>Java</em> Client API via <em>gRPC</em> to access the models. <strong>Under the hood, Triton exposes a robust <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-protobuf-maps"><em>protobuf</em></a> definition that <em>Java</em> applications can compile into strongly-typed stubs.</strong></p>
<p>The key classes of <em>gRPC</em> API are:</p>
<ul>
<li><em>ManagedChannel</em> that represents the underlying <em>gRPC</em> connection pool to the Triton server.</li>
<li><em>InferenceServerBlockingStub</em> that holds the synchronous client stub generated from Triton&#8217;s protobuf definitions.</li>
<li><em>ModelInferRequest</em> that encapsulates our input tensors, shapes, and data types.</li>
<li><em>ModelInferResponse</em> that gives the output containing the computed predictions.</li>
<li><em>InferTensorContents</em> to safely pack raw primitive types (like floats or integers) into byte streams.</li>
</ul>
<h3 id="bd-3-official-repository" data-id="3-official-repository">2.3. Official Repository</h3>
<div class="bd-anchor" id="3-official-repository"></div>
<p>For more advanced use, NVIDIA provides an official <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/triton-inference-server/server">repository</a> containing utility wrappers under <em>client/src/java</em>.<strong> It offers a cleaner <em>TritonClient</em> class that abstracts away the boilerplate protobuf generation and supports asynchronous inference and shared-memory execution. </strong> Apart from this, some of the other advanced Triton features include:</p>
<ul>
<li>Asynchronous Inference: Using <em>gRPC&#8217;s</em> asynchronous stubs to handle high-throughput, non-blocking requests.</li>
<li>Shared Memory: Allowing Triton and the <em>Java</em> client to read/write from the same system memory (or CUDA memory) space, eliminating the serialization and network overhead of <em>gRPC</em>.</li>
<li>String Tensors: Passing text data to NLP models like BERT or LLaMA.</li>
</ul>
<p>In this article, we&#8217;ll focus on building the raw <em>gRPC</em> <em>Java</em> Client API, as it&#8217;s the most common integration pattern for custom enterprise environments.</p>
<h2 id="bd-local-setup-with-docker" data-id="local-setup-with-docker">3. Local Setup With Docker</h2>
<div class="bd-anchor" id="local-setup-with-docker"></div>
<p>We&#8217;ll show standard object detection using the YOLO pretrained model running the Triton Inference Server. To run our example locally, we&#8217;ll use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.docker.com/products/docker-desktop/"><em>Docker Desktop</em></a> to run an active instance of Triton Inference Server.</p>
<h3 id="bd-1-prerequisites" data-id="1-prerequisites">3.1. Prerequisites</h3>
<div class="bd-anchor" id="1-prerequisites"></div>
<p>Here are the prerequisites to run this setup on a local CPU-based machine:</p>
<ul>
<li>Docker Desktop.</li>
<li>Java 11 or higher.</li>
<li>Maven for dependency management.</li>
<li>Python 3.10 and above.</li>
</ul>
<h3 id="bd-2-complete-project-structure" data-id="2-complete-project-structure">3.2. Complete Project Structure</h3>
<div class="bd-anchor" id="2-complete-project-structure"></div>
<p>Here&#8217;s the complete directory structure for this project:</p>
<pre><code class="language-shell">triton-java-yolo/
├── python/
│   ├── export_model.py              # It exports YOLO to ONNX 
│   └── requirements.txt             # Python dependencies (torch, ultralytics, onnx)
│
├── model_repository/                # It's a directory mounted into Docker Triton
│   └── yolo_onnx/
│       ├── 1/
│       │   └── model.onnx           # Compiled ONNX CPU runtime engine file
│       └── config.pbtxt             # Triton model configuration
│
├── src/
│   ├── main/
│   │   ├── proto/                   # Triton gRPC Protobuf definitions
│   │   │   ├── grpc_service.proto
│   │   │   └── model_config.proto
│   │   ├── java/
│   │   │   └── com/baeldung/triton/
│   │   │       ├── client/
│   │   │       │   └── TritonClientManager.java   
│   │   │       ├── yolo/
│   │   │       │   ├── ImagePreprocessor.java    
│   │   │       │   ├── YoloPostprocessor.java     
│   │   │       │   └── YoloInferenceRunner.java   
│   │   │       └── App.java                       
│   │   └── resources/
│   │       └── sample.jpeg           # Sample input image for object detection
│   └── test/
│       └── java/
│           └── com/baeldung/triton/
│               └── TritonInferenceLiveTest.java
│
├── pom.xml                          # Maven build file with gRPC &amp; Protobuf plugins</code></pre>
<h3 id="bd-3-building-the-model" data-id="3-building-the-model">3.3. Building the Model</h3>
<div class="bd-anchor" id="3-building-the-model"></div>
<p>First, we create the file <em>export_model.py:</em></p>
<pre><code class="language-python">from ultralytics import YOLO
model = YOLO("yolov8n.pt")
model.export(format="onnx", imgsz=640, dynamic=False)
print("Export complete: yolov8n.onnx generated.")
</code></pre>
<p>Then, we build our <em>Python</em> virtual environment and use it to run the file <em>export_model.py:</em></p>
<pre><code class="language-shell">python export_model.py</code></pre>
<p>This&#8217;ll download the pretrained model and then generate the ONNX runtime <em>yolov8n.onnx</em> in our working directory:</p>
<pre><code class="language-shell">Downloading https://github.com/ultralytics/assets/releases/download/v8.4.0/yolovDownloading https://github.com/ultralytics/assets/releases/download/v8.4.0/yolov8n.pt to 'yolov8n.pt': 100% ━━━━━━━━━━━━ 6.2MB 42.1MB/s 0.1s
Ultralytics 8.4.34 &#x1f680; Python-3.12.5 torch-2.2.2 CPU (Intel Core i9-9880H 2.30GHz)
YOLOv8n summary (fused): 72 layers, 3,151,904 parameters, 0 gradients, 8.7 GFLOPs
PyTorch: starting from 'yolov8n.pt' with input shape (1, 3, 640, 640) BCHW and output shape(s) (1, 84, 8400) (6.2 MB)
ONNX: starting export with onnx 1.22.0 opset 17...
</code></pre>
<h3 id="bd-4-creating-the-model-repository" data-id="4-creating-the-model-repository">3.4. Creating the Model Repository</h3>
<div class="bd-anchor" id="4-creating-the-model-repository"></div>
<p><strong>Triton loads models from a strictly formatted directory structure known as the model repository</strong>. So, we&#8217;ll create the model_repository directory structure and  move and rename the ONNX file:</p>
<pre><code class="language-shell">cp yolov8n.onnx ../model_repository/yolo_onnx/1/model.onnx
</code></pre>
<p>Thereafter, we&#8217;ll update the file <em>config.pbtxt</em>:</p>
<pre><code class="language-properties">name: "yolo_onnx"
backend: "onnxruntime"
max_batch_size: 0
input [
  {
    name: "images"
    data_type: TYPE_FP32
    dims: [ 1, 3, 640, 640 ]
  }
]
output [
  {
    name: "output0"
    data_type: TYPE_FP32
    dims: [ 1, 84, 8400 ]
  }
]</code></pre>
<h3 id="bd-35-run-server" data-id="35-run-server"> 3.5. Run Server</h3>
<div class="bd-anchor" id="35-run-server"></div>
<p>With the repository prepared, let&#8217;s launch Triton Inference Server using the Docker container and mount our model_repository directory:</p>
<pre><code class="language-shell">docker run --platform linux/amd64 --rm \ -p 8000:8000 -p 8001:8001 -p 8002:8002 \ -v /absolute/path/to/model_repository:/models \ nvcr.io/nvidia/tritonserver:23.10-py3 \ tritonserver --model-repository=/models</code></pre>
<p>Here, we use Port 8000 for HTTP REST requests, port 8001 for the <em>gRPC</em> endpoint in our <em>Java</em> application, and port 8002 for Prometheus metrics.</p>
<p>We can verify the container by looking at the logs:</p>
<pre><code class="language-shell">=============================
== Triton Inference Server ==
=============================
NVIDIA Release 23.10 (build 72127154)
Triton Server Version 2.39.0
Copyright (c) 2018-2023, NVIDIA CORPORATION &amp; AFFILIATES.  All rights reserved.
I0822 07:34:40.100142 1 server.cc:619] 
+-------------+-----------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Backend     | Path                                                            | Config                                                                                                                                                        |
+-------------+-----------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| onnxruntime | /opt/tritonserver/backends/onnxruntime/libtriton_onnxruntime.so | {"cmdline":{"auto-complete-config":"true","backend-directory":"/opt/tritonserver/backends","min-compute-capability":"6.000000","default-max-batch-size":"4"}} |
+-------------+-----------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
I0822 07:34:40.100196 1 server.cc:662] 
+-----------+---------+--------+
| Model     | Version | Status |
+-----------+---------+--------+
| yolo_onnx | 1       | READY  |
+-----------+---------+--------+
</code></pre>
<h3 id="bd-6-configure-protobufs" data-id="6-configure-protobufs">3.6. Configure Protobufs</h3>
<div class="bd-anchor" id="6-configure-protobufs"></div>
<p>To run our <em>Java</em> client, we need to set up the <em>gRPC</em> proto and the Maven dependencies. First, we place the official Triton proto files (<em>grpc_service.proto</em> and <em>model_config.proto</em> from the Triton Server repo) inside s<em>rc/main/proto/</em>:</p>
<pre><code class="language-shell">curl -L https://raw.githubusercontent.com/triton-inference-server/common/main/protobuf/grpc_service.proto -o src/main/proto/grpc_service.proto
curl -L https://raw.githubusercontent.com/triton-inference-server/common/main/protobuf/model_config.proto -o src/main/proto/model_config.proto</code></pre>
<h2 id="bd-java-client" data-id="java-client">4. Java Client</h2>
<div class="bd-anchor" id="java-client"></div>
<p>Next, we build our <em>Java</em> application.</p>
<h3 id="bd-1-maven-dependencies" data-id="1-maven-dependencies">4.1. Maven Dependencies</h3>
<div class="bd-anchor" id="1-maven-dependencies"></div>
<p>First, we need to declare our Maven dependencies for this project. For <em>gRPC,</em> we&#8217;ll use the core libraries: <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/io.grpc/grpc-netty-shaded"><em>grpc-netty-shaded</em>,</a> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/io.grpc/grpc-protobuf"><em>grpc-protobuf</em></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/io.grpc/grpc-stub"><em>grpc-stub</em></a>, alongside <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api"><em>javax.annotation-api</em></a>. To compile the .proto files into <em>Java</em> classes automatically, we choose the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/kr.motd.maven/os-maven-plugin"><em>os-maven-plugin</em></a> and the <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.xolstice.maven.plugins/protobuf-maven-plugin">protobuf-maven-plugin</a>. </em></p>
<p>Here is our dependencies:</p>
<pre><code class="language-xml">&lt;dependencies&gt;
    &lt;!-- gRPC &amp; Protobuf Dependencies --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.grpc&lt;/groupId&gt;
        &lt;artifactId&gt;grpc-netty-shaded&lt;/artifactId&gt;
        &lt;version&gt;${grpc.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.grpc&lt;/groupId&gt;
        &lt;artifactId&gt;grpc-protobuf&lt;/artifactId&gt;
        &lt;version&gt;${grpc.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.grpc&lt;/groupId&gt;
        &lt;artifactId&gt;grpc-stub&lt;/artifactId&gt;
        &lt;version&gt;${grpc.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.annotation&lt;/groupId&gt;
        &lt;artifactId&gt;javax.annotation-api&lt;/artifactId&gt;
        &lt;version&gt;1.3.2&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</code></pre>
<p>Next is our list of plugins:</p>
<pre><code class="language-xml">&lt;plugins&gt;
    &lt;!-- Compiles the .proto files in src/main/proto into Java classes --&gt;
    &lt;plugin&gt;
        &lt;groupId&gt;org.xolstice.maven.plugins&lt;/groupId&gt;
        &lt;artifactId&gt;protobuf-maven-plugin&lt;/artifactId&gt;
        &lt;version&gt;0.6.1&lt;/version&gt;
        &lt;configuration&gt;
            &lt;protocArtifact&gt;com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}&lt;/protocArtifact&gt;
            &lt;pluginId&gt;grpc-java&lt;/pluginId&gt;
            &lt;pluginArtifact&gt;io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}&lt;/pluginArtifact&gt;
        &lt;/configuration&gt;
        &lt;executions&gt;
            &lt;execution&gt;
                &lt;goals&gt;
                    &lt;goal&gt;compile&lt;/goal&gt;
                    &lt;goal&gt;compile-custom&lt;/goal&gt;
                &lt;/goals&gt;
            &lt;/execution&gt;
        &lt;/executions&gt;
    &lt;/plugin&gt;
&lt;/plugins&gt;</code></pre>
<h3 id="bd-2-establishing-the-connection" data-id="2-establishing-the-connection">4.2. Establishing the Connection</h3>
<div class="bd-anchor" id="2-establishing-the-connection"></div>
<p>We initiate communication by creating a <em>ManagedChannel</em> and instantiating a blocking stub:</p>
<pre><code class="language-java">ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8001)
  .usePlaintext() 
  .build();
GRPCInferenceServiceBlockingStub blockingStub = GRPCInferenceServiceGrpc.newBlockingStub(channel);</code></pre>
<h3 id="bd-3-verifying-server-health" data-id="3-verifying-server-health">4.3. Verifying Server Health</h3>
<div class="bd-anchor" id="3-verifying-server-health"></div>
<p>It&#8217;<span class="citation-2 citation-end-2">s a best practice to query the server&#8217;s health status to ensure the model has been loaded successfully, so that we are good to send heavy inference requests:</span></p>
<pre><code class="language-java">public boolean isServerLive() {
    try {
        ServerLiveRequest request = ServerLiveRequest.newBuilder().build();
        ServerLiveResponse response = blockingStub.serverLive(request);
        return response.getLive();
    } catch (Exception e) {
        return false;
    }
}</code></pre>
<h3 id="bd-4-preparing-the-input-tensor-in-java" data-id="4-preparing-the-input-tensor-in-java">4.4. Preparing the Input Tensor in Java</h3>
<div class="bd-anchor" id="4-preparing-the-input-tensor-in-java"></div>
<p>Here is the preprocessing logic using a standard <em>BufferedImage:</em></p>
<pre><code class="language-java">InputStream is = ImagePreprocessor.class.getClassLoader().getResourceAsStream(resourcePath);
BufferedImage originalImage = ImageIO.read(is);
BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_SMOOTH), 0, 0, null);
g.dispose();</code></pre>
<p>Before sending an image to the YOLOv8 model, we preprocess it to match the exact input shape and format as per the model configuration<strong>. </strong><strong>Standard computer vision preprocessing involves resizing, normalizing, and reordering the color channels.</strong> <strong>YOLOv8 expects an input tensor of shape [1, 3, 640, 640]. This corresponds to a batch size of 1, 3 color channels (RGB), and a resolution of 640&#215;640.</strong> Furthermore, the data must be in planar format (NCHW), in which we store all red pixels first, followed sequentially by all green and blue pixels. Finally, we extract and normalize the pixels into a float array <em>tensorData</em>:</p>
<pre><code class="language-java">int totalPixels = targetWidth * targetHeight;
float[] tensorData = new float[3 * totalPixels];
int rOffset = 0, gOffset = totalPixels, bOffset = 2 * totalPixels;
for (int y = 0; y &lt; targetHeight; y++) {
    for (int x = 0; x &lt; targetWidth; x++) { 
        int rgb = resizedImage.getRGB(x, y); 
        int r = (rgb &gt;&gt; 16) &amp; 0xFF;
        int gVal = (rgb &gt;&gt; 8) &amp; 0xFF;
        int b = rgb &amp; 0xFF;
        int index = y * targetWidth + x;
        tensorData[rOffset + index] = r / 255.0f;
        tensorData[gOffset + index] = gVal / 255.0f;
        tensorData[bOffset + index] = b / 255.0f;
    }
}</code></pre>
<h3 id="bd-5-executing-the-inference" data-id="5-executing-the-inference">4.5. Executing the Inference</h3>
<div class="bd-anchor" id="5-executing-the-inference"></div>
<p>With our input tensor ready, we now construct our <em>ModelInferRequest</em>.</p>
<p>The protobuf definitions suggest that output data can be extracted using <em>outputTensor.getContents().getFp32ContentsList()</em>, <strong>Triton optimizes performance by leaving this list empty for output responses, avoiding the massive performance penalty of deserializing hundreds of thousands of floats.</strong> Instead, Triton packs the underlying <em>C++</em> memory buffer into the <em>raw_output_contents</em> field as a <em>ByteString</em>. We extract these bytes and wrap them in a <em>Java ByteBuffer</em> using Little Endian byte order to read the floats manually:</p>
<pre><code class="language-java">ModelInferRequest request = ModelInferRequest.newBuilder()
  .setModelName("yolo_onnx")
  .setModelVersion("1")
  .addInputs(inputTensor)
  .build();
ModelInferResponse response = blockingStub.modelInfer(request);
ByteString rawData = response.getRawOutputContents(0);
ByteBuffer buffer = rawData.asReadOnlyByteBuffer().order(ByteOrder.LITTLE_ENDIAN);
List resultList = new ArrayList&lt;&gt;(buffer.capacity() / 4);
while (buffer.hasRemaining()) {
    resultList.add(buffer.getFloat());
}</code></pre>
<h3 id="bd-6-non-maximal-suppression" data-id="6-non-maximal-suppression">4.6. Non-Maximal Suppression</h3>
<div class="bd-anchor" id="6-non-maximal-suppression"></div>
<p>YOLOv8 returns a massive matrix of shape [1, C, N] where C=84 (COCO dataset classes) and N&gt;8000 (different anchor boxes across the image). So, it means that when an object is clearly visible, multiple overlapping anchor boxes will report a high-confidence detection for the same object.</p>
<p>To prevent our application from reporting multiple bounding boxes for a single object, we apply Non-Maximum Suppression (NMS). NMS isolates the highest-confidence bounding box for an object and suppresses (discards) any other boxes that overlap it significantly. We determine overlap using Intersection over Union (IoU). If a lower-confidence box overlaps the highest-confidence box by more than our threshold (e.g., 50%), it&#8217;s treated as a duplicate and removed:</p>
<pre><code class="language-java">if (current.classId == next.classId) {
    if (calculateIoU(current, next) &gt; 0.5f) {
        suppressed[j] = true;
    }
}</code></pre>
<h3 id="bd-7-final-run" data-id="7-final-run">4.7. Final Run</h3>
<div class="bd-anchor" id="7-final-run"></div>
<p>Now, we tie all our components together in the main application class. We begin by exporting our YOLO model. Then we preprocess our sample cat image. After that, we run the Inference over gRPC and finally post-process and print the detection:</p>
<pre><code class="language-shell">mvn clean compile  
mvn exec:java -Dexec.mainClass="com.baeldung.triton.App"
</code></pre>
<p>It yields a clean, optimized output, accurately identifying objects in the image without duplicate bounding boxes:</p>
<pre><code class="language-shell">Connecting to Triton Inference Server at localhost:8001
Triton Server is live and ready.
Building inference request for model: yolo_onnx
Sending inference request to Triton...
Received 705600 data points. Parsing bounding boxes...
Raw boxes found before NMS: 8
Detected [cat] (Confidence: 83.7%) at Box [xMin=2.0, yMin=53.2]
Final valid objects detected: 1</code></pre>
<h2 id="bd-testing" data-id="testing">5. Testing</h2>
<div class="bd-anchor" id="testing"></div>
<p><strong>We need to start the Triton Inference Server Docker container and ensure it is running on localhost:8001 with our yolo_onnx model loaded to run the Live Inference test.</strong></p>
<p>In our Live Inference test, we first preprocess our sample cat image. Then carry on the inference with the YOLO model, which natively outputs a matrix of [1, 84, 8400]. Moving further, we flatten it to 705,600 elements and then run the post-processor so the test logs print the NMS cat detection:</p>
<pre><code class="language-java">public void givenValidImage_whenRunningInference_thenReturnsDetections() throws Exception {
    float[] inputTensor = ImagePreprocessor.preprocessFromResources("sample.jpeg", 640, 640);
    YoloInferenceRunner runner = new YoloInferenceRunner(clientManager.getStub(), "yolo_onnx");
    List&lt;Float&gt; outputs = runner.runInference(inputTensor);
    assertFalse(outputs.isEmpty(), "Inference output should not be empty");
    
    assertEquals(705600, outputs.size(), 
      "Output tensor should contain exactly 705,600 float elements");
    YoloPostprocessor.parseAndPrint(outputs);
}</code></pre>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we&#8217;ve studied the Triton Inference Server and its integration with <em>Java.</em></p>
<p>In a nutshell, we explored how Triton Inference Server provides a powerful bridge between the Python-dominated ML ecosystem and <em>Java</em> microservices. By using Docker, we set up a local testing environment, constructed the necessary protobuf-based <em>gRPC</em> requests, and successfully executed an object detection pass using an ONNX CPU runtime engine.</p>
<p><strong>As ML models grow in size and complexity, utilizing dedicated inference servers like Triton ensures our <em>Java</em> applications remain responsive, scalable, and easy to maintain.</strong></p>
<p>As always, the complete code examples are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/triton">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-triton-api">Introduction to Triton Java API</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/969527177/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969527177/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969527177/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969527177/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969527177/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969527177/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-triton-api#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-triton-api/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/969527177/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-boot-upgrade-spring-framework-version</feedburner:origLink>
		<title>Upgrading Spring Framework Version in Spring Boot</title>
		<link>https://feeds.feedblitz.com/~/969518291/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/969518291/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Umara Mushtaq]]></dc:creator>
		<pubDate>Wed, 23 Sep 2026 03:55:47 +0000</pubDate>
				<category><![CDATA[Spring Boot]]></category>
		<category><![CDATA[Boot Basics]]></category>
		<category><![CDATA[Maven Basics]]></category>
		<category><![CDATA[Spring Core Basics]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/spring-boot-upgrade-spring-framework-version</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to upgrade the Spring Framework version in a Spring Boot application by understanding dependency management, selecting a compatible Boot release, and verifying the resolved dependencies with Maven.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/969518291/0/baeldung">Upgrading Spring Framework Version in Spring Boot</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969518291/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969518291/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Boot-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969518291/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969518291/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969518291/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-boot-upgrade-spring-framework-version#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-boot-upgrade-spring-framework-version/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot">Spring Boot</a> simplifies Java application development through auto-configuration, dependency management, embedded servers, and production-ready features. It builds on the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-intro">Spring Framework</a>, which provides features such as dependency injection, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mvc">Spring MVC</a>, and application configuration. <strong>As the Spring Framework evolves, developers may need to adopt newer framework versions to access security improvements, bug fixes, performance enhancements, and compatibility options for newer Java versions</strong>. However, upgrading requires more than changing a version number. Spring Boot manages several related dependencies, so developers must maintain compatibility between them.</p>
<p>In this tutorial, we&#8217;ll explore how to upgrade the Spring Framework version in a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-start">Spring Boot application</a> safely and systematically. First, we&#8217;ll examine how Spring Boot manages Spring Framework dependencies and how to identify the versions used by an application. Then, we&#8217;ll follow the recommended approach of upgrading Spring Boot to obtain the required Spring Framework version. Also, we&#8217;ll use the Maven dependency tree and effective POM to investigate unexpected dependency versions and identify dependency-management conflicts. Finally, we&#8217;ll review compatibility requirements, migration changes, and best practices to help maintain application stability after the upgrade.</p>
<h2 id="bd-spring-framework-version-management-in-spring-boot" data-id="spring-framework-version-management-in-spring-boot">2. Spring Framework Version Management in Spring Boot</h2>
<div class="bd-anchor" id="spring-framework-version-management-in-spring-boot"></div>
<p>Spring Boot manages dependency versions for each release. This dependency management includes the Spring Framework modules used by the application. Therefore, <strong>applications can declare Spring dependencies without specifying their versions individually</strong>.</p>
<p>To begin, a Maven project can use the Spring Boot parent:</p>
<pre><code class="language-java">&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;YOUR_BOOT_VERSION&lt;/version&gt;
    &lt;relativePath/&gt;
&lt;/parent&gt;</code></pre>
<p><strong>The Spring Boot parent provides access to the Spring Boot dependency management</strong>, including the <em>spring-boot-dependencies</em> POM. This POM defines compatible versions for Spring Framework modules and other dependencies.</p>
<p>Therefore, a dependency such as <em>spring-web</em> can be declared without specifying its version explicitly:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework&lt;/groupId&gt;
    &lt;artifactId&gt;spring-web&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<p>Maven then obtains the version from the Spring Boot dependency management. This approach keeps related Spring Framework modules aligned. It also avoids defining individual versions throughout the application.</p>
<p>The same mechanism applies when Spring Framework modules are included through a Spring Boot starter:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<p>The starter brings in the required Spring dependencies, while Spring Boot supplies their managed versions. As a result, the application uses a consistent set of Spring Framework modules without defining each version manually.</p>
<p>Therefore, <strong>upgrading Spring Framework in a Spring Boot application requires understanding this dependency-management relationship</strong>. Since Spring Boot manages the framework version for each release, the recommended approach is to upgrade Spring Boot to a release that provides the required Spring Framework version.</p>
<h2 id="bd-checking-the-current-spring-framework-version" data-id="checking-the-current-spring-framework-version">3. Checking the Current Spring Framework Version</h2>
<div class="bd-anchor" id="checking-the-current-spring-framework-version"></div>
<p>Before upgrading the Spring Framework, we should first identify the version currently used by the application. The Maven dependency tree can show the versions resolved during the build.</p>
<p>We can check them with the <em>dependency:tree</em> subcommand of <em>mvn</em>:</p>
<pre><code class="language-bash">mvn dependency:tree -Dincludes=org.springframework
</code></pre>
<p>The output may contain several entries:</p>
<pre><code class="language-text">org.springframework:spring-core:jar:5.2.8.RELEASE
org.springframework:spring-web:jar:5.2.8.RELEASE 
</code></pre>
<p>Each entry identifies a Spring Framework module and the version Maven resolves for it. However, <strong>the version shown in the dependency tree may not come directly from the project <em>pom.xml</em></strong>. Spring Boot can provide the version through its dependency management. Other imported BOMs or dependency-management configurations can also affect the final resolved version.</p>
<p>Therefore, checking the dependency tree gives us a clear starting point. Once we identify the current version, we can determine which Spring Boot release provides the required newer version. The next section explains this upgrade process.</p>
<h2 id="bd-upgrading-the-current-spring-framework-version" data-id="upgrading-the-current-spring-framework-version">4. Upgrading the Current Spring Framework Version</h2>
<div class="bd-anchor" id="upgrading-the-current-spring-framework-version"></div>
<p>Once we know the current Spring Framework version and how it&#8217;s managed, the next step is to select a Spring Boot release that manages the required version.</p>
<p>Spring Boot manages a compatible set of dependency versions for each release. Therefore, moving to a newer Boot release updates the managed Spring Framework version along with its related dependencies.</p>
<h3 id="bd-1-upgrade-the-spring-boot-version" data-id="1-upgrade-the-spring-boot-version">4.1. Upgrade the Spring Boot Version</h3>
<div class="bd-anchor" id="1-upgrade-the-spring-boot-version"></div>
<p>To upgrade Spring Boot, we only need to change the Boot version declared in the parent POM:</p>
<pre dir="ltr"><code dir="ltr">&lt;version&gt;NEW_BOOT_VERSION&lt;/version&gt;</code></pre>
<p><strong>After this change, Maven uses the dependency-management configuration associated with the new Spring Boot release</strong>. Consequently, the application receives the Spring Framework version managed by that release. We don&#8217;t need to specify versions for individual Spring Framework modules because Spring Boot continues to manage them.</p>
<h3 id="bd-2-verify-the-resolved-version" data-id="2-verify-the-resolved-version">4.2. Verify the Resolved Version</h3>
<div class="bd-anchor" id="2-verify-the-resolved-version"></div>
<p>After completing the upgrade, run the dependency tree again. Check the Spring Framework modules and their resolved versions. They should correspond to the dependency versions managed by the selected Spring Boot release.</p>
<p>This verification is important because changing the Boot version doesn&#8217;t guarantee that every dependency configuration in a complex project behaves as expected. Imported BOMs or internal libraries can influence the resolved version.<strong> If Maven resolves an unexpected Spring Framework version, we should inspect the effective project POM and dependency tree</strong>.</p>
<p>To that end, we continue with an explanation of how to trace such dependency-management issues.</p>
<h2 id="bd-investigating-unexpected-dependency-versions" data-id="investigating-unexpected-dependency-versions">5. Investigating Unexpected Dependency Versions</h2>
<div class="bd-anchor" id="investigating-unexpected-dependency-versions"></div>
<p>After upgrading Spring Boot, Maven should resolve the Spring Framework version managed by the selected Boot release. Complex projects can contain additional dependency-management configurations that affect the final resolved version. For example, the application may import another BOM or define its own dependency-management entries that override versions managed by Spring Boot. Dependencies may also introduce transitive Spring modules, which can influence dependency resolution.</p>
<p><strong>The effective POM helps trace the source of an unexpected dependency version as it combines the inherited and imported dependency configuration</strong>.</p>
<p>Let&#8217;s begin by generating it:</p>
<pre><code class="language-bash">mvn help:effective-pom
</code></pre>
<p>Then, we search for entries:</p>
<pre><code class="language-text">spring-boot-dependencies
spring-framework-bom</code></pre>
<p><strong>By examining the effective POM together with the dependency tree, we can determine which dependency-management configuration controls the resolved Spring Framework version and investigate possible overrides or conflicts</strong>.</p>
<h2 id="bd-verifying-the-upgrade" data-id="verifying-the-upgrade">6. Verifying the Upgrade</h2>
<div class="bd-anchor" id="verifying-the-upgrade"></div>
<p>Upgrading Spring Boot changes the managed Spring Framework version and may affect application behavior. Therefore, the upgrade should be validated beyond dependency resolution.</p>
<h3 id="bd-1-review-migration-changes" data-id="1-review-migration-changes">6.1. Review Migration Changes</h3>
<div class="bd-anchor" id="1-review-migration-changes"></div>
<p>Newer Spring Framework releases may introduce API changes, deprecate existing features, or remove APIs that were previously available. Configuration behavior can also change between framework versions. Therefore, we should review the relevant migration documentation and update application code or configuration where necessary.</p>
<h3 id="bd-2-third-party-dependencies" data-id="2-third-party-dependencies">6.2. Third-Party Dependencies</h3>
<div class="bd-anchor" id="2-third-party-dependencies"></div>
<p>The application may also depend on libraries that integrate with Spring Framework. These libraries may have their own compatibility requirements. After upgrading, we should test components such as security, database access, web endpoints, and other Spring-based integrations to ensure that they support the upgraded environment.</p>
<h2 id="bd-best-practices" data-id="best-practices">7. Best Practices</h2>
<div class="bd-anchor" id="best-practices"></div>
<p>When upgrading the Spring Framework in a Spring Boot application, we should follow a few practices to keep the upgrade manageable and maintainable:</p>
<ul>
<li><strong>Prefer upgrading Spring Boot</strong>: We should choose a Spring Boot release that manages the required Spring Framework version. This keeps related dependencies aligned.</li>
<li><strong>Avoid unnecessary overrides</strong>: We shouldn&#8217;t manually specify a Spring Framework version when the selected Spring Boot release already provides the required version.</li>
<li><strong>Keep Spring Framework modules aligned</strong>: We shouldn&#8217;t specify different versions for individual Spring Framework modules, as this can introduce compatibility problems.</li>
</ul>
<p>By sticking to the suggestions above, we can mitigate many of the issues that still persist despite the automatic dependency management.</p>
<h2 id="bd-conclusion" data-id="conclusion">8. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored how Spring Boot manages Spring Framework versions through its dependency-management system. Since Spring Boot provides compatible versions of Spring Framework modules, upgrading the Framework usually starts with selecting a Spring Boot release that manages the required version. This approach keeps the Framework modules aligned and avoids unnecessary dependency overrides.</p>
<p>Also, we used Maven to check the resolved Framework version and investigate unexpected dependency versions through the effective POM and dependency tree. Finally, we reviewed release changes, third-party dependencies, and application behavior after the upgrade. Together, these steps provide a structured way to upgrade the Spring Framework while keeping dependency resolution consistent and the application compatible.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-upgrade-spring-framework-version">Upgrading Spring Framework Version in Spring Boot</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/969518291/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969518291/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969518291/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Boot-Featured-Image-02-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969518291/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969518291/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969518291/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-boot-upgrade-spring-framework-version#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-boot-upgrade-spring-framework-version/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/969518291/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Boot-Featured-Image-02-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-solon-quick-tutorial</feedburner:origLink>
		<title>Introduction to Solon</title>
		<link>https://feeds.feedblitz.com/~/969398114/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/969398114/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Francesco Galgani]]></dc:creator>
		<pubDate>Mon, 21 Sep 2026 04:05:19 +0000</pubDate>
				<category><![CDATA[Java Web]]></category>
		<category><![CDATA[H2]]></category>
		<category><![CDATA[MyBatis]]></category>
		<category><![CDATA[popular]]></category>
		<category><![CDATA[REST Basics]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-solon-quick-tutorial</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn the fundamentals of Solon, including its architecture, dependency injection, configuration, HTTP handling, persistence, and testing, and see them in practice by building a REST API with MyBatis-Flex and H2.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/969398114/0/baeldung">Introduction to Solon</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969398114/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969398114/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969398114/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969398114/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969398114/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-solon-quick-tutorial#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-solon-quick-tutorial/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://solon.noear.org/article/about">Solon</a> is an enterprise Java framework built independently of Spring.</p>
<p>In this tutorial, we&#8217;ll introduce Solon theoretically and in practice. First, we&#8217;ll start with a greeting endpoint and extend it into a REST API that stores tasks in an H2 database.</p>
<p>In particular, we&#8217;ll use Solon 4.1.0, Java 21, and Maven 3.9.16, with MyBatis-Flex 1.11.8 for persistence. Along the way, we&#8217;ll compare configuration, dependency injection, and HTTP handling with familiar Spring Boot concepts, then test the API through real HTTP requests.</p>
<h2 id="bd-what-is-solon" data-id="what-is-solon">2. What Is Solon</h2>
<div class="bd-anchor" id="what-is-solon"></div>
<p>Solon separates its core application services from optional integrations. This gives us control over which features the application loads.</p>
<h3 id="bd-1-core-principles-and-architecture" data-id="1-core-principles-and-architecture">2.1. Core Principles and Architecture</h3>
<div class="bd-anchor" id="1-core-principles-and-architecture"></div>
<p>The project emphasizes restrained design, efficiency, openness, and an extensible ecosystem. Its core provides <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring">dependency injection</a>, aspect-oriented programming, and request routing. Plugins add capabilities such as database access and JSON serialization.</p>
<p><strong>Solon doesn&#8217;t require a Servlet container or a Java EE application server</strong>. HTTP adapters connect its request abstractions to a server. The example here uses Smart-HTTP through the <em>solon-web</em> bundle, although Servlet adapters are also available.</p>
<h3 id="bd-2-the-solon-ecosystem" data-id="2-the-solon-ecosystem">2.2. The Solon Ecosystem</h3>
<div class="bd-anchor" id="2-the-solon-ecosystem"></div>
<p>The main <em>solon</em> project is complemented by <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/opensolon/solon-cloud"><em>solon-cloud</em></a> for distributed services and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/opensolon/solon-ai"><em>solon-ai</em></a> for AI applications. There are also other related projects:</p>
<ul>
<li><em>solon-flow</em> for workflows</li>
<li><em>solon-expression</em> for expression evaluation</li>
<li>and <em>solon-admin</em> for application administration</li>
</ul>
<p>The <em>solon-java17</em> and <em>solon-java25</em> projects host implementations targeting newer Java baselines. The wider plugin ecosystem includes <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/mybatis-flex-guide">MyBatis-Flex</a>, JPA, Redis, Sa-Token, Nacos, and gateway integrations.</p>
<h3 id="bd-3-when-solon-is-a-good-fit" data-id="3-when-solon-is-a-good-fit">2.3. When Solon Is a Good Fit</h3>
<div class="bd-anchor" id="3-when-solon-is-a-good-fit"></div>
<p>Solon&#8217;s modular design makes it worth evaluating for microservices, serverless functions, and applications with constrained resources, including embedded or IoT workloads. Its AI modules also offer a starting point for applications that call language models.</p>
<p>These are candidates for evaluation, rather than performance guarantees. For a service with demanding concurrency requirements, we need to benchmark the actual workload with the required plugins.</p>
<p>Spring Boot may remain the more practical choice when a team already relies on its integrations and operating procedures. Adopting Solon means learning a different set of annotations, configuration conventions, and extension points.</p>
<h2 id="bd-first-application" data-id="first-application">3. First Application</h2>
<div class="bd-anchor" id="first-application"></div>
<p>Let&#8217;s create a Maven project with the standard <em>src/main/java</em> directory. Further, <strong>we place <em>App</em> in <em>com.baeldung.solon</em> and the controllers in its <em>web</em> subpackage</strong>.</p>
<h3 id="bd-1-prerequisites" data-id="1-prerequisites">3.1. Prerequisites</h3>
<div class="bd-anchor" id="1-prerequisites"></div>
<p>The test environment uses OpenJDK 21 and Maven 3.9.16. The POM excerpt we show assumes the Baeldung repository layout and its shared <em>parent-modules</em> POM.</p>
<p>So, let&#8217;s import <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://central.sonatype.com/artifact/org.noear/solon-parent/4.1.0"><em>solon-parent</em> 4.1.0</a> as a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-maven-bom">BOM</a> and add <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://central.sonatype.com/artifact/org.noear/solon-web/4.1.0"><em>solon-web</em></a> to <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;parent&gt;
    &lt;groupId&gt;com.baeldung&lt;/groupId&gt;
    &lt;artifactId&gt;parent-modules&lt;/artifactId&gt;
    &lt;version&gt;1.0.0-SNAPSHOT&lt;/version&gt;
&lt;/parent&gt;
&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.noear&lt;/groupId&gt;
            &lt;artifactId&gt;solon-parent&lt;/artifactId&gt;
            &lt;version&gt;4.1.0&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;
&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.noear&lt;/groupId&gt;
        &lt;artifactId&gt;solon-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
&lt;properties&gt;
    &lt;java.version&gt;21&lt;/java.version&gt;
    &lt;maven.compiler.parameters&gt;true&lt;/maven.compiler.parameters&gt;
&lt;/properties&gt;</code></pre>
<p>The BOM manages matching Solon dependency versions. The compiler retains method parameter names so Solon can bind request parameters by name. The web bundle brings in <em>solon-lib</em>, <em>solon-server-smarthttp</em>, and <em>solon-serialization-snack4</em>, among other plugins.</p>
<p>Solon declares support for Java 8 through Java 26, which can also make it relevant to legacy applications. However, individual integrations can require newer Java versions. This project specifically targets Java 21.</p>
<h3 id="bd-2-hello-world" data-id="2-hello-world">3.2. Hello World</h3>
<div class="bd-anchor" id="2-hello-world"></div>
<p>Let&#8217;s add an entry point that starts the application and discovers components in its package and subpackages:</p>
<pre><code class="language-java">public class App {
    public static void main(String[] args) {
        Solon.start(App.class, args);
    }
}</code></pre>
<p>Spring Boot commonly combines <em>SpringApplication.run()</em> with <em>@SpringBootApplication</em>. Here, <em>Solon.start()</em> initializes the container and available plugins without that annotation.</p>
<p>Next, let&#8217;s expose a greeting that accepts an optional query parameter:</p>
<pre><code class="language-java">@Controller
public class DemoController {
    @Get
    @Mapping("/hello")
    public String hello(@Param(defaultValue = "World") String name) {
        return "Hello, " + name + "!";
    }
}</code></pre>
<p><em>@Mapping</em> defines the path, while <em>@Get</em> restricts the HTTP method. <em>@Param</em> supplies a default when <em>name</em> is absent. In particular, these annotations come from <em>org.noear.solon.annotation</em>.</p>
<p>For this endpoint, Solon writes the returned string to the response body as plain text. A comparable Spring REST endpoint typically uses <em>@RestController</em> with <em>@GetMapping</em>.</p>
<h3 id="bd-3-running-the-application" data-id="3-running-the-application">3.3. Running the Application</h3>
<div class="bd-anchor" id="3-running-the-application"></div>
<p>To run the entry point from Maven, let&#8217;s configure <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://central.sonatype.com/artifact/org.codehaus.mojo/exec-maven-plugin/3.6.4"><em>exec-maven-plugin</em> 3.6.4</a> under <em>build/plugins</em>:</p>
<pre><code class="language-xml">&lt;plugin&gt;
    &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;
    &lt;artifactId&gt;exec-maven-plugin&lt;/artifactId&gt;
    &lt;version&gt;3.6.4&lt;/version&gt;
    &lt;configuration&gt;
        &lt;mainClass&gt;com.baeldung.solon.App&lt;/mainClass&gt;
    &lt;/configuration&gt;
&lt;/plugin&gt;</code></pre>
<p>With the plugin in place, we can compile and start the application:</p>
<pre><code class="language-bash">mvn compile exec:java</code></pre>
<p>From another terminal, let&#8217;s call the endpoint:</p>
<pre><code class="language-bash">curl 'http://localhost:8080/hello?name=Baeldung'</code></pre>
<p>Thus, the response confirms that the query parameter reaches the controller:</p>
<pre><code class="language-plaintext">Hello, Baeldung!</code></pre>
<p>Calling <em>/hello</em> without the parameter returns <em>Hello, World!</em>.</p>
<h2 id="bd-restful-api-example" data-id="restful-api-example">4. RESTful API Example</h2>
<div class="bd-anchor" id="restful-api-example"></div>
<p>The API we&#8217;re constructing can create, list, update, and delete tasks. Specifically, each task has a generated ID, a title, and a completion flag.</p>
<h3 id="bd-1-dependencies" data-id="1-dependencies">4.1. Dependencies</h3>
<div class="bd-anchor" id="1-dependencies"></div>
<p>Let&#8217;s add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://central.sonatype.com/artifact/com.mybatis-flex/mybatis-flex-solon-plugin/1.11.8">MyBatis-Flex Solon plugin 1.11.8</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://central.sonatype.com/artifact/com.zaxxer/HikariCP/7.1.0">HikariCP 7.1.0</a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://central.sonatype.com/artifact/com.h2database/h2/2.5.250">H2 2.5.250</a> to the dependencies:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.mybatis-flex&lt;/groupId&gt;
    &lt;artifactId&gt;mybatis-flex-solon-plugin&lt;/artifactId&gt;
    &lt;version&gt;1.11.8&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.zaxxer&lt;/groupId&gt;
    &lt;artifactId&gt;HikariCP&lt;/artifactId&gt;
    &lt;version&gt;7.1.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.h2database&lt;/groupId&gt;
    &lt;artifactId&gt;h2&lt;/artifactId&gt;
    &lt;version&gt;2.5.250&lt;/version&gt;
    &lt;scope&gt;runtime&lt;/scope&gt;
&lt;/dependency&gt;</code></pre>
<p>Next, we continue with the configuration.</p>
<h3 id="bd-2-configuration-properties" data-id="2-configuration-properties">4.2. Configuration Properties</h3>
<div class="bd-anchor" id="2-configuration-properties"></div>
<p>Solon reads application settings from <em>src/main/resources/app.yml</em>. So, let&#8217;s name the application, choose its port, and configure a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/hikaricp">HikariCP connection pool</a> and mapper discovery:</p>
<pre><code class="language-yaml">solon.app:
  name: task-api
  group: examples
server.port: 8080
solon.dataSources:
  tasks!:
    class: com.zaxxer.hikari.HikariDataSource
    jdbcUrl: jdbc:h2:mem:tasks
    username: sa
    password: ""
    maximumPoolSize: 4
mybatisFlex:
  mapperLocations:
    - com.baeldung.solon.persistence</code></pre>
<p>The <em>!</em> suffix registers the datasource by type as well as by the name <em>tasks</em>. Here, we use that name when injecting it. The <em>mapperLocations</em> list identifies the package containing the mapper interface.</p>
<p><strong>Settings from later configuration layers override earlier values for the same key</strong>. The <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://solon.noear.org/article/301">six-layer configuration model</a> places application files at the lowest priority:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/09/solon-configuration-precedence.png"><img decoding="async" class="aligncenter size-full wp-image-266596" src="https://www.baeldung.com/wp-content/uploads/2026/09/solon-configuration-precedence.png" alt="Solon Configuration Precedence" /></a></p>
<p>For example, a startup argument overrides the port in <em>app.yml</em>:</p>
<pre><code class="language-bash">mvn compile exec:java -Dexec.args="--server.port=8081"</code></pre>
<p>The cloud layer only applies when the relevant plugins are configured. While Spring Boot users are familiar with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-properties-dynamic-update">externalized configuration</a>, Solon uses <em>app.properties</em> or <em>app.yml</em> instead of the Spring Boot <em>application.properties</em> or <em>application.yml</em>.</p>
<p>Critically, <strong>the H2 database lives in memory only</strong>. Its contents disappear when the application stops.</p>
<h3 id="bd-3-architectural-layers" data-id="3-architectural-layers">4.3. Architectural Layers</h3>
<div class="bd-anchor" id="3-architectural-layers"></div>
<p>Let&#8217;s organize the code around three responsibilities:</p>
<ul>
<li><em>TaskController</em> handles HTTP requests and responses</li>
<li><em>TaskService</em> validates titles and coordinates database operations</li>
<li><em>TaskMapper</em> performs persistence operations through MyBatis-Flex</li>
</ul>
<p>The Solon <em>@Component</em> annotation registers the service as a managed bean. Spring provides additional <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-component-repository-service">role-specific annotations</a>, such as <em>@Service</em> and <em>@Repository</em>. However, <strong>neither framework requires this layered organization</strong>.</p>
<p>In addition, we can connect the objects with <em>@Inject</em>, which fills a role similar to that of Spring <em>@Autowired</em>.</p>
<h3 id="bd-4-presentation-layer" data-id="4-presentation-layer">4.4. Presentation Layer</h3>
<div class="bd-anchor" id="4-presentation-layer"></div>
<p>A <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-record-keyword">record</a> defines the fields accepted in a request body:</p>
<pre><code class="language-java">public record TaskRequest(String title, Boolean completed) {
}</code></pre>
<p>Using <em>Boolean</em> enables the completion flag to be <em>null</em>. Conversely, creating a task always sets it to <em>false</em>. On the other hand, updating a task treats an absent or <em>null</em> flag as <em>false</em>.</p>
<p>Let&#8217;s register a controller under <em>/tasks</em> and inject the service. Its creation endpoint binds JSON with <em>@Body</em>:</p>
<pre><code class="language-java">@Controller
@Mapping("/tasks")
public class TaskController {
    @Inject
    private TaskService taskService;
    @Post
    @Mapping
    public Task create(@Body TaskRequest request, Context context) {
        Task task = taskService.create(request.title());
        context.status(201);
        context.headerSet("Location", "/tasks/" + task.getId());
        return task;
    }
}</code></pre>
<p><strong>Returning a <em>Task</em> lets the Snack4 plugin serialize the response as JSON</strong>. Furthermore, the endpoint sets status <em>201</em> and a <em>Location</em> header pointing to the new resource.</p>
<p>Inside the same controller, the update method combines a path parameter with a JSON body:</p>
<pre><code class="language-java">@Put
@Mapping("/{id}")
public Task update(long id, @Body TaskRequest request) {
    return taskService.update(id, request.title(),
      Boolean.TRUE.equals(request.completed()));
}</code></pre>
<p>Solon binds <em>{id}</em> to the parameter named <em>id</em>. <strong>The complete controller also provides several mappings</strong>:</p>
<ul>
<li><em>GET /tasks</em> returns all tasks, ordered by ID</li>
<li><em>GET /tasks/{id}</em> returns one task</li>
<li><em>DELETE /tasks/{id}</em> deletes a task and returns <em>204</em> with an empty body</li>
</ul>
<p>These are fairly standard endpoints, so the structure and framework remain the focus instead of the implementation.</p>
<h3 id="bd-5-business-logic-layer" data-id="5-business-logic-layer">4.5. Business Logic Layer</h3>
<div class="bd-anchor" id="5-business-logic-layer"></div>
<p>Let&#8217;s annotate <em>TaskService</em> with <em>@Component</em>. It receives a mapper and creates tasks within a transaction:</p>
<pre><code class="language-java">@Inject
TaskMapper taskMapper;
@Transaction
public Task create(String title) {
    Task task = new Task();
    task.setTitle(normalizeTitle(title));
    task.setCompleted(false);
    taskMapper.insert(task);
    return task;
}</code></pre>
<p>Here, <em>@Transaction</em> comes from <em>org.noear.solon.data.annotation</em>. <strong>The persistence plugin integrates mapper operations with the Solon transaction management</strong>.</p>
<p>The <em>normalizeTitle()</em> helper strips surrounding whitespace and rejects blank titles or titles longer than 200 characters. Invalid input raises <em>IllegalArgumentException</em>.</p>
<p>Updates first load the existing task, preserving its ID. Missing tasks raise <em>NoSuchElementException</em>. Deletion checks the number of affected rows so deleting an unknown ID produces the same error.</p>
<p>The application <em>ApiErrorFilter</em> translates those exceptions into JSON responses with status <em>400</em> or <em>404</em>. This keeps HTTP status handling out of the service.</p>
<h3 id="bd-6-persistence-layer" data-id="6-persistence-layer">4.6. Persistence Layer</h3>
<div class="bd-anchor" id="6-persistence-layer"></div>
<p>The <em>Task</em> entity is a mutable POJO with standard getters and setters. In addition, its MyBatis-Flex mapping uses an automatically generated key:</p>
<pre><code class="language-java">@Table("tasks")
public class Task {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String title;
    private boolean completed;
    // getters and setters
}</code></pre>
<p>MyBatis-Flex writes the generated ID back into the entity after insertion. Thus, the controller has the ID needed for the response and its <em>Location</em> header.</p>
<p>The mapper inherits the standard database operations:</p>
<pre><code class="language-java">public interface TaskMapper extends BaseMapper&lt;Task&gt; {
}</code></pre>
<p>The plugin discovers this interface through the <em>mybatisFlex.mapperLocations</em> configuration and makes it available for injection. We don&#8217;t need to implement its insert, update, or delete methods.</p>
<h3 id="bd-7-initializing-the-database" data-id="7-initializing-the-database">4.7. Initializing the Database</h3>
<div class="bd-anchor" id="7-initializing-the-database"></div>
<p>Let&#8217;s save the table definition in <em>src/main/resources/schema.sql</em>:</p>
<pre><code class="language-sql">CREATE TABLE IF NOT EXISTS tasks (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    completed BOOLEAN NOT NULL DEFAULT FALSE
);</code></pre>
<p>The <em>DatabaseInitializer</em> is another <em>@Component</em>. In this case, it receives the named datasource and executes a script in its <em>@Init</em> method:</p>
<pre><code class="language-java">@Inject("tasks")
private DataSource dataSource;
@Init
public void initialize() throws SQLException, IOException {
    String schema = ResourceUtil.getResourceAsString("schema.sql");
    try (Connection connection = dataSource.getConnection();
      Statement statement = connection.createStatement()) {
        statement.execute(schema);
    }
}</code></pre>
<p>Solon invokes the initialization method after dependency injection. The schema is created explicitly by this component, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-try-with-resources">try-with-resources</a> closes the JDBC resources afterward.</p>
<h3 id="bd-8-trying-the-api" data-id="8-trying-the-api">4.8. Trying the API</h3>
<div class="bd-anchor" id="8-trying-the-api"></div>
<p>After restarting the completed application on port <em>8080</em>, let&#8217;s create a task:</p>
<pre><code class="language-bash">curl -i -X POST http://localhost:8080/tasks \
  -H 'Content-Type: application/json' \
  -d '{"title":"Learn Solon"}'</code></pre>
<p>On a fresh database, the response has status <em>201 Created</em>, a <em>Location: /tasks/1</em> header, and this JSON body:</p>
<pre><code class="language-json">{"id":1,"title":"Learn Solon","completed":false}</code></pre>
<p>Using the returned ID, let&#8217;s read, update, and delete the task:</p>
<pre><code class="language-bash">curl http://localhost:8080/tasks/1
curl -X PUT http://localhost:8080/tasks/1 \
  -H 'Content-Type: application/json' \
  -d '{"title":"Learn Solon REST APIs","completed":true}'
curl -i -X DELETE http://localhost:8080/tasks/1</code></pre>
<p>The update returns the changed task. Deletion returns <em>204 No Content</em>, and a subsequent read returns <em>404</em>. A blank title produces <em>400</em> with a JSON error message.</p>
<h3 id="bd-9-api-testing" data-id="9-api-testing">4.9. API Testing</h3>
<div class="bd-anchor" id="9-api-testing"></div>
<p>For automated tests, we add <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://central.sonatype.com/artifact/org.noear/solon-test/4.1.0"><em>solon-test</em></a>, which includes <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/junit-5">JUnit 5</a> in this Solon version:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.noear&lt;/groupId&gt;
    &lt;artifactId&gt;solon-test&lt;/artifactId&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;</code></pre>
<p>The example module uses JUnit 5.14.4 with Surefire 3.5.5 and inherits the test selection rules from the Baeldung shared parent POM.</p>
<p>Next, let&#8217;s enable the HTTP server and select the test environment:</p>
<pre><code class="language-java">@SolonTest(value = App.class, env = "test", enableHttp = true,
  delay = 0, debug = false)
public class TaskApiIntegrationTest {
    @Inject("${server.port}")
    private int port;
    @Inject
    private TaskService taskService;
}</code></pre>
<p>Solon requires a public test class here. In the repository, <em>app-test.yml</em> selects a separate H2 database. The Maven <em>integration</em> profile reserves an available port and passes it as <em>server.port</em>.</p>
<p>The <em>request()</em> helper uses the JDK <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-9-http-client"><em>HttpClient</em></a> to contact that port. This test therefore checks the actual HTTP response and the database state:</p>
<pre><code class="language-java">@Test
void givenBlankTitle_whenCreatingTask_thenReturnBadRequestWithoutPersisting() throws Exception {
    HttpResponse&lt;String&gt; response = request("POST", "/tasks", "{\"title\":\" \"}");
    assertEquals(400, response.statusCode());
    assertEquals("Title must not be blank",
      ONode.ofJson(response.body()).get("message").getString());
    assertTrue(taskService.findAll().isEmpty());
}</code></pre>
<p>The integration suite also checks greetings, missing IDs, and a complete create-read-update-delete sequence. Let&#8217;s run unit tests and integration tests separately from the module directory:</p>
<pre><code class="language-bash">mvn clean install -Pdefault
mvn clean install -Pintegration</code></pre>
<p>Let&#8217;s see the integration report:</p>
<pre><code class="language-plaintext">Tests run: 5, Failures: 0, Errors: 0, Skipped: 0</code></pre>
<p>Thus, all tests pass without issues.</p>
<h3 id="bd-10-transaction-rollback" data-id="10-transaction-rollback">4.10. Transaction Rollback</h3>
<div class="bd-anchor" id="10-transaction-rollback"></div>
<p>Finally, let&#8217;s demonstrate <em>@Rollback</em> with a separate service-level test:</p>
<pre><code class="language-java">@Test
@Rollback
public void whenCreatingTaskWithinTransaction_thenReadUncommittedTask() {
    Task task = taskService.create("Temporary task");
    assertEquals("Temporary task", taskService.findById(task.getId()).getTitle());
}</code></pre>
<p>The annotated method is public so the Solon proxy can intercept it. The <em>@AfterEach</em> hook checks that the database is empty before cleanup, verifying the rollback.</p>
<p>Notably, <strong>we don&#8217;t apply <em>@Rollback</em> to the test that exercises CRUD across multiple HTTP requests</strong>. When <em>@Rollback</em> is active, Solon installs an interceptor that rolls back the transaction of each HTTP request separately. A task created by one request is therefore unavailable to the next.</p>
<p>The HTTP workflow test therefore runs without <em>@Rollback</em> and uses explicit row cleanup before and after each test. Changes persist across requests within a test, while cleanup keeps tests isolated from one another.</p>
<h2 id="bd-conclusion" data-id="conclusion">5. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we built and tested a Solon REST API with MyBatis-Flex and H2. Further, we saw how its annotations, configuration layers, and plugins supported request handling and persistence, including the difference between testing an HTTP workflow and verifying transaction rollback.</p>
<p>As always, the full source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/blob/master/solon-introduction">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-solon-quick-tutorial">Introduction to Solon</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/969398114/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969398114/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969398114/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969398114/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969398114/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969398114/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-solon-quick-tutorial#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-solon-quick-tutorial/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/969398114/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-664</feedburner:origLink>
		<title>Java Weekly, Issue 664</title>
		<link>https://feeds.feedblitz.com/~/969333680/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/969333680/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Sun, 20 Sep 2026 07:55:45 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204966</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>A better spec-driven development and the brain as example architecture.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/969333680/0/baeldung">Java Weekly, Issue 664</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969333680/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969333680/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969333680/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969333680/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969333680/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-664#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-664/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4.jpg 952w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 style="text-align: left;" id="bd-spring-and-java" data-id="spring-and-java">1.<strong> Spring and Java</strong></h2>
<div class="bd-anchor" id="spring-and-java"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/15/jdk-27-available/">&gt;&gt; The Arrival of Java 27!</a></strong> [<span style="color: #993300;">inside.java</span>]</p>
<p>Java 27 is here with a lot of goodies &#8211; post-quantum TLS, G1 as the default collector everywhere, compact object headers by default, and improvements across concurrency, JFR, and the Vector API.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/16/jdk27-security-enhancements/" target="_blank" rel="noopener"><strong>JDK 27 Security Enhancements</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/09/logpoints-walkthrough/" target="_blank" rel="noopener"><strong>Logpoints Walkthrough</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/09/java-27-in-intellij-idea/" target="_blank" rel="noopener"><strong>Java 27 in IntelliJ IDEA</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/java21/" target="_blank" rel="noopener"><strong>Java 21: the foundation for Quarkus 4</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://lucumr.pocoo.org/2026/9/14/interpreting-pangram/" target="_blank" rel="noopener"><strong>Interpreting Pangram</strong></a> [<span style="color: #800000;">pocoo.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://nurkiewicz.com/2026/09/generating-java-bytecode-write-yourself-a-compiler.html" target="_blank" rel="noopener"><strong>Generating Java bytecode: Write yourself a compiler, Part V</strong></a> [<span style="color: #800000;">nurkiewicz.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://lucumr.pocoo.org/2026/9/12/pdoom/" target="_blank" rel="noopener"><strong>P(doom)</strong></a> [<span style="color: #800000;">pocoo.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~blog.code-cop.org/2026/09/my-writing-and-genai.html" target="_blank" rel="noopener"><strong>My Writing and GenAI</strong></a> [<span style="color: #800000;">code-cop.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/never-send-slides/nail-your-narrative.html" target="_blank" rel="noopener"><strong>Nail your narrative</strong></a> [<span style="color: #800000;">martinfowler.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wimdeblauwe.com/blog/2026/09/14/using-multiple-github-accounts-on-one-machine/" target="_blank" rel="noopener"><strong>Using multiple GitHub accounts on one machine</strong></a> [<span style="color: #800000;">wimdeblauwe.com</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/16/java-27-launch/" target="_blank" rel="noopener"><strong>Java 27 Launch Stream</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/13/jdk-27-in-2-min/" target="_blank" rel="noopener"><strong>Java 27 Technically Within 2 Minutes</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/09/10/podcast-069/" target="_blank" rel="noopener"><strong>Episode 69 “Declassifying Java 27” [IJN]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-insights-259-reshapr/" target="_blank" rel="noopener"><strong>Quarkus Insights #259: MCP Servers Without the Boilerplate — Introducing reShapr</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/09/16/spring-office-hours-podcast-S5E23" target="_blank" rel="noopener"><strong>Spring Office Hours Podcast: S5E23 &#8211; Java 27 Release Party with Billy Korando</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/09/10/a-bootiful-podcast-paul-bakker" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Netflix&#8217;s Paul Bakker</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/09/intellij-idea-2026-2-3/" target="_blank" rel="noopener"><strong>IntelliJ IDEA 2026.2.3 Is Out!</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-39-3-released/" target="_blank" rel="noopener"><strong>Quarkus 3.39.3 &#8211; Maintenance release</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.wildfly.org/news/2026/09/10/A2A-Jakarta-1-0-0-Final-is-released/" target="_blank" rel="noopener"><strong>A2A Jakarta 1.0.0.Final is released!</strong></a> [<span style="color: #800000;">wildfly.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/4.5.34" target="_blank" rel="noopener"><strong>Vert.x 4.5.34</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.5.4" target="_blank" rel="noopener"><strong>Elasticsearch 9.5.4</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.4.7" target="_blank" rel="noopener"><strong>9.4.7</strong></a> [<span style="color: #800000;">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.7" target="_blank" rel="noopener"><strong>Zuul 4.1.7</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v4.1.4" target="_blank" rel="noopener"><strong>4.1.4</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.2" target="_blank" rel="noopener"><strong>Micronaut Core 5.2.2</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.1" target="_blank" rel="noopener"><strong>5.2.1</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.1.15" target="_blank" rel="noopener"><strong>5.1.15</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.28" target="_blank" rel="noopener"><strong>4.10.28</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v3.10.12" target="_blank" rel="noopener"><strong>3.10.12</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v5.2.0" target="_blank" rel="noopener"><strong>5.2.0</strong></a> [<span style="color: #800000;">github.com/micronaut-projects</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/4.5.5" target="_blank" rel="noopener"><strong>Helidon 4.5.5</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/helidon-io/helidon/releases/tag/3.2.21" target="_blank" rel="noopener"><strong>3.2.21</strong></a> [<span style="color: #800000;">github.com/helidon-io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.22.1" target="_blank" rel="noopener"><strong>Apache Camel 4.22.1</strong></a> [<span style="color: #800000;">github.com/apache</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/payara/Payara/releases/tag/payara-server-7.2026.9" target="_blank" rel="noopener"><strong>Azul Payara Community 7.2026.9</strong></a> [<span style="color: #800000;">github.com/payara</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">2.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.benjoffe.com/fast-time-of-day">&gt;&gt; A faster way to convert a timestamp ➜ Hour, Min, Sec</a></strong> [<span style="color: #993300;">benjoffe.com</span>]</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-664">Java Weekly, Issue 664</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/969333680/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969333680/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969333680/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969333680/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969333680/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969333680/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-weekly-664#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-weekly-664/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/969333680/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-anthropic-claude-prompt-cache</feedburner:origLink>
		<title>Prompt Caching Support in Spring AI with Anthropic Claude</title>
		<link>https://feeds.feedblitz.com/~/969174386/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/969174386/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Stelios Anastasakis]]></dc:creator>
		<pubDate>Wed, 16 Sep 2026 07:49:56 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Anthropic]]></category>
		<category><![CDATA[Spring AI ChatClient]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=204905</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how prompt caching works, the limitations for different Claude models, and how to use it in Spring AI.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/969174386/0/baeldung">Prompt Caching Support in Spring AI with Anthropic Claude</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969174386/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969174386/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969174386/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969174386/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969174386/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-anthropic-claude-prompt-cache#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-anthropic-claude-prompt-cache/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>When working with large prompts, repeatedly sending the same context to the model can increase both latency and costs. This becomes especially noticeable when applications reuse large system instructions, documents, or conversation context across requests.</p>
<p>Prompt Caching in <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-anthropic-agent-skills">Spring AI with Anthropic</a> addresses this by allowing frequently reused parts of a prompt to be cached and reused across requests, reducing the amount of work the model needs to process each time. This can improve response latency while lowering input-token costs.</p>
<p>In this tutorial, we&#8217;ll explain how prompt caching works, the limitations and requirements for different Claude models, and how to use it with Spring AI. We&#8217;ll also cover the main configuration options and practical considerations when applying caching in an application.</p>
<h2 id="bd-dependencies" data-id="dependencies">2. Dependencies</h2>
<div class="bd-anchor" id="dependencies"></div>
<p><strong>Let&#8217;s start by defining the minimum dependencies possible for demonstrating the Prompt Caching in Spring AI</strong>. We&#8217;ll only need <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web">spring-boot-starter-web</a> and the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-anthropic">spring-ai-starter-model-anthropic</a>. The first is for the basic Spring Boot Application and auto-configuration. The latter contains the tools we&#8217;re exploring in this article, including Spring AI. The minimum version for Prompt Caching is 1.0.3, so we can go with:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;version&gt;3.5.13&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-starter-model-anthropic&lt;/artifactId&gt;
    &lt;version&gt;2.0.1&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-3-prompt-caching" data-id="3-prompt-caching"> 3. Prompt Caching</h2>
<div class="bd-anchor" id="3-prompt-caching"></div>
<p>Let&#8217;s start by understanding what Prompt Caching is. <strong>Prompt Caching allows Claude to reuse a previously processed prefix of a prompt instead of processing the same content from scratch on every request. </strong>This is particularly useful for large system instructions, tool definitions, documents, and other context that remains unchanged across requests.</p>
<p>The main benefit we get is reduced latency and input-token cost. Anthropic currently charges cache reads at 10% of the regular input-token price for most Claude models. While the initial cache write costs 25% more than the base input price for a 5-minute cache, the savings increase as the same prompt prefix is reused.</p>
<p>There are, however, some limitations to consider:</p>
<ul>
<li>the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://platform.claude.com/docs/en/build-with-claude/prompt-caching?utm_source=chatgpt.com#cache-limitations">minimum cacheable prompt size depends on the model</a></li>
<li>the cache uses a limited TTL</li>
<li>we can define only up to four cache breakpoints</li>
<li>changes we make to the cached prefix can invalidate the cache</li>
<li>a cache entry is also only available after the first response begins, which matters when sending concurrent requests</li>
</ul>
<p>Next, we need to understand the Prompt Caching Hierarchy for other concepts, like Strategies, to make sense. <strong>Prompt caching follows the hierarchy: tools → system → messages</strong>. Claude processes these sections in this order, and each level builds on the previous one.</p>
<p><strong>Invalidation also follows this hierarchy</strong>. If we change the tools, this invalidates the tools cache and everything below it. If we change the system prompt, this leaves the tools cache intact but invalidates the system and message caches. When we change messages, only the message cache gets invalidated.</p>
<p>The idea is therefore simple:<strong> keep stable content as high in the hierarchy as possible and frequently changing content as low as possible</strong>. This allows the largest possible portion of the prompt to remain cached between requests.</p>
<h2 id="bd-prompt-caching-strategies" data-id="prompt-caching-strategies">4. Prompt Caching Strategies</h2>
<div class="bd-anchor" id="prompt-caching-strategies"></div>
<p>The hierarchy and invalidation rules naturally lead to different caching strategies. <strong>The goal is to place cache breakpoints where they provide the most reuse while minimizing the amount of content that gets invalidated when something changes.</strong></p>
<p>A cache breakpoint tells Anthropic where to create a cache entry. Each breakpoint caches the prompt content up to that point, following the request hierarchy of tools → system → messages. Anthropic currently allows a maximum of four cache breakpoints per request, so they should be placed deliberately rather than on every possible section.</p>
<p>Spring AI provides five Prompt Caching Strategies through AnthropicCacheStrategy:</p>
<table class="table-styled" style="border-collapse: collapse;width: 100%">
<tbody>
<tr>
<th style="width: 25%">Strategy</th>
<th style="width: 25%">Breakpoints</th>
<th style="width: 25%">Cached Content</th>
<th style="width: 25%">Typical Use Case</th>
</tr>
<tr>
<td style="width: 25%"><strong>NONE</strong></td>
<td style="width: 25%">0</td>
<td style="width: 25%">Nothing</td>
<td style="width: 25%"> One-off requests, testing</td>
</tr>
<tr>
<td style="width: 25%"><strong>SYSTEM_ONLY</strong></td>
<td style="width: 25%">1</td>
<td style="width: 25%">Tools + system message</td>
<td style="width: 25%">Stable system prompts</td>
</tr>
<tr>
<td style="width: 25%"><strong>TOOLS_ONLY</strong></td>
<td style="width: 25%">1</td>
<td style="width: 25%">Tool definitions</td>
<td style="width: 25%">Large, shared tools with dynamic system prompts</td>
</tr>
<tr>
<td style="width: 25%"><strong>SYSTEM_AND_TOOLS</strong></td>
<td style="width: 25%">2</td>
<td style="width: 25%">Tools + system message</td>
<td style="width: 25%">Tools and system prompt need independent caching</td>
</tr>
<tr>
<td style="width: 25%"><strong>CONVERSATION_HISTORY</strong></td>
<td style="width: 25%">1–4 Conversation history</td>
<td style="width: 25%">Conversation history</td>
<td style="width: 25%">Multi-turn conversations</td>
</tr>
</tbody>
</table>
<p>The number and placement of breakpoints also affect invalidation. With a single breakpoint at the system message, for example, a change to the tools invalidates the whole cached prefix. With <em>SYSTEM_AND_TOOLS</em>, Spring AI places separate breakpoints after the tools and system message, allowing the tool cache to remain valid when only the system prompt changes.</p>
<p>Choosing the right strategy therefore depends on how stable each part of the request is. The more frequently a section changes, the more carefully its breakpoint should be separated from stable content.</p>
<h2 id="bd-prompt-caching-in-practice" data-id="prompt-caching-in-practice">5. Prompt Caching In Practice</h2>
<div class="bd-anchor" id="prompt-caching-in-practice"></div>
<p>Last, let&#8217;s put what we&#8217;ve learned so far into practice. First, we&#8217;ll apply the theory in a Spring Boot application, and then we&#8217;ll run some tests to see prompt caching in action.</p>
<h3 id="bd-1-spring-boot-application-with-prompt-caching" data-id="1-spring-boot-application-with-prompt-caching">5.1. Spring Boot Application with Prompt Caching</h3>
<div class="bd-anchor" id="1-spring-boot-application-with-prompt-caching"></div>
<p>As we&#8217;ve seen before, we can <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-anthropics-claude-models">use Spring properties to set up the model</a>:</p>
<pre><code class="language-yaml">spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: claude-sonnet-4-6
          max-tokens: 500
        cache-options:
          strategy: SYSTEM_ONLY</code></pre>
<p>At this moment, <em>claude-sonnet</em> models before 3.5 are no longer available. We set 4-6 and <em>max-tokens</em> to 500 as a guardrail to control the cost, and we&#8217;ll use <em>SYSTEM_ONLY</em> as the <em>strategy</em> in our application. <strong>The minimum cacheable prompt for <em>claude-sonnet-4-6</em> is 1024 tokens</strong>.</p>
<p>Then, all we need is a service using this model:</p>
<pre><code class="language-java">public class ChatWithPromptCachingService {
    private final ChatClient chatClient;
    // ... constructors, etc
    public ChatResponseWithMetadataDto chat(String userMessage, String systemPrompt) {
        ChatResponse response = chatClient
          .prompt()
          .system(systemPrompt)
          .user(userMessage)
          .call()
          .chatClientResponse()
          .chatResponse();
        if (response == null || response.getResult() == null) {
            throw new RuntimeException("Client response has no results");
        }
        return ChatResponseWithMetadataDto.fromChatResponse(response);
    }
}</code></pre>
<p>The <em>chat()</em> method accepts a system prompt and a user message and requests our chat-client. Then, it returns the <em>ChatResponseWithMetadataDto</em>:</p>
<pre><code class="language-java">public record ChatResponseWithMetadataDto(
  String responseText,
  Integer promptTokens,
  Integer completionTokens,
  Long cacheReadInputTokens,
  Long cacheWriteInputTokens) {
    public static ChatResponseWithMetadataDto fromChatResponse(ChatResponse chatResponse) {
        // ... implementation of the mapper
    }
}</code></pre>
<p><em>ChatResponseWithMetadataDto</em> only holds the information we need: <em>responseText</em>, <em>promptTokens</em>, <em>completionTokens</em>, <em>cacheReadInputTokens</em>, and <em>cacheWriteInputTokens</em>.</p>
<h3 id="bd-2-testing-the-prompt-caching-application" data-id="2-testing-the-prompt-caching-application">5.2. Testing the Prompt Caching Application</h3>
<div class="bd-anchor" id="2-testing-the-prompt-caching-application"></div>
<p>To make sure that prompts are truly cached, let&#8217;s first have a test with <em>strategy</em> set to <em>NONE</em> and run the test:</p>
<pre><code class="language-java">@Test
void chat_whenPromptCachingDisabled_returnsResponse() {
    String chatMessage = "hello there";
    ChatResponseWithMetadataDto response = service.chat(chatMessage, PromptsUtils.LONG_SYSTEM_PROMPT);
    assertThat(response).isNotNull();
    assertThat(response.promptTokens()).isGreaterThan(1030);
    assertThat(response.cacheReadInputTokens()).isEqualTo(0);
    response = service.chat(chatMessage + " again", PromptsUtils.LONG_SYSTEM_PROMPT);
    assertThat(response).isNotNull();
    assertThat(response.promptTokens()).isGreaterThan(1030);
    assertThat(response.cacheReadInputTokens()).isEqualTo(0);
}</code></pre>
<p>In this test, we request a dummy message and the default <em>PromptsUtils.LONG_SYSTEM_PROMPT</em>, which is just a bit longer than 1024 tokens. As expected, for caching strategy <em>NONE</em>, we see that the prompt tokens are a full 1030 tokens, and there is no cached token recorded.</p>
<p>Next, let&#8217;s try the same test with the default caching strategy of our application (<em>SYSTEM_ONLY</em>):</p>
<pre><code class="language-java">@Test
void chat_whenPromptCachingEnabled_returnsResponse() {
    String chatMessage = "hello there";
    UUID testId = UUID.randomUUID();
    ChatResponseWithMetadataDto response = service.chat(
      chatMessage,
      PromptsUtils.LONG_SYSTEM_PROMPT + "\nTEST_ID=" + testId);
    assertThat(response).isNotNull();
    assertThat(response.promptTokens()).isLessThan(50);
    assertThat(response.cacheWriteInputTokens()).isGreaterThan(1000);
    assertThat(response.cacheReadInputTokens()).isEqualTo(0);
    response = service.chat(chatMessage + " again", PromptsUtils.LONG_SYSTEM_PROMPT + "\nTEST_ID=" + testId);
    assertThat(response).isNotNull();
    assertThat(response.promptTokens()).isLessThan(50);
    assertThat(response.cacheWriteInputTokens()).isEqualTo(0);
    assertThat(response.cacheReadInputTokens()).isGreaterThan(1000);
}</code></pre>
<p>Now we see Prompt Caching effects! In the first request, the response tells us that <em>cacheWriteInputTokens</em> is over 1000, which means it just wrote to the cache. Moreover, <em>promptTokens</em> is low, just the user message, and <em>cacheReadInputTokens</em> is 0 cause there was no cache hit.</p>
<p>In the second request, <em>promptTokens</em> is low, but this time <em>cacheWriteInputTokens</em> is 0 (no new tokens cached). <em>CacheReadInputTokens</em> is now over 1000, which shows there was a cache hit!</p>
<p>The prompt in this test also uses a <em>TEST_ID</em> to make sure no previous tests will affect the next execution. It&#8217;s a trick to use different caches between test executions.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explained how Prompt Caching Support in Spring AI with Anthropic Claude works. We walked through how it works, configuration options, and the limitations. Then we saw the Prompt Caching Strategies. Finally, we used Spring AI to demonstrate it in practice.</p>
<p>As always, the source code of the examples can be found <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-ai-modules/spring-ai-anthropic">over on GitHub</a>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-anthropic-claude-prompt-cache">Prompt Caching Support in Spring AI with Anthropic Claude</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/969174386/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969174386/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969174386/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969174386/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969174386/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969174386/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/spring-ai-anthropic-claude-prompt-cache#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/spring-ai-anthropic-claude-prompt-cache/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/969174386/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-gson-deserialization-inaccessibleobjectexception</feedburner:origLink>
		<title>Gson Deserialization and the InaccessibleObjectException</title>
		<link>https://feeds.feedblitz.com/~/969124883/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/969124883/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Marcin Buczkowski]]></dc:creator>
		<pubDate>Tue, 15 Sep 2026 05:32:46 +0000</pubDate>
				<category><![CDATA[JSON]]></category>
		<category><![CDATA[Gson]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-gson-deserialization-inaccessibleobjectexception</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to resolve an InaccessibleObjectException when working with Gson and modern Java versions.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/969124883/0/baeldung">Gson Deserialization and the InaccessibleObjectException</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969124883/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969124883/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969124883/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969124883/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969124883/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-gson-deserialization-inaccessibleobjectexception#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-gson-deserialization-inaccessibleobjectexception/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>Starting with version 9, Java places a strong emphasis on encapsulation. The major feature behind this is the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-modularity" target="_blank" rel="noopener">Java Platform Module System (JPMS)</a>. It controls how our own packages are exposed and, at the same time, restricts access to Java&#8217;s internal classes. However, many libraries rely heavily on reflection to access private members. So we need ways to relax these restrictions in a controlled manner.</p>
<p>In this tutorial, we&#8217;ll learn how to avoid the <em>InaccessibleObjectException</em> when <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/gson-deserialization-guide" target="_blank" rel="noopener">deserializing JSON with Gson</a>. We&#8217;ll focus on modular projects and date handling.</p>
<h2 id="bd-gson-status" data-id="gson-status">2. Gson Status</h2>
<div class="bd-anchor" id="gson-status"></div>
<p>Gson offers convenient tools for serializing objects to JSON and deserializing them back, and it relies on accessing private fields via reflection. With strong encapsulation in place, this no longer works seamlessly, either for our own objects or for Java&#8217;s internal classes. The latest Gson versions have made a small move towards public APIs, specifically for <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-record-keyword" target="_blank" rel="noopener">records</a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-8-date-time-intro" target="_blank" rel="noopener">dates</a>.</p>
<p>Gson is currently in maintenance mode. <strong>Therefore, we shouldn&#8217;t expect any new features, only bug fixes and security fixes.</strong></p>
<h2 id="bd-project-setup" data-id="project-setup">3. Project Setup</h2>
<div class="bd-anchor" id="project-setup"></div>
<p>Let&#8217;s examine the <em>pom.xml</em> we&#8217;ll use. We need Java 17 to demonstrate strong encapsulation and for its full support of records. Therefore, we set it in the <em>properties</em> section of <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;maven.compiler.release&gt;17&lt;/maven.compiler.release&gt;</code></pre>
<p>Next, let&#8217;s check the Gson version. We should use the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.google.code.gson/gson" target="_blank" rel="noopener">latest one</a>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.google.code.gson&lt;/groupId&gt;
    &lt;artifactId&gt;gson&lt;/artifactId&gt;
    &lt;version&gt;2.14.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-gson-and-java-modules" data-id="gson-and-java-modules">4. Gson and Java Modules</h2>
<div class="bd-anchor" id="gson-and-java-modules"></div>
<p>Let&#8217;s create a simple Java project named <em>gson-module</em>. <strong>We declare the <em>gson.exception</em> module in the <em>module-info.java</em> file:</strong></p>
<pre><code class="language-java">module gson.exception {
    requires com.google.gson;
    requires org.slf4j;
    exports gson.exception;
}</code></pre>
<p>With the <em>requires</em> statement, we state that our module needs Gson (and SLF4J for logging). Then, we make our own package <em>gson.exception</em> available to other modules with the <em>exports</em> statement.</p>
<p>Now let&#8217;s add some classes that describe a conference. The first is a good old POJO:</p>
<pre><code class="language-java">public class ConferencePojo {
    private String name;
    private int numberOfParticipants;
    // standard setters and getters
}</code></pre>
<p>This object stores the name and number of participants of a conference. It has private fields and public getters and setters.</p>
<p>Next, we prepare an equivalent Java record:</p>
<pre><code class="language-java">public record ConferenceRecord(String name, int numberOfParticipants) {
}</code></pre>
<p>With a record, we don&#8217;t need to declare the fields or write getters and setters.</p>
<h3 id="bd-1-gson-failure-with-pojo" data-id="1-gson-failure-with-pojo">4.1. Gson Failure With POJO</h3>
<div class="bd-anchor" id="1-gson-failure-with-pojo"></div>
<p>Gson easily deserializes a POJO such as <em>ConferencePojo</em> in a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-classpath-vs-modulepath" target="_blank" rel="noopener">classpath project, but not in a module path</a> one. To prove that, we&#8217;ll run the <em>GsonModuleMain</em> application inside our modular project. Let&#8217;s look at its <em>main</em> method:</p>
<pre><code class="language-java">public static void main(String[] args) {
    String moduleName = GsonModuleMain.class.getModule()
      .getName();
    if (moduleName == null) {
        log.info("Mode: [ Class Path ] (Class in the Unnamed Module)");
    } else {
        log.info("Mode: [ Module Path ] - Module name: " + moduleName);
    }
    Gson gson = new Gson();
    String json = "{\"name\":\"Java Conference\"}";
    try {
        ConferencePojo pojo = gson.fromJson(json, ConferencePojo.class);
        log.info("Deserialization successful! Object " + pojo);
    } catch (Exception e) {
        log.error("Expected exception caught!", e);
    }
}</code></pre>
<p>At the beginning, we log whether the JVM actually loaded our class from the module path. Then we try to deserialize the POJO. Let&#8217;s examine the result:</p>
<pre><code class="language-plaintext">10:40:06.766 [main] INFO gson.exception.GsonModuleMain -- Mode: [ Module Path ] - Module name: gson.exception
10:40:06.868 [main] ERROR gson.exception.GsonModuleMain -- Expected exception caught!
com.google.gson.JsonIOException: Failed making field 'gson.exception.ConferencePojo#name' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
See https://github.com/google/gson/blob/main/Troubleshooting.md#reflection-inaccessible-to-module-gson
...
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String gson.exception.ConferencePojo.name accessible: module gson.exception does not "opens gson.exception" to module com.google.gson
...</code></pre>
<p><strong>Gson throws a <em>JsonIOException</em> caused by an <em>InaccessibleObjectException</em>.</strong> From the two messages, we learn what happened and get hints on how to solve the problem. In short, within a modular project, Gson can&#8217;t access the private fields of our class unless we let it.</p>
<h3 id="bd-2-success-with-records" data-id="2-success-with-records">4.2. Success With Records</h3>
<div class="bd-anchor" id="2-success-with-records"></div>
<p>With records, we&#8217;ll get a completely different result. Let&#8217;s use a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/junit-5" target="_blank" rel="noopener">JUnit 5</a> test to demonstrate it:</p>
<pre><code class="language-java">@Test
void givenModularAndExportedPackage_whenDeserializingRecord_thenSuccess() {
    String json = """
        {
            "name": "Java Conference",
            "numberOfParticipants": 150
        }
        """;
    Gson gson = new Gson();
    ConferenceRecord result = assertDoesNotThrow(() -&gt; {
        return gson.fromJson(json, ConferenceRecord.class);
    });
    assertNotNull(result);
    assertEquals("Java Conference", result.name());
}</code></pre>
<p>This time, Gson successfully deserializes the JSON and creates the record. <strong>Since version 2.10, Gson uses a record&#8217;s canonical constructor when instantiating the object, unlike classes, where Gson needs access to private fields.</strong></p>
<h2 id="bd-how-to-deserialize-a-pojo" data-id="how-to-deserialize-a-pojo">5. How to Deserialize a POJO</h2>
<div class="bd-anchor" id="how-to-deserialize-a-pojo"></div>
<p>If we still want to use a POJO, let&#8217;s follow the hints from the exception messages. We have four ways to fix this.</p>
<h3 id="bd-1-make-fields-public" data-id="1-make-fields-public">5.1. Make Fields Public</h3>
<div class="bd-anchor" id="1-make-fields-public"></div>
<p>Let&#8217;s create a copy of <em>ConferencePojo</em> with public fields:</p>
<pre><code class="language-java">public class ConferencePojoPublic {
    public String name;
    public int numberOfParticipants;
}</code></pre>
<p>Now Java lets Gson access the fields with reflection, and deserialization succeeds. However, we lose encapsulation this way.</p>
<h3 id="bd-2-opens-to-expose-private-fields" data-id="2-opens-to-expose-private-fields">5.2. <em>opens</em> to Expose Private Fields</h3>
<div class="bd-anchor" id="2-opens-to-expose-private-fields"></div>
<p>To let Gson work with our private fields, we can open our package to it. Let&#8217;s create a new project, <em>gson-module-opens</em>, and look at its <em>module-info.java</em> file:</p>
<pre><code class="language-java">module gson.exception {
    requires com.google.gson;
    requires org.slf4j;
    opens gson.exception to com.google.gson;
    exports gson.exception;
}</code></pre>
<p><strong>With the statement <em>opens gson.exception to com.google.gson</em>, we allow Gson to access the private fields in the <em>gson.exception</em> package.</strong> Let&#8217;s run our <em>GsonModuleMain</em> application again:</p>
<pre><code class="language-plaintext">19:08:55.369 [main] INFO gson.exception.GsonModuleMain -- Mode: [ Module Path ] - Module name: gson.exception
19:08:55.410 [main] INFO gson.exception.GsonModuleMain -- Deserialization successful! Object gson.exception.ConferencePojo@675d3402</code></pre>
<p>This time, the program doesn&#8217;t throw an exception and deserializes the JSON successfully, even in the modular project.</p>
<h3 id="bd-3-using-the-typeadapter-abstract-class" data-id="3-using-the-typeadapter-abstract-class">5.3. Using the <em>TypeAdapter</em> Abstract Class</h3>
<div class="bd-anchor" id="3-using-the-typeadapter-abstract-class"></div>
<p>If we can&#8217;t use <em>opens</em>, we can extend Gson&#8217;s <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html" target="_blank" rel="noopener"><em>TypeAdapter</em></a> abstract class for our POJO. All we need to do is implement its <em>write</em> and <em>read</em> methods. As we focus on deserialization, let&#8217;s look only at the <em>read</em> method:</p>
<pre><code class="language-java">@Override
public ConferencePojo read(JsonReader in) throws IOException {
    String name = null;
    int numberOfParticipants = 0;
    in.beginObject();
    while (in.hasNext()) {
        String key = in.nextName();
        if ("name".equals(key)) {
            name = in.nextString();
        } else if ("numberOfParticipants".equals(key)) {
            numberOfParticipants = in.nextInt();
        } else {
            in.skipValue();
        }
    }
    in.endObject();
    ConferencePojo result = new ConferencePojo();
    result.setName(name);
    result.setNumberOfParticipants(numberOfParticipants);
    return result;
}</code></pre>
<p>The <em>read</em> method returns an instance of <em>ConferencePojo</em>. It loops over all the entries of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/stream/JsonReader.html" target="_blank" rel="noopener"><em>JsonReader</em></a> and picks out the fields by their JSON names.</p>
<p>Note that we create the <em>ConferencePojo</em> object with its default constructor and fill it in with setters. <strong>This is the crucial point: to use a <em>TypeAdapter</em>, we need a public API for creating the object.</strong> In our case, that&#8217;s the setters, but a constructor, public fields, a factory, or a builder would work as well.</p>
<p>Now let&#8217;s test the adapter:</p>
<pre><code class="language-java">@Test
void whenAdapterForPojo_thenSuccess() {
    Gson gson = new GsonBuilder()
      .registerTypeAdapter(ConferencePojo.class, new ConferencePojoAdapter())
      .create();
    String json = """
        {
            "name": "Java Conference",
            "numberOfParticipants": 100
        }
        """;
    ConferencePojo result = gson.fromJson(json, ConferencePojo.class);
    assertNotNull(result);
    assertEquals("Java Conference", result.getName());
}</code></pre>
<p>Note that we need to register the adapter with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html" target="_blank" rel="noopener"><em>GsonBuilder</em></a>.</p>
<h3 id="bd-4-the---add-opens-argument-as-a-last-resort" data-id="4-the---add-opens-argument-as-a-last-resort">5.4. The <em>&#8211;add-opens</em> Argument as a Last Resort</h3>
<div class="bd-anchor" id="4-the---add-opens-argument-as-a-last-resort"></div>
<p>When we can&#8217;t modify the module&#8217;s source code, we still have one option left. <strong>We can open the package when the program starts by passing the <em>&#8211;add-opens</em> argument to the JVM.</strong> Let&#8217;s look at the argument for our <em>gson-module</em> project, which doesn&#8217;t open its package:</p>
<pre><code class="language-plaintext">--add-opens gson.exception/gson.exception=com.google.gson</code></pre>
<p>Starting from the left, we have the module name <em>gson.exception</em>, as declared in the <em>module-info.java</em> file. Next comes the package we want to open, <em>gson.exception</em>. Finally, we name the module we grant the access to, <em>com.google.gson</em>.</p>
<p>This is the launch-time equivalent of the <em>opens</em> statement, and with it, <em>GsonModuleMain</em> from <em>gson-module</em> deserializes the POJO successfully. However, we should treat it as a last resort, since the argument lives outside the code and we have to repeat it in every launch configuration. Our article on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-illegal-reflective-access" target="_blank" rel="noopener">illegal reflective access</a> covers the background in more detail.</p>
<h2 id="bd-gson-and-time" data-id="gson-and-time">6. Gson and Time</h2>
<div class="bd-anchor" id="gson-and-time"></div>
<p><strong>Besides problems with modular projects, we can run into the same exception when deserializing JSON into the date and time classes from the <em>java.time</em> API, such as <em>LocalDate</em>, <em>Instant</em>, or <em>Duration</em>.</strong> With strong encapsulation and an older Gson version, we get a <em>JsonIOException</em> with the message &#8220;Failed making field &#8216;java.time.LocalDate#year&#8217; accessible&#8221;, again caused by an <em>InaccessibleObjectException</em>. This time, Gson tries to reach the private fields of a Java internal class. Moreover, we can&#8217;t solve this problem with the <em>opens</em> statement, as it can only open our own packages, not Java&#8217;s internals. <strong>However, Gson 2.14.0 has restored support for these classes.</strong></p>
<h3 id="bd-1-localdate-and-structured-json" data-id="1-localdate-and-structured-json">6.1. <em>LocalDate</em> and Structured JSON</h3>
<div class="bd-anchor" id="1-localdate-and-structured-json"></div>
<p>Let&#8217;s add a <em>conferenceStart</em> field in a new <em>ConferencePojoWithDate</em> class. We&#8217;re continuing in the <em>gson-module-opens</em> project, as the other fields are private:</p>
<pre><code class="language-java">public class ConferencePojoWithDate {
    private String name;
    private int numberOfParticipants;
    private LocalDate conferenceStart;
    // standard setters and getters
}</code></pre>
<p>Now let&#8217;s prepare the JSON. We need to use a nested structure for <em>conferenceStart</em>:</p>
<pre><code class="language-json">{
    "name": "Java Conference",
    "numberOfParticipants": 500,
    "conferenceStart": {
        "year": 2026,
        "month": 8,
        "day": 17
    }
}</code></pre>
<p><strong>Note that the keys <em>year</em>, <em>month</em>, and <em>day</em> exactly match the names of the private fields of <em>LocalDate</em>.</strong> The same is true for the other time classes. For example, for <em>Duration</em>, the nested JSON provides the seconds and nanoseconds and looks like this:</p>
<pre><code class="language-json">"duration": {
    "seconds": 7200,
    "nanos": 0
}</code></pre>
<p>Gson kept this naming convention for backward compatibility. It doesn&#8217;t (and isn&#8217;t allowed to) access the private fields of Java&#8217;s internal classes anymore. Instead, the authors implemented built-in <em>TypeAdapter</em>s that use the public <em>java.time</em> API to transfer the data.</p>
<p>We should be especially careful to use the correct field names. If Gson doesn&#8217;t find a matching key in the JSON, it silently falls back to the default value of zero. For <em>LocalDate</em>, a zero month then triggers a completely different exception, a <em>DateTimeException</em> from the date validation in <em>java.time</em>.</p>
<p><strong>Next, we can&#8217;t deserialize a date in ISO format out of the box.</strong> So, a plain string value such as the following fails with a <em>JsonSyntaxException</em>:</p>
<pre><code class="language-json">"conferenceStart":"2026-08-17"</code></pre>
<p>To handle dates encoded as strings, we need to implement a custom <em>TypeAdapter</em>.</p>
<p>Finally, let&#8217;s emphasize that the same rules apply to records: we can deserialize nested time structures out of the box, and we need a <em>TypeAdapter</em> otherwise.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we looked at using Gson in modern, modular Java. <strong>We saw that it no longer works as easily as it did with Java 8 and the classpath.</strong></p>
<p>We examined the problem in a modular project and looked at several ways to make Gson fit the rules of the JPMS. As our first measure, we made the class fields public. Next, we gave Gson access to private fields with the <em>opens</em> statement, or with the equivalent <em>&#8211;add-opens</em> JVM argument.</p>
<p>We then implemented a <em>TypeAdapter</em> that creates the object through its public API. <strong>We also saw that Gson works with records out of the box, using their canonical constructors.</strong></p>
<p>Finally, we checked how Gson handles the classes from the <em>java.time</em> package. <strong>We noted that the newest version can deserialize nested time objects as expected again.</strong></p>
<p><strong>The bottom line is that we have two main ways to avoid the <em>InaccessibleObjectException</em>.</strong> The first is to open our code to Gson&#8217;s reflection, and with the <em>opens</em> statement, we can grant that access to Gson only. The second is to switch to records and stay within the rules of the JPMS. In addition, if we have a public API for creating the object, we can implement a <em>TypeAdapter</em>.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-gson-deserialization-inaccessibleobjectexception">Gson Deserialization and the InaccessibleObjectException</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/969124883/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969124883/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969124883/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-12-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969124883/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969124883/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969124883/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-gson-deserialization-inaccessibleobjectexception#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-gson-deserialization-inaccessibleobjectexception/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/969124883/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-12-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-jackson-fix-no-creator-exception</feedburner:origLink>
		<title>Resolving Exception: Cannot Deserialize From Object Value (No Delegate- Or Property-Based Creator)</title>
		<link>https://feeds.feedblitz.com/~/969076403/0/baeldung</link>
					<comments>https://feeds.feedblitz.com/~/969076403/0/baeldung#respond</comments>
		
		<dc:creator><![CDATA[Martin Blažević]]></dc:creator>
		<pubDate>Mon, 14 Sep 2026 04:21:09 +0000</pubDate>
				<category><![CDATA[Jackson]]></category>
		<category><![CDATA[Exception]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-jackson-fix-no-creator-exception</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2021/09/Java-5-Featured-1024x536.png" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>During JSON deserialization, Jackson may throw an InvalidDefinitionException when it can't determine how to create an instance of the target class. In this article, you'll learn how to fix this in Jackson 2.x and 3.x.</p>
<p>The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/969076403/0/baeldung">Resolving Exception: Cannot Deserialize From Object Value (No Delegate- Or Property-Based Creator)</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.</p><div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969076403/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969076403/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2021%2f09%2fJava-5-Featured-1024x536.png"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969076403/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969076403/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969076403/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-jackson-fix-no-creator-exception#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-jackson-fix-no-creator-exception/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2021/09/Java-5-Featured-1024x536.png" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2021/09/Java-5-Featured-1024x536.png 1024w, https://www.baeldung.com/wp-content/uploads/2021/09/Java-5-Featured-300x157.png 300w, https://www.baeldung.com/wp-content/uploads/2021/09/Java-5-Featured-768x402.png 768w, https://www.baeldung.com/wp-content/uploads/2021/09/Java-5-Featured-100x52.png 100w, https://www.baeldung.com/wp-content/uploads/2021/09/Java-5-Featured.png 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson">Jackson</a> is a Java library commonly used to convert between Java objects and JSON. <strong>During JSON deserialization, Jackson may throw an <em>InvalidDefinitionException</em> when it can&#8217;t determine how to create an instance of the target class.</strong> This can happen even when the JSON structure matches the class fields.</p>
<p>In this tutorial, we&#8217;ll reproduce the exception using Jackson 2.17.2 and explore several ways to resolve it.</p>
<h2 id="bd-reproducing-the-exception" data-id="reproducing-the-exception">2. Reproducing the Exception</h2>
<div class="bd-anchor" id="reproducing-the-exception"></div>
<p>Let&#8217;s start with an immutable <em>Notification</em> class that contains a parameterized constructor:</p>
<pre><code class="language-java">public class Notification {
    private final String message;
    private final int priority;
    public Notification(String message, int priority) {
        this.message = message;
        this.priority = priority;
    }
    // getters
}</code></pre>
<p>Next, let&#8217;s try to deserialize a JSON object into the <em>Notification</em> class:</p>
<pre><code class="language-java">String json = "{\"message\":\"Server maintenance\",\"priority\":2}";
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(json, Notification.class);</code></pre>
<p>Although the JSON properties match the fields in <em>Notification</em>, <strong>Jackson can&#8217;t determine how they map to the constructor parameters</strong>. The constructor doesn&#8217;t specify which JSON property Jackson should use for each parameter.</p>
<p>As a result, deserialization fails with an <em>InvalidDefinitionException</em>:</p>
<pre><code class="language-plaintext">Cannot construct instance of `com.baeldung.invaliddefinitionexception.Notification` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)</code></pre>
<p><strong>The exception indicates that Jackson needs a suitable mechanism to create the <em>Notification</em> instance.</strong> We can address this in several ways.</p>
<h2 id="bd-adding-a-default-constructor" data-id="adding-a-default-constructor">3. Adding a Default Constructor</h2>
<div class="bd-anchor" id="adding-a-default-constructor"></div>
<p>One way to resolve the exception is to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson-exception#111-the-problem-no-default-constructor"><strong>provide a no-argument constructor</strong></a>:</p>
<pre><code class="language-java">public class NotificationWithDefaultConstructor {
    private String message;
    private int priority;
    public NotificationWithDefaultConstructor() {
    }
    // getters and setters
}</code></pre>
<p>By default, <strong>Jackson relies on a no-argument constructor</strong> to instantiate the target class. It can then <strong>populate the object&#8217;s properties during deserialization</strong>:</p>
<pre><code class="language-java">NotificationWithDefaultConstructor notification = objectMapper.readValue(json, NotificationWithDefaultConstructor.class);
assertEquals("Server maintenance", notification.getMessage());
assertEquals(2, notification.getPriority());</code></pre>
<p><strong>This approach works well for mutable classes, where Jackson can set the properties after creating the instance.</strong></p>
<h2 id="bd-defining-a-property-based-creator" data-id="defining-a-property-based-creator">4. Defining a Property-Based Creator</h2>
<div class="bd-anchor" id="defining-a-property-based-creator"></div>
<p>For immutable classes, we can <strong>explicitly define how Jackson should use the parameterized constructor</strong>:</p>
<pre><code class="language-java">import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class NotificationWithCreator {
    private final String message;
    private final int priority;
    @JsonCreator
    public NotificationWithCreator(
      @JsonProperty("message") String message, 
      @JsonProperty("priority") int priority) {
        this.message = message;
        this.priority = priority;
    }
    
    // getters
}</code></pre>
<p>In this case, the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson-deserialization-multi-param-constructor#deserialization-using-jsoncreator"><em>@JsonCreator</em> annotation</a> tells Jackson to use the annotated constructor to create the object. <strong>The <em>@JsonProperty</em> annotations specify which JSON property corresponds to each constructor parameter</strong>.</p>
<p>With the constructor configured as a creator, we can deserialize the JSON while keeping the class immutable:</p>
<pre><code class="language-java">NotificationWithCreator notification = objectMapper.readValue(json, NotificationWithCreator.class);
assertEquals("Server maintenance", notification.getMessage());
assertEquals(2, notification.getPriority());</code></pre>
<p>In addition, we should make sure to import the annotations from the <em>com.fasterxml.jackson.annotation </em>package, which Jackson 3.x also uses. Jackson won&#8217;t recognize creator annotations imported from the legacy <em>org.codehaus.jackson</em> package.</p>
<h3 id="bd-1-factory-methods-as-creators" data-id="1-factory-methods-as-creators">4.1. Factory Methods as Creators</h3>
<div class="bd-anchor" id="1-factory-methods-as-creators"></div>
<p>Factory methods can also be creators. If Jackson doesn&#8217;t recognize our factory method as a creator, it will throw the same <em>InvalidDefinitionException</em>.</p>
<p>To resolve the issue, we annotate the method and its parameters in the same way as we do with parameterized constructors:</p>
<pre><code class="language-java">@JsonCreator
public static NotificationWithFactory create(
  @JsonProperty("message") String message,
  @JsonProperty("priority") int priority) {
    return new NotificationWithFactory(message, priority);
}</code></pre>
<p>Now, this method can also act as a creator.</p>
<h2 id="bd-discovering-constructor-parameter-names" data-id="discovering-constructor-parameter-names">5. Discovering Constructor Parameter Names</h2>
<div class="bd-anchor" id="discovering-constructor-parameter-names"></div>
<p>Finally, instead of adding Jackson annotations to the target class, we can configure Jackson to discover constructor parameter names.</p>
<h3 id="bd-1-using-parameternamesmodule" data-id="1-using-parameternamesmodule">5.1. Using <em>ParameterNamesModule</em></h3>
<div class="bd-anchor" id="1-using-parameternamesmodule"></div>
<p>In Jackson 2.x, we can use <em>ParameterNamesModule</em> to discover constructor parameter names without adding <em>@JsonProperty</em> annotations:</p>
<pre><code class="language-java">ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ParameterNamesModule());
Notification notification = mapper.readValue(json, Notification.class);
assertEquals("Server maintenance", notification.getMessage());
assertEquals(2, notification.getPriority());</code></pre>
<p><strong>We need to compile the class with the Java <em>-parameters</em> option so that the parameter names are available at runtime.</strong> <strong>In Jackson 3.x, this functionality is built into <em>jackson-databind</em> and enabled by default,</strong> so we don&#8217;t need to register a separate module.</p>
<h3 id="bd-2-using-paranamermodule" data-id="2-using-paranamermodule">5.2. Using <em>ParanamerModule</em></h3>
<div class="bd-anchor" id="2-using-paranamermodule"></div>
<p>Another option in Jackson 2.x is to use <em>ParanamerModule</em>. First, we need to add the <em>jackson-module-paranamer</em> dependency to our project:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.module&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-module-paranamer&lt;/artifactId&gt;
    &lt;version&gt;${jackson.version}&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Next, we register <em>ParanamerModule</em> with the <em>ObjectMapper</em>:</p>
<pre><code class="language-java">ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ParanamerModule());
Notification notification =
    mapper.readValue(json, Notification.class);
assertEquals("Server maintenance", notification.getMessage());
assertEquals(2, notification.getPriority());</code></pre>
<p>Once registered, <strong><em>ParanamerModule</em> </strong><span style="margin: 0px;padding: 0px"><strong>gives Jackson the constructor parameter names</strong>, so it can match the </span>message and priority to the corresponding JSON properties.</p>
<p>However, <em>Paranamer</em> is discontinued and supports Jackson 2.x only, which limits this approach for newer projects.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we reproduced a Jackson deserialization exception that occurs when Jackson can&#8217;t determine how to create the target object. We then explored several approaches to resolve it.</p>
<p>These include adding a default constructor, defining a property-based creator with <em>@JsonCreator</em> and <em>@JsonProperty</em>, and letting Jackson discover constructor parameter names. Static factory methods can also serve as creators.</p><p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jackson-fix-no-creator-exception">Resolving Exception: Cannot Deserialize From Object Value (No Delegate- Or Property-Based Creator)</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.</p><Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/969076403/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a href="https://feeds.feedblitz.com/_/28/969076403/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/29/969076403/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2021%2f09%2fJava-5-Featured-1024x536.png"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/24/969076403/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/19/969076403/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a href="https://feeds.feedblitz.com/_/20/969076403/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/java-jackson-fix-no-creator-exception#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/java-jackson-fix-no-creator-exception/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</content:encoded>
					
					<wfw:commentRss>https://feeds.feedblitz.com/~/969076403/0/baeldung/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2021/09/Java-5-Featured-150x150.png</webfeeds:featuredImage></item>
</channel></rss>

