<?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>Tue, 07 Apr 2026 23:06:25 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
<meta xmlns="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
<item>
<feedburner:origLink>https://www.baeldung.com/spring-resttemplate-no-suitable-httpmessageconverter</feedburner:origLink>
		<title>Handle &#8220;No Suitable HttpMessageConverter&#8221; in Spring RestTemplate</title>
		<link>https://feeds.feedblitz.com/~/953488283/0/baeldung~Handle-No-Suitable-HttpMessageConverter-in-Spring-RestTemplate</link>
					<comments>https://feeds.feedblitz.com/~/953488283/0/baeldung~Handle-No-Suitable-HttpMessageConverter-in-Spring-RestTemplate#respond</comments>
		
		<dc:creator><![CDATA[Sudarshan Hiray]]></dc:creator>
		<pubDate>Tue, 07 Apr 2026 23:02:40 +0000</pubDate>
				<category><![CDATA[Spring Web]]></category>
		<category><![CDATA[RestTemplate]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203399</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" fetchpriority="high" /><p>Learn how to prevent one of the most common RestClientExceptions when using RestTeamplate.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/953488283/0/baeldung~Handle-No-Suitable-HttpMessageConverter-in-Spring-RestTemplate">Handle “No Suitable HttpMessageConverter” in Spring RestTemplate</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953488283/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953488283/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953488283/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953488283/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953488283/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-resttemplate-no-suitable-httpmessageconverter#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-resttemplate-no-suitable-httpmessageconverter/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11.jpg 1200w" sizes="(max-width: 580px) 100vw, 580px" /><h2 data-path-to-node="1" id="bd-introduction" data-id="introduction"><b data-path-to-node="1" data-index-in-node="0">1. Introduction</b></h2>
<div class="bd-anchor" id="introduction"></div>
<p data-path-to-node="2">When consuming RESTful services in Spring, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/rest-template"><i data-path-to-node="2" data-index-in-node="43">RestTemplate</i></a> is a reliable workhorse for synchronous <em>HTTP</em> communication. However, one of the most common and frustrating hurdles developers face is the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-restclient#:~:text=By%20default%2C%20when%20RestClient%20encounters%20a%204xx%20or%205xx%20status%20code%20in%20the%20HTTP%20response%2C%20it%20raises%20an%20exception%20that%E2%80%99s%20a%20subclass%20of%20RestClientException."><em>RestClientException</em></a>. Specifically, the following error message:</p>
<pre><code class="language-java">Could not extract response: no suitable HttpMessageConverter found for response type and content type...</code></pre>
<p data-path-to-node="3"><strong>This error typically occurs when the client receives a response that it doesn&#8217;t know how to deserialize into the desired Java object.</strong> In this article, we’ll explore why this happens and how to configure our application to handle non-standard API responses effectively without encountering any such errors.</p>
<h2 data-path-to-node="5" id="bd-project-setup" data-id="project-setup"><b data-path-to-node="5" data-index-in-node="0">2. Project Setup</b></h2>
<div class="bd-anchor" id="project-setup"></div>
<p data-path-to-node="6">To get started, we&#8217;ll need the standard <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> dependency:</p>
<div class="code-block ng-tns-c2827611141-91 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwiIvrb72dOTAxUAAAAAHQAAAAAQaw">
<div class="formatted-code-block-internal-container ng-tns-c2827611141-91">
<div class="animated-opacity ng-tns-c2827611141-91">
<pre><code class="language-java">&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;4.0.5&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
</div>
</div>
</div>
<p data-path-to-node="8">For <em>JSON</em> processing, Spring Boot includes <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson"><em>Jackson</em></a> by default. To better understand it, we&#8217;ll ensure <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind">jackson-databind</a> is on our classpath, as it provides the underlying logic for the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.html"><em>MappingJackson2HttpMessageConverter</em></a>.</p>
<h2 data-path-to-node="10" id="bd-understanding-and-identifying-the-root-cause" data-id="understanding-and-identifying-the-root-cause"><b data-path-to-node="10" data-index-in-node="0">3. Understanding and Identifying the Root Cause</b></h2>
<div class="bd-anchor" id="understanding-and-identifying-the-root-cause"></div>
<p data-path-to-node="4">To fix this error, we’ll first understand how Spring maps the raw HTTP response to Java objects. The issue is rarely with the data itself, but rather a mismatch in the expected communication protocol. We&#8217;ll now examine the internal conversion mechanism, identify common misleading media types, and learn how to inspect the actual response headers.</p>
<h3 data-path-to-node="11" id="bd-1-how-resttemplate-converts-responses" data-id="1-how-resttemplate-converts-responses"><b data-path-to-node="11" data-index-in-node="0">3.1. How RestTemplate Converts Responses</b></h3>
<div class="bd-anchor" id="1-how-resttemplate-converts-responses"></div>
<p data-path-to-node="12"><i data-path-to-node="12" data-index-in-node="0">RestTemplate</i> relies on a list of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-httpmessageconverter-rest"><em>HttpMessageConverter</em> beans</a> to transform HTTP request and response bodies. <strong>When a response arrives, Spring looks at the <em>Content-Type</em> header sent by the server. It then iterates through its registered converters to find one that supports both that <em>MediaType</em> and the target Java class.</strong></p>
<h3 data-path-to-node="13" id="bd-2-common-mismatched-media-types" data-id="2-common-mismatched-media-types"><b data-path-to-node="13" data-index-in-node="0">3.2. Common Mismatched Media Types</b></h3>
<div class="bd-anchor" id="2-common-mismatched-media-types"></div>
<p data-path-to-node="14">The issue usually isn&#8217;t that the data is invalid, but that the metadata is misleading. Many legacy or third-party APIs return valid JSON, but set the <span style="margin: 0px;padding: 0px"><em>Content-Type </em>header</span> to something other than <em>application/json</em>. Common mismatched types include <em>text/plain</em>, <em>text/javascript,</em> and <em>application/octet-stream.</em></p>
<p data-path-to-node="16"><strong>The default <span style="margin: 0px;padding: 0px"><em>MappingJackson2HttpMessageConverter </em>only</span> claims to support <em>application/</em><span style="margin: 0px;padding: 0px"><em>json </em>and</span> <em>application/*+json.</em></strong> It ignores any other type of responses, leading to the &#8220;Could not extract response: no suitable HttpMessageConverter found for response type and content type&#8221; error.</p>
<h3 data-path-to-node="17" id="bd-3-checking-api-response-headers" data-id="3-checking-api-response-headers"><b data-path-to-node="17" data-index-in-node="0">3.3. Checking API Response Headers</b></h3>
<div class="bd-anchor" id="3-checking-api-response-headers"></div>
<p data-path-to-node="18">To diagnose this, we must inspect the headers. <strong>If we can&#8217;t access external logs, we can enable debug logging for Spring Web in our <em>application.properties</em></strong>:</p>
<div class="code-block ng-tns-c2827611141-92 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwiIvrb72dOTAxUAAAAAHQAAAAAQbA">
<div class="formatted-code-block-internal-container ng-tns-c2827611141-92">
<div class="animated-opacity ng-tns-c2827611141-92">
<pre><code class="language-java">logging.level.org.springframework.web.client.RestTemplate=DEBUG</code></pre>
</div>
</div>
</div>
<p data-path-to-node="20">This will reveal the exact <span style="margin: 0px;padding: 0px"><em>Content-Type</em></span> the server is providing, allowing us to target the specific mismatch.</p>
<h2 data-path-to-node="22" id="bd-solving-the-error-configuration-strategies" data-id="solving-the-error-configuration-strategies"><b data-path-to-node="22" data-index-in-node="0">4. Solving the Error: Configuration Strategies</b></h2>
<div class="bd-anchor" id="solving-the-error-configuration-strategies"></div>
<p>In this section, we&#8217;ll create a config class named <em>RestTemplateConverterConfig </em>to inject the <em>RestTemplate </em>bean, which will help us resolve this error. The following section mentions the details on it:</p>
<h3 data-path-to-node="23" id="bd-1-adding-support-for-custom-media-types" data-id="1-adding-support-for-custom-media-types"><b data-path-to-node="23" data-index-in-node="0">4.1. Adding Support for Custom Media Types</b></h3>
<div class="bd-anchor" id="1-adding-support-for-custom-media-types"></div>
<p data-path-to-node="24"><strong>The most surgical fix is to tell the Jackson converter to treat these uncommon media types as JSON</strong>. We can create an instance of <span style="margin: 0px;padding: 0px"><em>MappingJackson2HttpMessageConverter </em></span>and update its supported media types:</p>
<div class="code-block ng-tns-c2827611141-93 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwiIvrb72dOTAxUAAAAAHQAAAAAQbQ">
<div class="formatted-code-block-internal-container ng-tns-c2827611141-93">
<div class="animated-opacity ng-tns-c2827611141-93">
<pre><code class="language-java">@Configuration
public class RestTemplateConverterConfig {
    @Bean("specificMediaTypesRestTemplate")
    public RestTemplate specificMediaTypesRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        List&lt;HttpMessageConverter&lt;?&gt;&gt; converters = restTemplate.getMessageConverters();
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        jsonConverter.setSupportedMediaTypes(Arrays.asList(
          MediaType.APPLICATION_JSON,
          MediaType.TEXT_PLAIN,
          MediaType.valueOf("text/javascript")
        ));
        converters.add(jsonConverter);
        restTemplate.setMessageConverters(converters);
        return restTemplate;
    }
}</code></pre>
</div>
</div>
</div>
<p>Here we explicitly list only the media types we expect to encounter. This keeps the converter&#8217;s scope narrow and intentional. Other unexpected content types, such as <em>application/octet-stream, </em>would still fail, which is often the desired behavior in a controlled environment.</p>
<h3 data-path-to-node="26" id="bd-2-manually-registering-converters-in-resttemplate" data-id="2-manually-registering-converters-in-resttemplate"><b data-path-to-node="26" data-index-in-node="0">4.2. Manually Registering Converters in <em>RestTemplate</em></b></h3>
<div class="bd-anchor" id="2-manually-registering-converters-in-resttemplate"></div>
<p data-path-to-node="27">If we want a more permissive setup, within the same <em>RestTemplateConverterConfig</em> class, we&#8217;ll register another Jackson converter with <em>MediaType.ALL. </em>This instructs the <em>RestTemplate</em> to handle any content type the server returns:</p>
<div class="code-block ng-tns-c2827611141-94 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwiIvrb72dOTAxUAAAAAHQAAAAAQbg">
<div class="formatted-code-block-internal-container ng-tns-c2827611141-94">
<div class="animated-opacity ng-tns-c2827611141-94">
<pre><code class="language-java">@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    List&lt;HttpMessageConverter&lt;?&gt;&gt; converters = restTemplate.getMessageConverters();
    MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
    jsonConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
    converters.add(jsonConverter);
    restTemplate.setMessageConverters(converters);
    return restTemplate;
}</code></pre>
</div>
</div>
</div>
<p data-path-to-node="29">Here, we call the <em>getMessageConverters() </em>method and append to the existing list rather than replacing it. This preserves all the default converters Spring registers, such as those for <em>String </em>and <em>byte[]</em>, and simply adds our custom Jackson converter at the end.</p>
<h3 data-path-to-node="29" id="bd-3-using-resttemplateexchange-with-parameterizedtypereference" data-id="3-using-resttemplateexchange-with-parameterizedtypereference"><b data-path-to-node="29" data-index-in-node="0">4.3. Using <em>RestTemplate.exchange()</em> With <em>ParameterizedTypeReference</em></b></h3>
<div class="bd-anchor" id="3-using-resttemplateexchange-with-parameterizedtypereference"></div>
<p data-path-to-node="30">Sometimes the error arises because we are trying to deserialize into a generic collection, like <em>List&lt;User&gt;</em>. <strong>In this example, the <em>User</em> class is a simple POJO with <em>id</em> and <em>name</em> fields that <em>Jackson</em> maps from the <em>JSON</em> response.</strong></p>
<p data-path-to-node="30">Using <em>getForObject() </em>with <em>List.class </em>loses generic type information at runtime due to type erasure. Instead, we should use the <em>restTemplate.exchange() </em>method with a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-parameterized-type-reference"><em>ParameterizedTypeReference</em></a>:</p>
<pre><code class="language-java">ResponseEntity&lt;List&lt;User&gt;&gt; response = restTemplate.exchange(
  "/users",
  HttpMethod.GET,
  null,
  new ParameterizedTypeReference&lt;List&lt;User&gt;&gt;() {}
);</code></pre>
<p data-path-to-node="33">The anonymous subclass of <em>ParameterizedTypeReference</em> captures the full generic type <em>List&lt;User&gt;</em> at compile time, giving Jackson enough information to correctly deserialize each element in the array into a <em>User</em> object.</p>
<h2 data-path-to-node="0" id="bd-testing-and-verification" data-id="testing-and-verification"><b data-path-to-node="0" data-index-in-node="0">5. Testing and Verification</b></h2>
<div class="bd-anchor" id="testing-and-verification"></div>
<p data-path-to-node="1">To verify our configurations, we’ll use <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-mock-rest-template#:~:text=MockRestServiceServer%20actually%20works%20by%20intercepting%20the%20HTTP%20API%20calls%20using%20a%20MockClientHttpRequestFactory.%20Based%20on%20our%20configuration%2C%20it%20creates%20a%20list%20of%20expected%20requests%20and%20corresponding%20responses."><em>MockRestServiceServer</em></a>. This allows us to simulate the exact mismatched header scenarios that typically trigger the <em>RestClientException </em>without needing a live external API.</p>
<h3 data-path-to-node="2" id="bd-1-testing-the-surgical-fix" data-id="1-testing-the-surgical-fix"><b data-path-to-node="2" data-index-in-node="0">5.1. Testing the Surgical Fix</b></h3>
<div class="bd-anchor" id="1-testing-the-surgical-fix"></div>
<p data-path-to-node="3">In this first scenario, we use the <em>@Qualifier</em> to inject our <em>specificMediaTypesRestTemplate</em>. We&#8217;ll simulate a response that is explicitly labeled as <em>text/plain</em>. <strong>This proves that our surgical inclusion of that specific media type works as intended</strong>:</p>
<div class="code-block ng-tns-c2827611141-138 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwiIvrb72dOTAxUAAAAAHQAAAAAQzAE">
<div class="formatted-code-block-internal-container ng-tns-c2827611141-138">
<div class="animated-opacity ng-tns-c2827611141-138">
<pre><code class="language-java">@Autowired
@Qualifier("specificMediaTypesRestTemplate")
private RestTemplate specificMediaTypesRestTemplate;
@Test
void givenSpecificMediaTypesRestTemplate_whenTextPlainResponse_thenDeserializeCorrectly() {
    MockRestServiceServer mockServer = MockRestServiceServer.createServer(specificMediaTypesRestTemplate);
    mockServer.expect(requestTo("/user"))
      .andRespond(withSuccess("{\"id\":1,\"name\":\"Sudarshan\"}", MediaType.TEXT_PLAIN));
    User user = specificMediaTypesRestTemplate.getForObject("/user", User.class);
    assertNotNull(user);
    assertEquals("Sudarshan", user.getName());
}</code></pre>
</div>
</div>
</div>
<h3 data-path-to-node="5" id="bd-2-testing-the-permissive-fix" data-id="2-testing-the-permissive-fix"><b data-path-to-node="5" data-index-in-node="0">5.2. Testing the Permissive Fix</b></h3>
<div class="bd-anchor" id="2-testing-the-permissive-fix"></div>
<p data-path-to-node="6">Next, <strong>we test the primary <em>RestTemplate</em> bean configured with <em>MediaType.ALL</em></strong>. This test confirms that<span style="margin: 0px;padding: 0px">, by making the converter permissive, it ignores the misleading <em>text/plain</em> header and defaults to <em>Jackson</em> for </span>deserialization.</p>
<div class="code-block ng-tns-c2827611141-139 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwiIvrb72dOTAxUAAAAAHQAAAAAQzQE">
<div class="formatted-code-block-internal-container ng-tns-c2827611141-139">
<div class="animated-opacity ng-tns-c2827611141-139">
<pre><code class="language-java">@Test
void givenMockServer_whenTextPlainResponse_thenDeserializeCorrectly() {
    MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
    mockServer.expect(requestTo("/user"))
      .andRespond(withSuccess("{\"id\":1,\"name\":\"Sudarshan\"}", MediaType.TEXT_PLAIN));
    User user = restTemplate.getForObject("/user", User.class);
    assertNotNull(user);
    assertEquals("Sudarshan", user.getName());
}</code></pre>
</div>
</div>
</div>
<h3 data-path-to-node="8" id="bd-3-verifying-generic-type-resolution" data-id="3-verifying-generic-type-resolution"><b data-path-to-node="8" data-index-in-node="0">5.3. Verifying Generic Type Resolution</b></h3>
<div class="bd-anchor" id="3-verifying-generic-type-resolution"></div>
<p data-path-to-node="9">Finally, <strong>we verify that our <em>restTemplate.exchange()</em> strategy correctly handles collections</strong>. Even with a mismatched <em>text/plain</em> header, the <em>ParameterizedTypeReference </em>ensures that the generic information <em>List&lt;User&gt;</em> is preserved during the conversion process:</p>
<div class="code-block ng-tns-c2827611141-140 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwiIvrb72dOTAxUAAAAAHQAAAAAQzgE">
<div class="formatted-code-block-internal-container ng-tns-c2827611141-140">
<div class="animated-opacity ng-tns-c2827611141-140">
<pre><code class="language-java">@Test
void givenMockServer_whenTextPlainResponseForList_thenDeserializeWithParameterizedTypeReference() {
    MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
    mockServer.expect(requestTo("/users"))
      .andRespond(
        withSuccess(
          "[{\"id\":1,\"name\":\"Sudarshan\"},{\"id\":2,\"name\":\"Baeldung\"}]",
          MediaType.TEXT_PLAIN));
    ResponseEntity&lt;List&lt;User&gt;&gt; response = restTemplate.exchange(
      "/users",
      HttpMethod.GET,
      null,
      new ParameterizedTypeReference&lt;List&lt;User&gt;&gt;() {}
    );
    assertNotNull(response.getBody());
    assertEquals(2, response.getBody().size());
    assertEquals("Sudarshan", response.getBody().get(0).getName());
}</code></pre>
</div>
</div>
</div>
<h2 data-path-to-node="41" id="bd-conclusion" data-id="conclusion"><b data-path-to-node="41" data-index-in-node="0">6. Conclusion</b></h2>
<div class="bd-anchor" id="conclusion"></div>
<p data-path-to-node="42">In this tutorial, we understood that the &#8220;<em>No Suitable HttpMessageConverter</em>&#8221; error is rarely a sign of broken data; it’s a communication breakdown between the server&#8217;s headers and the client&#8217;s expectations. <strong>By identifying the returned <span style="margin: 0px;padding: 0px"><em>Content-Type</em></span> and explicitly configuring our <span style="margin: 0px;padding: 0px"><em>MappingJackson2HttpMessageConverter </em></span>to support it, we can bridge this gap.</strong></p>
<p data-path-to-node="43">Whether you choose to support all media types via <em>MediaType.ALL</em> or strictly list the exceptions, like <em>text/plain</em>, understanding the converter registration process is key to building resilient Spring clients.</p>
<p data-path-to-node="43">As always, the complete code samples used in this article are available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-resttemplate-3">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-resttemplate-no-suitable-httpmessageconverter">Handle “No Suitable HttpMessageConverter” in Spring RestTemplate</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/953488283/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953488283/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953488283/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953488283/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953488283/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953488283/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-resttemplate-no-suitable-httpmessageconverter#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-resttemplate-no-suitable-httpmessageconverter/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/~/953488283/0/baeldung~Handle-No-Suitable-HttpMessageConverter-in-Spring-RestTemplate/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-flip-bits-number</feedburner:origLink>
		<title>Flip the Bits of a Number in Java</title>
		<link>https://feeds.feedblitz.com/~/953487332/0/baeldung~Flip-the-Bits-of-a-Number-in-Java</link>
					<comments>https://feeds.feedblitz.com/~/953487332/0/baeldung~Flip-the-Bits-of-a-Number-in-Java#respond</comments>
		
		<dc:creator><![CDATA[Zachary Bouhannana]]></dc:creator>
		<pubDate>Tue, 07 Apr 2026 22:58:58 +0000</pubDate>
				<category><![CDATA[Java Numbers]]></category>
		<category><![CDATA[Java Integer]]></category>
		<category><![CDATA[Java Operators]]></category>
		<category><![CDATA[Math]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-flip-bits-number</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2021/09/Java-8-Featured-1024x536.png" class="webfeedsFeaturedVisual wp-post-image" alt="Contact Us Featured" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" /><p>Learn standard and alternative ways to invert the value of all bits or only the significant bits of a number in Java.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/953487332/0/baeldung~Flip-the-Bits-of-a-Number-in-Java">Flip the Bits of a Number in Java</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953487332/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953487332/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2021%2f09%2fJava-8-Featured-1024x536.png"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953487332/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953487332/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953487332/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-flip-bits-number#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-flip-bits-number/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-8-Featured-1024x536.png" class="webfeedsFeaturedVisual wp-post-image" alt="Contact Us Featured" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2021/09/Java-8-Featured-1024x536.png 1024w, https://www.baeldung.com/wp-content/uploads/2021/09/Java-8-Featured-300x157.png 300w, https://www.baeldung.com/wp-content/uploads/2021/09/Java-8-Featured-768x402.png 768w, https://www.baeldung.com/wp-content/uploads/2021/09/Java-8-Featured-100x52.png 100w, https://www.baeldung.com/wp-content/uploads/2021/09/Java-8-Featured.png 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>Bit manipulation is a fundamental concept in programming that involves working with individual bits of binary data. One common operation is flipping all bits in an integer, converting every <em>0</em> to <em>1</em> and every <em>1</em> to <em>0</em>.</p>
<p>In this tutorial, we&#8217;ll explain what bit flipping means and explore several Java methods to flip the bits of an integer. For completeness, we&#8217;ll cover both full 32-bit flipping and flipping only the significant bits.</p>
<h2 id="bd-understanding-bit-flipping" data-id="understanding-bit-flipping">2. Understanding Bit Flipping</h2>
<div class="bd-anchor" id="understanding-bit-flipping"></div>
<p>Before we learn how to flip bits in an integer, it&#8217;s best to look at a visual example.</p>
<p>To begin with, let&#8217;s consider the decimal number <em>21</em> and, more specifically, its binary representation:</p>
<pre><code class="language-java">10101</code></pre>
<p>The goal is to invert each bit so that every <em>0</em> becomes a <em>1</em> and every <em>1</em> becomes a <em>0</em>:</p>
<pre><code class="language-java">01010</code></pre>
<p>This corresponds to the decimal value <em>10</em> (<em>8+2</em>).</p>
<p>During this transformation, each bit at position <em>i</em> in the original number gets inverted in the result. However, there&#8217;s an important distinction to make. <strong>When we flip all 32 bits of an integer, i.e., its full size, the result differs from flipping only the significant bits</strong>. For instance, flipping all 32 bits of the integer <em>21</em> produces <em>-22</em> due to <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-compute-twos-complement">two&#8217;s complement</a> representation, whereas flipping only the significant bits (<em>10101</em> -&gt; <em>01010</em>) yields <em>10</em>.</p>
<p>In the following sections, let&#8217;s explore both scenarios in detail.</p>
<h2 id="bd-using-the-bitwise-not-operator" data-id="using-the-bitwise-not-operator">3. Using the Bitwise NOT Operator</h2>
<div class="bd-anchor" id="using-the-bitwise-not-operator"></div>
<p>The most direct approach to flip bits is the bitwise NOT operator <em>~</em>. This <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-operators#unary-operators">unary operator</a> inverts every bit in the integer&#8217;s binary representation:</p>
<pre><code class="language-java">public static int flipAllBits(int n) {
    return ~n;
}</code></pre>
<p>The operator flips all 32 bits of the integer. <strong>This means that <em>~</em> inverts every bit, including leading zeros, turning a positive number into a negative one due to the two&#8217;s complement representation in Java</strong>.</p>
<p>Let&#8217;s verify the results using a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/junit-5">JUnit 5</a> test and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/introduction-to-assertj">AssertJ</a> matchers:</p>
<pre><code class="language-java">@Test
void givenPositiveInteger_whenFlipAllBits_thenReturnsNegativeComplement() {
    assertThat(flipAllBits(21)).isEqualTo(-22);
    assertThat(flipAllBits(0)).isEqualTo(-1);
    assertThat(flipAllBits(-1)).isEqualTo(0);
}</code></pre>
<p>As we can see, flipping all 32 bits of <em>21</em> gives us <em>-22</em>, not <em>10</em>. This is because Java&#8217;s <em>int</em> type is a signed 32-bit integer.</p>
<p>To get a result like <em>10</em> from <em>21</em>, we need to flip only the significant bits. Let&#8217;s cover this case next.</p>
<h2 id="bd-flipping-only-the-significant-bits" data-id="flipping-only-the-significant-bits">4. Flipping Only the Significant Bits</h2>
<div class="bd-anchor" id="flipping-only-the-significant-bits"></div>
<p>In many practical scenarios, we want to flip only the bits that are actually used in the binary representation of the number, excluding leading zeros. For instance, flipping the significant bits of <em>21</em> (<em>10101</em>) should give us <em>10</em> (<em>01010</em>).</p>
<h3 id="bd-1-using-a-mask-with-bit-length-calculation" data-id="1-using-a-mask-with-bit-length-calculation">4.1. Using a Mask With Bit Length Calculation</h3>
<div class="bd-anchor" id="1-using-a-mask-with-bit-length-calculation"></div>
<p>The idea is straightforward: we create a mask with a <em>1</em> at each significant bit position, then <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-xor-operator">XOR</a> the number with this mask. The XOR operation flips only the bits where the mask has a <em>1</em>:</p>
<pre><code class="language-java">public static int flipSignificantBits(int n) {
    if (n == 0) {
        return 0;
    }
    int bitLength = Integer.SIZE - Integer.numberOfLeadingZeros(n);
    int mask = (1 &lt;&lt; bitLength) - 1;
    return n ^ mask;
}</code></pre>
<p>First, we handle the edge case where <em>n</em> is <em>0</em>. Then, we calculate the number of significant bits using <em>Integer.numberOfLeadingZeros()</em>. With this value, we create a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-bitmasking">mask</a> by shifting <em>1</em> left by <em>bitLength</em> positions and subtracting <em>1</em>, which gives us a sequence of 1s matching the significant bit length.</p>
<p><strong>Finally, the XOR operation (<em>^</em>) flips only the significant bits, since XOR with <em>1</em> inverts a bit while XOR with <em>0</em> leaves it unchanged</strong>.</p>
<p>Let&#8217;s verify this with a test:</p>
<pre><code class="language-java">@Test
void givenPositiveInteger_whenFlipSignificantBits_thenReturnsFlippedValue() {
    assertThat(flipSignificantBits(21)).isEqualTo(10);
    assertThat(flipSignificantBits(26)).isEqualTo(5);
    assertThat(flipSignificantBits(0)).isEqualTo(0);
    assertThat(flipSignificantBits(1)).isEqualTo(0);
    assertThat(flipSignificantBits(7)).isEqualTo(0);
}</code></pre>
<p>To illustrate, let&#8217;s trace through the example with <em>26</em> (binary <em>11010</em>):</p>
<pre><code class="language-java">n     = 11010  (26)
mask  = 11111  (31)
n ^ mask = 00101  (5)</code></pre>
<p>This approach effectively and efficiently flips only the significant bits.</p>
<h3 id="bd-2-using-integerhighestonebit" data-id="2-using-integerhighestonebit">4.2. Using <em>Integer.highestOneBit()</em></h3>
<div class="bd-anchor" id="2-using-integerhighestonebit"></div>
<p>We can also use the built-in <em>Integer.highestOneBit()</em> method to build the mask. This method returns a value with only the highest bit of the input set:</p>
<pre><code class="language-java">public static int flipSignificantBitsUsingHighestOneBit(int n) {
    if (n == 0) {
        return 0;
    }
    int mask = (Integer.highestOneBit(n) &lt;&lt; 1) - 1;
    return n ^ mask;
}</code></pre>
<p>Here, <em>Integer.highestOneBit(n)</em> returns the value of the most significant bit. <strong>By shifting it left by one position and subtracting <em>1</em>, we create a mask with a <em>1</em> at each significant bit</strong>. Then, a XOR operation with this mask again flips only those bits.</p>
<h3 id="bd-3-using-the-bitwise-not-with-a-mask" data-id="3-using-the-bitwise-not-with-a-mask">4.3. Using the Bitwise NOT With a Mask</h3>
<div class="bd-anchor" id="3-using-the-bitwise-not-with-a-mask"></div>
<p>Instead of XOR, we can also combine the bitwise NOT operator with a mask to get the same result:</p>
<pre><code class="language-java">public static int flipSignificantBitsUsingNot(int n) {
    if (n == 0) {
        return 0;
    }
    int bitLength = Integer.SIZE - Integer.numberOfLeadingZeros(n);
    int mask = (1 &lt;&lt; bitLength) - 1;
    return ~n &amp; mask;
}</code></pre>
<p>This function first computes the bitwise NOT of <em>n</em>, which flips all 32 bits. Then, the AND operation with the mask zeroes out all the bits beyond the significant ones. Simply put, <strong>the result is equivalent to flipping only the significant bits</strong>.</p>
<h2 id="bd-alternative-methods" data-id="alternative-methods">5. Alternative Methods</h2>
<div class="bd-anchor" id="alternative-methods"></div>
<p>Apart from the standard bitwise operators, there are other, more unorthodox ways to flip all 32 bits of an integer.</p>
<h3 id="bd-1-using-arithmetic-negation" data-id="1-using-arithmetic-negation">5.1. Using Arithmetic Negation</h3>
<div class="bd-anchor" id="1-using-arithmetic-negation"></div>
<p>Due to the two&#8217;s complement representation, flipping all bits of <em>n</em> is equivalent to computing <em>-n &#8211; 1</em>:</p>
<pre><code class="language-java">public static int flipBitsArithmetic(int n) {
    return -n - 1;
}</code></pre>
<p>This works because, in two&#8217;s complement, the negation of <em>n</em> is the bitwise complement plus <em>1</em> (<em>-n = ~n + 1</em>). <strong>Therefore, <em>~n = -n &#8211; 1</em>, which gives the same result as the bitwise NOT operator</strong>.</p>
<h3 id="bd-2-using-xor-with--1" data-id="2-using-xor-with--1">5.2. Using XOR With <em>-1</em></h3>
<div class="bd-anchor" id="2-using-xor-with--1"></div>
<p>Another way to flip all bits is to XOR the number with <em>-1</em>. In binary, we represent <em>-1</em> as all 1s (<em>11111111&#8230;1</em> in 32 bits). Notably, <em>~0</em> also evaluates to <em>-1</em>, so we can use either interchangeably:</p>
<pre><code class="language-java">public static int flipBitsXorMinusOne(int n) {
    return n ^ -1;
}</code></pre>
<p>Since XOR with <em>1</em> flips a bit, performing XOR with a value where all bits are <em>1</em> effectively flips every bit in the integer. <strong>This produces the same result as the ~ operator</strong>.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored several approaches for flipping bits of an integer in Java. We started with the bitwise NOT operator for full 32-bit flipping. Next, we looked at mask-based methods for flipping only the significant bits using XOR and AND operations. Finally, we covered alternative approaches using arithmetic negation and XOR.</p>
<p>As always, all the source code is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang-math-5">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-flip-bits-number">Flip the Bits of a Number in Java</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/953487332/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953487332/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953487332/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2021%2f09%2fJava-8-Featured-1024x536.png"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953487332/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953487332/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953487332/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-flip-bits-number#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-flip-bits-number/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/~/953487332/0/baeldung~Flip-the-Bits-of-a-Number-in-Java/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2021/09/Java-8-Featured-150x150.png</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-security-7-mfa</feedburner:origLink>
		<title>Multi-Factor Authentication in Spring Security 7</title>
		<link>https://feeds.feedblitz.com/~/953487335/0/baeldung~MultiFactor-Authentication-in-Spring-Security</link>
					<comments>https://feeds.feedblitz.com/~/953487335/0/baeldung~MultiFactor-Authentication-in-Spring-Security#respond</comments>
		
		<dc:creator><![CDATA[Sagar Verma]]></dc:creator>
		<pubDate>Tue, 07 Apr 2026 22:52:17 +0000</pubDate>
				<category><![CDATA[Spring Security]]></category>
		<category><![CDATA[Authentication]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203362</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2017/08/Spring-Security-2.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Spring Security 7 introduces built-in support for multi-factor authentication, allowing developers to enforce multiple authentication steps using the existing authorization model.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/953487335/0/baeldung~MultiFactor-Authentication-in-Spring-Security">Multi-Factor Authentication in Spring Security 7</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953487335/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953487335/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2017%2f08%2fSpring-Security-2.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953487335/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953487335/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953487335/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-security-7-mfa#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-security-7-mfa/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/2017/08/Spring-Security-2.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/2017/08/Spring-Security-2.jpg 952w, https://www.baeldung.com/wp-content/uploads/2017/08/Spring-Security-2-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2017/08/Spring-Security-2-768x402.jpg 768w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 data-section- data-start="52" data-end="66" id="bd-v7scl5" data-id="v7scl5">1. Overview</h2>
<div class="bd-anchor" id="v7scl5"></div>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-security-basic-authentication">Authentication</a> is the first line of defense in all web applications. Traditionally, applications rely on a username and password to verify a user&#8217;s identity. However, experts no longer consider relying on a single authentication factor secure for modern systems.</p>
<p><strong>Multi-Factor Authentication (MFA)</strong> <strong>addresses this by requiring users to verify their identity using multiple independent factors before accessing a system.</strong></p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://docs.spring.io/spring-security/reference/whats-new.html">Spring Security 7</a> introduces built-in support for <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2025/10/21/multi-factor-authentication-in-spring-security-7">multi-factor authentication</a>, allowing developers to enforce multiple authentication steps using the existing authorization model. In this article, we’ll explore how MFA works in Spring Security 7 and how to implement it in a Spring Boot application.</p>
<h2 data-section- data-start="1736" data-end="1779" id="bd-f626pv" data-id="f626pv">2. Understanding MFA in Spring Security 7</h2>
<div class="bd-anchor" id="f626pv"></div>
<p><strong>Spring Security 7</strong> <strong>introduces a new way to model authentication factors using authorities</strong>. <strong>Instead of treating MFA as a separate authentication mechanism, Spring Security progressively grants authorities for each successful authentication factor.</strong></p>
<p>Multi-Factor Authentication (MFA) strengthens security by requiring users to verify their identity using multiple independent factors. These factors typically fall into three categories: something the user knows, such as a password or PIN, something the user has, such as a mobile device or email token, and something the user is, such as a fingerprint or other biometric data. By combining these factors, applications significantly reduce the risk of compromised credentials and unauthorized access.</p>
<p>Each time a user successfully authenticates with a specific factor, Spring Security adds a corresponding authority to the authentication object. This authority represents the factor that has already been verified during the authentication process. Some common examples include <strong><em>FACTOR_PASSWORD</em> for password-based authentication, <em>FACTOR_X509</em> for certificate-based authentication, and <em>FACTOR_OTT</em> for one-time token authentication.</strong> These authorities are represented internally by the <em>FactorGrantedAuthority</em> class and become part of the authenticated user&#8217;s security context.</p>
<p>This design allows authorization rules to verify that the required authentication factors are satisfied before granting access to protected resources.</p>
<h2 data-section- data-start="2865" data-end="2883" id="bd-p5u09" data-id="p5u09">3. Project Setup</h2>
<div class="bd-anchor" id="p5u09"></div>
<p data-start="2885" data-end="2977">Before implementing multi-factor authentication, we’ll set up a simple <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot">Spring Boot</a> application <span style="margin: 0px;padding: 0px">using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://start.spring.io/" target="_blank" rel="noopener">Spring Initializr</a></span> with Spring Security 7. First, we need to configure the required dependencies.</p>
<p data-start="2885" data-end="2977">We include Spring Boot starters for <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web/4.0.3">web</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security/4.0.3/dependencies">security</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test/4.0.3/dependencies">starter-test</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webmvc-test/4.0.3">webmvc-test</a>, </em>and <em><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.security/spring-security-test/7.0.0">security-testing</a></em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;version&gt;4.0.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
    &lt;version&gt;4.0.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
    &lt;version&gt;4.0.3&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-webmvc-test&lt;/artifactId&gt;
    &lt;version&gt;4.0.3&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.security&lt;/groupId&gt;
    &lt;artifactId&gt;spring-security-test&lt;/artifactId&gt;
    &lt;version&gt;7.0.0&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;</code></pre>
<p data-start="0" data-end="308">This configuration adds the core dependencies required for our application. It includes Spring Boot’s web support for building REST endpoints, the Spring Security framework for implementing authentication and authorization, and Spring Security testing utilities for writing security-focused unit tests.</p>
<p data-start="310" data-end="458" data-is-last-node="" data-is-only-node="">With these dependencies in place, the application now has everything required to configure and enable multi-factor authentication.</p>
<h2 data-section- data-start="4204" data-end="4254" id="bd-vp91pr" data-id="vp91pr">4. Enabling Multi-Factor Authentication Globally</h2>
<div class="bd-anchor" id="vp91pr"></div>
<p>Spring Security 7 introduces a convenient annotation called <em>@EnableMultiFactorAuthentication</em>. <strong>This annotation allows developers to define which authentication factors are required for all protected endpoints.</strong></p>
<p data-start="4502" data-end="4598">The following configuration enables MFA globally using password and X509 authentication factors:</p>
<pre><code class="language-java">@Configuration
@EnableWebSecurity
@EnableMultiFactorAuthentication(
  authorities = { FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.X509_AUTHORITY }
)
public class GlobalMfaSecurityConfig {
    @Bean
    @Order(3)
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/**").authorizeHttpRequests(auth -&gt;auth.requestMatchers("/public")
          .permitAll().anyRequest().authenticated()).formLogin(withDefaults());
        return http.build();
    }
}</code></pre>
<p data-start="0" data-end="257">This configuration enables Spring Security for the application and enables multi-factor authentication globally. As part of the setup, it specifies that the user must complete two authentication factors before the system considers them fully authenticated.</p>
<p data-start="259" data-end="629" data-is-last-node="" data-is-only-node="">In this case, the required factors are password and X.509 certificate authentication. Once the configuration is active, every secured endpoint in the application requires both factors for access to be granted. Applying MFA globally like this helps avoid inconsistencies and reduces the risk of missing MFA rules in individual authorization configurations.</p>
<h2 data-section- data-start="5594" data-end="5633" id="bd-kbel6w" data-id="kbel6w">5. Applying MFA to Specific Endpoints</h2>
<div class="bd-anchor" id="kbel6w"></div>
<p data-start="5635" data-end="5774">In many applications, MFA should be required only for sensitive endpoints, such as account settings, financial operations, or admin actions. <strong>Spring Security provides <i>the AuthorizationManagerFactory</i> API to define MFA rules programmatically.</strong></p>
<p data-start="5919" data-end="5985">The following configuration requires MFA only for admin endpoints:</p>
<pre><code class="language-java">@Configuration
@EnableWebSecurity
public class AdminMfaSecurityConfig {
    @Bean
    @Order(1)
    SecurityFilterChain adminSecurityFilterChain(HttpSecurity http) throws Exception {
        AuthorizationManagerFactory&lt;Object&gt; mfa = AuthorizationManagerFactories.multiFactor()
          .requireFactors(
            FactorGrantedAuthority.PASSWORD_AUTHORITY,
            FactorGrantedAuthority.X509_AUTHORITY)
          .build();
        http.securityMatcher("/admin/**").authorizeHttpRequests(auth -&gt;auth.requestMatchers("/admin/**")
          .access(mfa.hasRole("ADMIN")).anyRequest().authenticated()).formLogin(withDefaults());
        return http.build();
    }
}</code></pre>
<p data-start="0" data-end="262">In this configuration, multi-factor authentication is applied only to requests that match the <em data-start="94" data-end="103">/admin/</em>** endpoints. When a user attempts to access these routes, Spring Security checks whether the required authentication factors are present.</p>
<p data-start="264" data-end="571" data-is-last-node="" data-is-only-node="">In addition to completing both authentication factors, the user must also have the <em data-start="347" data-end="354">ADMIN</em> role. This approach provides fine-grained control over security rules, allowing developers to enforce MFA only on sensitive endpoints while keeping the rest of the application accessible with standard authentication.</p>
<h2 data-section- data-start="7060" data-end="7109" id="bd-oeaajp" data-id="oeaajp">6. Implementing Time-Based Authentication Rules</h2>
<div class="bd-anchor" id="oeaajp"></div>
<p data-start="7111" data-end="7296">Some applications require users to re-authenticate before performing sensitive actions. For example, a banking application may require a fresh login before updating payment information. Spring Security 7 supports time-based MFA rules.</p>
<p data-start="7391" data-end="7497">The following configuration requires the user to authenticate the password factor within the last five minutes:</p>
<pre><code class="language-java">@Configuration
@EnableWebSecurity
public class TimeBasedMfaSecurityConfig {
    @Bean
    @Order(2)
    SecurityFilterChain profileSecurityFilterChain(HttpSecurity http) throws Exception {
        AuthorizationManagerFactory&lt;Object&gt; recentLogin = AuthorizationManagerFactories.
          multiFactor().requireFactor(
            factor -&gt; factor.passwordAuthority().validDuration(Duration.ofMinutes(5)))
          .build();
        http.securityMatcher("/profile", "/profile/**").authorizeHttpRequests(
          auth -&gt; auth.requestMatchers("/profile", "/profile/**")
            .access(recentLogin.authenticated())
            .anyRequest().
            authenticated()).formLogin(withDefaults());
        return http.build();
    }
}</code></pre>
<p data-start="0" data-end="258">This rule allows users to navigate most parts of the application using their existing authenticated session. However, when a user attempts to access endpoints under <em>*/profile/**</em>, the system verifies whether the password authentication was completed recently.</p>
<p data-start="260" data-end="546" data-is-last-node="" data-is-only-node=""><strong>If the password factor is older than five minutes, Spring Security requires the user to authenticate again before granting access. This approach commonly allows users to perform sensitive operations, ensuring they complete critical actions only after a recent, verified authentication step.</strong></p>
<h2 data-section- data-start="8828" data-end="8866" id="bd-k4s5ze" data-id="k4s5ze">7. Implementing User-Based MFA Rules</h2>
<div class="bd-anchor" id="k4s5ze"></div>
<p data-start="8868" data-end="9025">Sometimes MFA rules apply only to specific users. For example, administrators may be required to use MFA while regular users can log in with a single factor. Spring Security allows implementing custom authorization managers to support such scenarios.</p>
<p data-start="9159" data-end="9218">The following example enforces MFA only for the admin user:</p>
<pre><code class="language-java">@Component
public class AdminMfaAuthorizationManager implements AuthorizationManager&lt;Object&gt; {
    AuthorizationManager&lt;Object&gt; mfa =
      AllAuthoritiesAuthorizationManager.hasAllAuthorities(FactorGrantedAuthority.OTT_AUTHORITY,
        FactorGrantedAuthority.PASSWORD_AUTHORITY);
    @Override
    public AuthorizationResult authorize(Supplier&lt;? extends Authentication&gt; authentication, Object context) {
        Authentication auth = authentication.get();
        if (auth != null &amp;&amp; "admin".equals(auth.getName())) {
            return mfa.authorize(authentication, context);
        }
        return new AuthorizationDecision(true);
    }
}</code></pre>
<p data-start="0" data-end="245">This logic checks the authenticated user&#8217;s identity and applies MFA rules conditionally. If the authenticated user is an admin, the system verifies that the user has completed both required authentication factors before granting access.</p>
<p data-start="247" data-end="515" data-is-last-node="" data-is-only-node="">For all other users, the system allows the request without enforcing additional MFA checks. This approach is useful when introducing MFA gradually, allowing organizations to enforce stronger security for high-privilege users first before extending it to the entire user base.</p>
<h2 data-section- data-start="10148" data-end="10179" id="bd-15wz0nu" data-id="15wz0nu">8. Writing Unit Tests for MFA</h2>
<div class="bd-anchor" id="15wz0nu"></div>
<p data-start="0" data-end="247">Testing authentication flows is essential to ensure that security rules behave as expected. Without proper tests, it becomes difficult to verify whether authentication requirements such as roles, permissions, or MFA factors are correctly enforced.</p>
<p data-start="249" data-end="541" data-is-last-node="" data-is-only-node="">Spring Security provides dedicated testing utilities that make it easier to simulate authenticated users and validate authorization behavior. These tools allow developers to write focused tests that verify that security configurations function correctly without requiring a full authentication setup.</p>
<h3 data-section- data-start="10349" data-end="10375" id="bd-dkifrs" data-id="dkifrs">8.1. Controller Example</h3>
<div class="bd-anchor" id="dkifrs"></div>
<p data-start="10377" data-end="10425">Before writing tests, we define a simple controller that exposes the endpoints secured by our MFA configurations:</p>
<pre><code class="language-java">@RestController
public class DemoController {
    @GetMapping("/public")
    public String publicEndpoint() {
        return "public endpoint";
    }
    @GetMapping("/profile")
    public String profileEndpoint() {
        return "profile endpoint";
    }
    @GetMapping("/admin/dashboard")
    public String adminDashboard() {
        return "admin dashboard";
    }
}</code></pre>
<p>This controller exposes three endpoints <em>/public, /profile, </em>and <em>/admin/dashboard</em> that help demonstrate different MFA enforcement strategies.</p>
<h3 data-section- data-start="10640" data-end="10665" id="bd-1buszc5" data-id="1buszc5">8.2. MFA Security Test</h3>
<div class="bd-anchor" id="1buszc5"></div>
<p data-start="10667" data-end="10736">This test verifies how the system behaves when it enforces multi-factor authentication globally:</p>
<pre><code class="language-java">@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
class GlobalMfaSecurityTest {
    @Autowired
    MockMvc mockMvc;
    @Test
    void givenUserWithoutMfa_whenAccessProfile_thenForbidden() throws Exception {
        mockMvc.perform(get("/profile").with(user("user").roles("USER")))
          .andExpect(status().is3xxRedirection())
          .andExpect(header().string("Location", containsString("/login")));
    }
}</code></pre>
<p>This test simulates a user attempting to access the admin endpoint without completing the required MFA factors. Since the authentication request only contains the basic role and does not include the necessary factor authorities, the request does not satisfy the configured security rules. As a result, Spring Security returns a 403 Forbidden response because the user has not completed all required authentication factors.</p>
<h3 data-section- data-start="11803" data-end="11819" id="bd-f2307e" data-id="f2307e">8.3. Admin Endpoint MFA Test</h3>
<div class="bd-anchor" id="f2307e"></div>
<p data-section-id="f2307e" data-start="11803" data-end="11819">Next, we&#8217;ll verify that the system enforces MFA for admin endpoints:</p>
<pre><code class="language-java">@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
class AdminMfaSecurityTest {
    @Autowired
    MockMvc mockMvc;
    @Test
    void givenAdminWithoutMfa_whenAccessAdminEndpoint_thenForbidden() throws Exception {
        mockMvc.perform(get("/admin/dashboard").with(user("admin").roles("ADMIN")))
          .andExpect(status().is3xxRedirection())
          .andExpect(header().string("Location", containsString("/login")));
    }
}</code></pre>
<p>This test simulates an administrator attempting to access the <em>/admin/dashboard</em> endpoint. Although the user has the required ADMIN role, the request does not include the required MFA authorities. Because the authentication factors are incomplete, Spring Security redirects the request to the login page. This confirms that MFA enforcement works correctly for privileged endpoints.</p>
<h3 id="bd-4-time-based-mfa-security-test" data-id="4-time-based-mfa-security-test">8.4. Time-Based MFA Security Test</h3>
<div class="bd-anchor" id="4-time-based-mfa-security-test"></div>
<p>Some operations require users to authenticate recently to ensure no one has compromised the session. The following test verifies that the profile endpoint requires a recent authentication:</p>
<pre><code class="language-java">@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
class TimeBasedMfaSecurityTest {
    @Autowired
    MockMvc mockMvc;
    @Test
    void givenUserWithoutRecentAuthentication_whenAccessProfile_thenForbidden() throws Exception {
        mockMvc.perform(get("/profile").with(user("user").roles("USER")))
          .andExpect(status().is3xxRedirection())
          .andExpect(header().string("Location", containsString("/login")));
    }
}</code></pre>
<p>This test checks that the <em>/profile</em> endpoint requires a recent authentication. Since the simulated request does not include the necessary MFA factor information, Spring Security redirects the request to the login page. This confirms that the system correctly enforces the time-based MFA rule.</p>
<h2 data-section- data-start="11803" data-end="11819" id="bd-f2307e-1" data-id="f2307e-1">9. Conclusion</h2>
<div class="bd-anchor" id="f2307e-1"></div>
<p>Multi-Factor Authentication (MFA) is a critical security measure for modern applications, helping reduce the risk of unauthorized access by requiring multiple verification factors.</p>
<p>Spring Security 7 provides built-in support for MFA, allowing developers to enforce multiple authentication steps using the existing authorization model. In this article, we explored how <em>FactorGrantedAuthority</em> models MFA, how to enable it globally with <em>@EnableMultiFactorAuthentication</em>, and how to apply it selectively to specific endpoints using <em>AuthorizationManagerFactory</em>. We also covered time-based rules, custom user-based policies, and testing MFA behavior using Spring Security test utilities.</p>
<p>By leveraging these features, developers can build secure and flexible authentication flows. As security threats continue to evolve, implementing MFA is no longer optional but an essential step in protecting user data and critical systems. As always, the code for this example is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-mfa">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-security-7-mfa">Multi-Factor Authentication in Spring Security 7</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/953487335/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953487335/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953487335/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2017%2f08%2fSpring-Security-2.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953487335/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953487335/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953487335/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-security-7-mfa#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-security-7-mfa/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/~/953487335/0/baeldung~MultiFactor-Authentication-in-Spring-Security/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2017/08/Spring-Security-2-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-aot-class-loading-linking</feedburner:origLink>
		<title>Ahead-of-Time Class Loading &#038; Linking</title>
		<link>https://feeds.feedblitz.com/~/953194679/0/baeldung~AheadofTime-Class-Loading-Linking</link>
					<comments>https://feeds.feedblitz.com/~/953194679/0/baeldung~AheadofTime-Class-Loading-Linking#respond</comments>
		
		<dc:creator><![CDATA[Polivakha Mikhail]]></dc:creator>
		<pubDate>Fri, 03 Apr 2026 18:40:45 +0000</pubDate>
				<category><![CDATA[JVM]]></category>
		<category><![CDATA[JVM Flags]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203357</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>JEP 514 and JEP 515 are both part of the OpenJDK Project Leyden effort to improve Java startup and warmup performance. Learn how they work together.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/953194679/0/baeldung~AheadofTime-Class-Loading-Linking">Ahead-of-Time Class Loading & Linking</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953194679/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953194679/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 title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953194679/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953194679/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953194679/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-aot-class-loading-linking#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-aot-class-loading-linking/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>JDK 24 introduced AOT Cache with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/483">JEP 483</a>. This cache allows applications to start faster by pre-loading and also pre-linking classes. However, the workflow of creating the cache effectively required two separate <i>java</i> invocations.</p>
<p>As a result, JDK 25 improves on that with two new JEPs. First, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/514">JEP 514</a> simplifies the AOT cache creation into a single command. And second, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/jeps/515">JEP 515</a> extends the cache to store method execution profiles, improving application warmup time.</p>
<p>In this article, we&#8217;ll explore both new JEPs and see how they work together. It is also important to note that we&#8217;re going ot need JDK 25 to take advantage of these features.</p>
<h2 id="bd-how-aot-cache-worked-in-jdk-24" data-id="how-aot-cache-worked-in-jdk-24">2. How AOT Cache Worked in JDK 24</h2>
<div class="bd-anchor" id="how-aot-cache-worked-in-jdk-24"></div>
<p>Before we delve into the JDK 25 ways of doing AOT Caches, let&#8217;s quickly recap how AOT caching worked with JDK 24.</p>
<p>To create an AOT cache back then, we had to invoke the <i>java</i> utility twice, and thus create two separate <em>java</em> processes. The first invocation runs the application in record mode. It means that Runtime observes how the application behaves during a training run and saves that information into an AOT configuration file:</p>
<pre><code class="language-bash">$ java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -cp app.jar com.example.App</code></pre>
<p>The second invocation uses that configuration generated on the previous step to actually build the cache:</p>
<pre><code class="language-bash">$ java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf  -XX:AOTCache=app.aot</code></pre>
<p>And finally, we can run the application with the generated cache, which, theoretically, should significantly reduce startup time:</p>
<pre><code class="language-bash">$ java -XX:AOTCache=app.aot -cp app.jar com.example.App</code></pre>
<p>This workflow, while it of course works, leaves us with a temporary configuration file and requires managing two separate commands. The experienced readers may note that a similar clumsy process with two phases was with AppCDS archives (effectively, a predecessor to AOT Cache). So, JEP 514 addresses exactly that for AOT Caches now.</p>
<h2 id="bd-one-shot-aot-cache-creation-jep-514" data-id="one-shot-aot-cache-creation-jep-514">3. One-Shot AOT Cache Creation (JEP 514)</h2>
<div class="bd-anchor" id="one-shot-aot-cache-creation-jep-514"></div>
<p>JEP 514 introduces a new command-line non-standard (<i>-XX</i>) VM option: <i>AOTCacheOutput</i>. When we use this option alone, without any other AOT flags, the launcher automatically splits the invocation into two internal sub-invocations—one for training and one for cache creation.</p>
<p>So, instead of the two-step workflow above, we can simply do:</p>
<pre><code class="language-bash">$ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App</code></pre>
<p>This single command replaces the two commands that were initially used to create the AOT Cache. The JVM runs the application as a training exercise, records the dynamics, and then just creates the AOT cache in one shot.</p>
<p>The production command (providing the cache to the production workload) remains the same since JDK 24:</p>
<pre><code class="language-bash">$ java -XX:AOTCache=app.aot -cp app.jar com.example.App</code></pre>
<p>But here is the thing &#8211; <strong>there is still a two-phase procedure done in the background</strong>. Effectively, the <i>java</i> launcher creates two subprocesses to complete the cache creation. That is important, and it has its implications.</p>
<h2 id="bd-the-downsides-of-the-one-shot-approach" data-id="the-downsides-of-the-one-shot-approach">4. The Downsides of the One-Shot Approach</h2>
<div class="bd-anchor" id="the-downsides-of-the-one-shot-approach"></div>
<p>One may think that a two-process setup is just the obvious choice, and it is the way to go &#8211; not quite.</p>
<p>As mentioned, these are two <em>distinct</em> java processes that are going to get launched, and both of them have their own heaps. And because they are both launched by the java launcher process (literally the one that is created by calling the <i>java</i> binary), the peak memory consumption is potentially doubled. So, if we specify <i>-Xmx4g</i>, the one-step workflow, potentially, at most, will need 8GB of heap memory in total to complete.</p>
<p>So, the one-step workflow is great for most scenarios, but the two-step approach also has its place, in particular in memory-constrained environments. It may easily be the case that the cloud VM that is going to host our application will not have sufficient RAM resources to serve the double-sized heap. In that case, the explicit two-step workflow is the preferred option.</p>
<h2 id="bd-aot-method-profiling-jep-515" data-id="aot-method-profiling-jep-515">5. AOT Method Profiling (JEP 515)</h2>
<div class="bd-anchor" id="aot-method-profiling-jep-515"></div>
<p>While JEP 514 simplifies the process of AOT cache creation, JEP 515 enhances what information the cache stores.</p>
<p>To understand JEP 515, we need to recall how HotSpot reaches peak performance. The JIT compiler identifies hot methods (the methods that are executed relatively frequently) and then compiles them to optimized native code. But in order to do this, it has to collect some profiling information for those methods. And this process takes time. Usually, this time is called a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jvm-start-up">warmup period</a>, and during this period, the application runs slower than it potentially can.</p>
<p>And frankly, it is often the case that the execution patterns of our application are roughly the same. Our production workloads often took similar <i>if</i> branches under the same circumstances. So the profiling information won&#8217;t really change from one app launch to another. Thus, it also makes sense to cache it ahead of time.</p>
<p>So, JEP 515 solves this by extending the AOT cache to include method execution profiles from the training run. <strong>When the application starts in production, those profiles are instantly available, so the JIT compiler can begin generating optimized code right away, without waiting for warmup.</strong></p>
<p>The cool thing is that we don&#8217;t need to change the launch command, let alone the application code, to benefit from this feature. Profiling data is automatically collected during the training run and then stored in the AOT cache. So, this just works out-of-the-box with the one-step workflow from JEP 514:</p>
<pre><code class="language-bash"># Training + cache creation (profiles are included automatically)
$ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App
# Production run (benefits from both cached classes and cached profiling info)
$ java -XX:AOTCache=app.aot -cp app.jar com.example.App
</code></pre>
<p>In the case above, the JVM starts with the code cache that already contains certain profiling information.</p>
<h2 id="bd-runtime-profiling-vs-cached-profiling" data-id="runtime-profiling-vs-cached-profiling">6. Runtime Profiling vs. Cached Profiling</h2>
<div class="bd-anchor" id="runtime-profiling-vs-cached-profiling"></div>
<p>An important note here is that cached profiles don&#8217;t prevent additional profiling during production. The HotSpot JVM continues to profile and optimize the application as it runs, combining the benefits of AOT profiles, online profiling, and JIT compilation.</p>
<p><strong>It is important since an application&#8217;s behavior in production can still possibly diverge from what was observed during the training run</strong>. Cached profiles just give the JIT a quick start, allowing it to compile some methods sooner. As the app runs, the HotSpot re-evaluates its understanding of workload patterns and then recompiles methods if needed.</p>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>JEP 514 and JEP 515 are both part of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://openjdk.org/projects/leyden">OpenJDK Project Leyden</a> effort to improve Java startup and warmup performance. JEP 514 brings a practical quality-of-life improvement — collapsing the two-step AOT cache creation into a single command. While often the preferred approach, still, in memory-constrained environments, the two-phase process might be the right way to go.</p>
<p>JEP 515 enriches the cached data by including method profiles, so the JIT compiler can start compiling from the very start of the app.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-aot-class-loading-linking">Ahead-of-Time Class Loading & Linking</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/953194679/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953194679/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953194679/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 title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953194679/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953194679/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953194679/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-aot-class-loading-linking#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-aot-class-loading-linking/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/~/953194679/0/baeldung~AheadofTime-Class-Loading-Linking/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>
<item>
<feedburner:origLink>https://www.baeldung.com/jackson-deserialization-multi-param-constructor</feedburner:origLink>
		<title>JSON Deserialization with Multiple Parameters Constructor Using Jackson</title>
		<link>https://feeds.feedblitz.com/~/953193365/0/baeldung~JSON-Deserialization-with-Multiple-Parameters-Constructor-Using-Jackson</link>
					<comments>https://feeds.feedblitz.com/~/953193365/0/baeldung~JSON-Deserialization-with-Multiple-Parameters-Constructor-Using-Jackson#respond</comments>
		
		<dc:creator><![CDATA[Michael Krimgen]]></dc:creator>
		<pubDate>Fri, 03 Apr 2026 18:35:46 +0000</pubDate>
				<category><![CDATA[Jackson]]></category>
		<category><![CDATA[Java Constructor]]></category>
		<category><![CDATA[Serialization]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203337</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>Learn how to deserialize JSON into a Java object using multi-parameter constructors.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/953193365/0/baeldung~JSON-Deserialization-with-Multiple-Parameters-Constructor-Using-Jackson">JSON Deserialization with Multiple Parameters Constructor Using Jackson</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953193365/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953193365/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953193365/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953193365/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953193365/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/jackson-deserialization-multi-param-constructor#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/jackson-deserialization-multi-param-constructor/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>In this tutorial, we&#8217;ll learn how to deserialize JSON into Java objects using multi-parameter constructors with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson">Jackson</a>.</p>
<p><strong>By default, Jackson requires a default constructor that doesn&#8217;t accept any parameters.</strong> The fields are set using setter methods or reflection. If we want Jackson to use a non-default constructor, we need to annotate that constructor with the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson-annotations"><em>@JsonCreator</em> annotation</a>. This annotation can be applied to constructors and static factory methods of records and enums. In this tutorial, we&#8217;ll have a look at all these cases.</p>
<p>We&#8217;ll also look at the options Jackson provides to reduce the number of annotations needed for deserialization.</p>
<h2 id="bd-setup" data-id="setup">2. Setup</h2>
<div class="bd-anchor" id="setup"></div>
<h3 id="bd-1-maven-dependencies" data-id="1-maven-dependencies">2.1. Maven Dependencies</h3>
<div class="bd-anchor" id="1-maven-dependencies"></div>
<p>In addition to the basic <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core">Jackson dependency</a>, we need <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-parameter-names">Jackson&#8217;s parameter names module</a>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-core&lt;/artifactId&gt;
    &lt;version&gt;2.17.2&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.module&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-module-parameter-names&lt;/artifactId&gt;
    &lt;version&gt;2.17.2&lt;/version&gt;
&lt;dependency&gt;
</code></pre>
<h3 id="bd-2-java-classes" data-id="2-java-classes">2.2. Java Classes</h3>
<div class="bd-anchor" id="2-java-classes"></div>
<p>Throughout the tutorial, we&#8217;ll use the <em>Ticket</em> class:</p>
<pre><code class="language-java">public class Ticket {
    @JsonProperty("event")
    private String eventName;
    private String guest;
    private final Currency currency;
    private final int price;
    public Ticket() {
        this.price = 0;
        this.currency = Currency.EUR;
    }
    public void setGuest(String guest) {
        this.guest = guest;
    }
    
    // getters for all attributes
}</code></pre>
<p>Note that we defined a setter method only for the guest attributes, while providing getters for all attributes. Here, we intentionally omit the setters for the three other attributes to demonstrate the behavior of the deserialization. Also, we use <em>@JsonProperty</em> on the <em>eventName</em> attribute to demonstrate the different ways Jackson offers to serialize a field.</p>
<p>Here&#8217;s the currency enum:</p>
<pre><code class="language-java">public enum Currency {
    EUR("Euro", "cent"),
    GBP("Pound sterling", "penny"),
    CHF("Swiss franc", "Rappen");
    private String fullName;
    private String fractionalUnit;
    Currency(String fullName, String fractionalUnit) {
        this.fullName = fullName;
        this.fractionalUnit = fractionalUnit;
    }
}</code></pre>
<h3 id="bd-3-json-to-deserialize" data-id="3-json-to-deserialize">2.3. JSON To Deserialize</h3>
<div class="bd-anchor" id="3-json-to-deserialize"></div>
<p>Here&#8217;s the JSON that we want to deserialize in the examples:</p>
<pre><code class="language-json">{
  "event": "Devoxx",
  "guest": "Maria Monroe",
  "currency": "EUR",
  "price": 50
}</code></pre>
<h2 id="bd-default-deserialization" data-id="default-deserialization">3. Default Deserialization</h2>
<div class="bd-anchor" id="default-deserialization"></div>
<p>We need to define a default, no-argument constructor because the class has a final attribute. <strong>If we only define a constructor that takes the currency and price as arguments, Jackson won’t be able to deserialize the object:</strong></p>
<pre><code class="language-java">public Ticket(Currency currency, int price) {
    this.price = price;
    this.currency = currency;
}</code></pre>
<p>Jackson will <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson-exception">throw an exception</a>:</p>
<pre><code class="language-bash">com.fasterxml.jackson.databind.exc.MismatchedInputException: 
  Cannot construct instance of `com.baeldung.jackson.multiparameterconstructor.Ticket` 
  (no Creators, like default constructor, exist): cannot deserialize from Object value</code></pre>
<h2 id="bd-deserialization-using-jsoncreator" data-id="deserialization-using-jsoncreator">4. Deserialization Using <em>@JsonCreator</em></h2>
<div class="bd-anchor" id="deserialization-using-jsoncreator"></div>
<p>To indicate which constructor should be used for deserialization, we can use the <em>@JsonCreator</em> annotation.</p>
<h3 id="bd-1-defining-a-constructor-with-jsoncreator-and-jsonproperty" data-id="1-defining-a-constructor-with-jsoncreator-and-jsonproperty">4.1. Defining a Constructor With <em>@JsonCreator</em> and <em>@JsonProperty</em></h3>
<div class="bd-anchor" id="1-defining-a-constructor-with-jsoncreator-and-jsonproperty"></div>
<p>If we want to use the two-argument constructor, we need to annotate it with <em>@JsonCreator</em> and annotate the parameters with <em>@JsonProperty</em>:</p>
<pre><code class="language-java">@JsonCreator
public Ticket(@JsonProperty("currency") Currency currency, @JsonProperty("price") int price) {
    this.price = price;
    this.currency = currency;
}</code></pre>
<p>Jackson will deserialize the JSON as follows:</p>
<ul>
<li>The two attributes, <em>currency</em> and <em>price,</em> are set in the constructor.</li>
<li>The <em>guest</em> attribute is set using its setter method.</li>
<li>The <em>eventName</em> attribute doesn&#8217;t have a setter method and is set using reflection.</li>
</ul>
<p><strong>If there is no setter method for an attribute, Jackson sets the attribute using reflection based on its name.</strong> If the Java attribute name differs from the JSON field name, we can use the <em>@JsonProperty</em> annotation to define the JSON field name. In our example, we annotate the <em>eventName</em> attribute with <em>@JsonProperty(&#8220;event&#8221;)</em> to indicate that the JSON field name is event<em>,</em> while the Java attribute name is <em>eventName</em>.</p>
<p>We can annotate only one constructor with <em>@JsonCreator</em>. If we annotate a second constructor in addition to the one we have already defined:</p>
<pre><code class="language-java">@JsonCreator
public Ticket(@JsonProperty("currency") Currency currency, @JsonProperty("price") int price, @JsonProperty("guest") String guest) {
    this.price = price;
    this.currency = currency;
    this.guest = guest;
}</code></pre>
<p>Jackson will throw an exception:</p>
<pre><code class="language-bash">com.fasterxml.jackson.databind.JsonMappingException: 
  com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
    Conflicting property-based creators: 
    already had explicitly marked creator [constructor for `com.baeldung.jackson.multiparameterconstructor.Ticket` (2 args), 
    annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}, 
    encountered another: [constructor for `com.baeldung.jackson.multiparameterconstructor.Ticket` (3 args), 
    annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}</code></pre>
<h3 id="bd-2-constructor-without-jsonproperty" data-id="2-constructor-without-jsonproperty">4.2. Constructor Without <em>@JsonProperty</em></h3>
<div class="bd-anchor" id="2-constructor-without-jsonproperty"></div>
<p>Jackson uses reflection to map JSON field names to Java class attributes. This works for class attributes but doesn&#8217;t work for method parameter names. Therefore, if we define a constructor without <em>@JsonProperty</em> annotations:</p>
<pre><code class="language-java">@JsonCreator
public Ticket(Currency currency, int price, String guest) {
    this.price = price;
    this.currency = currency;
    this.guest = guest;
}</code></pre>
<p>We get an exception:</p>
<pre><code class="language-bash">com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
  Invalid type definition for type `com.baeldung.jackson.multiparameterconstructor.Ticket`: 
  Argument #0 of constructor [constructor for `com.baeldung.jackson.multiparameterconstructor.Ticket` (2 args), 
  annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)} 
  has no property name (and is not Injectable): can not use as property-based Creator</code></pre>
<p>Java does not retain method parameter names at runtime, so we need to annotate the parameters with <em>@JsonProperty</em> to specify the JSON field name that should be mapped to each parameter.</p>
<p><strong>If we want to avoid annotating the parameters with <em>@JsonProperty</em>, we can register the <em>ParameterNamesModul</em>e and add the <em>parameters</em> flag to the compiler.</strong></p>
<p>First, we need to add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-parameter-names">Maven dependency</a>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.module&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-module-parameter-names&lt;/artifactId&gt;
    &lt;version&gt;2.21.1&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Then, we need to register the <em>ParameterNamesModule </em>with the object mapper:</p>
<pre><code class="language-java">ObjectMapper mapper = JsonMapper.builder()
  .constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
  .addModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))
  .build();</code></pre>
<p>And add the <em>parameters</em> flag to the compiler:</p>
<pre><code class="language-xml">&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;compilerArgs&gt;
            &lt;arg&gt;-parameters&lt;/arg&gt;
        &lt;/compilerArgs&gt;
    &lt;/configuration&gt;
&lt;/plugin&gt;</code></pre>
<h3 id="bd-3configuration-with-constructordetector" data-id="3configuration-with-constructordetector">4.3. Configuration With <em>ConstructorDetector</em></h3>
<div class="bd-anchor" id="3configuration-with-constructordetector"></div>
<p>We still need to add the <em>@JsonCreator</em> annotation to mark the constructor that we want to use for deserialization.</p>
<p>As of Jackson 2.12, we can register a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jackson-constructordetector-deserialization">ConstructorDetector </a>to specify the constructor that should be used for deserialization without the need to annotate it with <em>@JsonCreator:</em></p>
<pre><code class="language-java">ObjectMapper mapper = JsonMapper.builder()
  .constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
  .addModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))
  .constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
  .build();</code></pre>
<p><strong>Notably,  if we configure Jackson to detect the constructor and also annotate one of multiple constructors with <em>@JsonCreator</em>, then the annotated constructor has preference over a constructor that would be detected otherwise.</strong></p>
<h2 id="bd-records" data-id="records">5. Records</h2>
<div class="bd-anchor" id="records"></div>
<p>Jackson can deserialize records using the canonical constructor, which is present by default. Let&#8217;s consider the following record:</p>
<pre><code class="language-java">public record Guest(@JsonProperty("firstname") String firstname, @JsonProperty("surname") String surname) {}</code></pre>
<p>And the following JSON:</p>
<pre><code class="language-json">{
  "firstname": "Maria",
  "surname": "Monroe"
}</code></pre>
<p><strong>Jackson can deserialize JSON to Java records without the need for a no-argument constructor.</strong> If we register the <em>ParameterNamesModule</em> and compile with the <em>parameters</em> flag, we don&#8217;t need to annotate the record components with <em>@JsonProperty</em>:</p>
<pre><code class="language-java">public record Guest(String firstname, String surname) {}</code></pre>
<p>In some cases, we might want to customize the canonical constructor:</p>
<pre><code class="language-java">public Guest(String firstname, String surname) {
    this.firstname = firstname;
    this.surname = surname;
    // some validation
}</code></pre>
<p>Again, we don&#8217;t need to annotate the constructor with <em>@JsonCreator</em> because Jackson will use the canonical constructor by default.</p>
<p>One case where we do need the <em>@JsonCreator</em> annotation is when we want to add a static factory method:</p>
<pre><code class="language-java">@JsonCreator
public static Guest fromJson(String firstname, String surname) {
    // some validation
    return new Guest(firstname, surname);
}</code></pre>
<p>A difference to using a constructor is that a static factory method can have additional arguments:</p>
<pre><code class="language-java">@JsonCreator
public static Guest fromJson(String firstname, String surname, int id) {
    // some validation
    return new Guest(firstname, surname);
}</code></pre>
<p>Whereas a non-canonical constructor won&#8217;t be used even if it&#8217;s annotated with <em>@JsonCreator</em>:</p>
<pre><code class="language-java">@JsonCreator
public Guest(String firstname, String surname, int id) {
    this(firstname, surname)
    // some validation
}</code></pre>
<p>Jackson will ignore this constructor and use the canonical constructor instead.</p>
<h2 id="bd-enums" data-id="enums">6. Enums</h2>
<div class="bd-anchor" id="enums"></div>
<p><em>@JsonCreator</em> can also be used to deserialize enums. By default, Jackson <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson-serialize-enums">deserializes enums</a> using their name.</p>
<p>We can change the default behavior by annotating the enum with <em>@JsonFormat</em>:</p>
<pre><code class="language-java">@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Currency {
    EUR("Euro", "cent"),
    GBP("Pound sterling", "penny"),
    CHF("Swiss franc", "Rappen");
    private String fullName;
    private String fractionalUnit;
    Currency(String fullName, String fractionalUnit) {
        this.fullName = fullName;
        this.fractionalUnit = fractionalUnit;
    }
    
    // getters for all attributes
}</code></pre>
<p>The enum value <em>EUR</em> will then be serialized to the following JSON:</p>
<pre><code class="language-json">{
  "fullName": "Euro",
  "fractionalUnit": "cent"
}</code></pre>
<p>However, the deserialization will fail with the following exception:</p>
<pre><code class="language-bash">com.fasterxml.jackson.databind.exc.MismatchedInputException: 
  Cannot deserialize value of type `com.baeldung.jackson.multiparameterconstructor.Currency` 
  from Object value (token `JsonToken.START_OBJECT`)</code></pre>
<p><strong>That&#8217;s because Jackson attempts to deserialize the enum based on its name: <em>EUR</em>.</strong> We might think that annotating the constructor with <em>@JsonCreator</em> would solve the problem:</p>
<pre><code class="language-java">@JsonCreator
Currency(String fullName, String fractionalUnit) {
    this.fullName = fullName;
    this.fractionalUnit = fractionalUnit;
}</code></pre>
<p><strong>That doesn&#8217;t work because enum constructors are private and cannot be used by Jackson.</strong> The solution is to define a
<br>
static factory method and annotate it with <em>@JsonCreator</em>:</p>
<pre><code class="language-java">@JsonCreator
public static Currency fromJsonString(String fullName, String fractionalUnit) {
    for (Currency c : Currency.values()) {
        if (c.fullName.equalsIgnoreCase(fullName) &amp;&amp; c.fractionalUnit.equalsIgnoreCase(fractionalUnit)) {
            return c;
        }
    }
    throw new IllegalArgumentException("Unknown currency: " + fullName + " " + franctionalUnit);
}</code></pre>
<h2 id="bd-conclusion" data-id="conclusion">7. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we&#8217;ve learned how to deserialize JSON into a Java object using multi-parameter constructors. We&#8217;ve seen how to use the <em>@JsonCreator</em> in Java classes, enums, and records. In addition, we&#8217;ve learnt that the parameter names module and the constructor detector setting help reduce the number of necessary annotations.</p>
<p>As usual, the code in this article is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/jackson-modules/jackson-custom-conversions">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/jackson-deserialization-multi-param-constructor">JSON Deserialization with Multiple Parameters Constructor Using Jackson</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/953193365/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953193365/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953193365/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953193365/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953193365/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953193365/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/jackson-deserialization-multi-param-constructor#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/jackson-deserialization-multi-param-constructor/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/~/953193365/0/baeldung~JSON-Deserialization-with-Multiple-Parameters-Constructor-Using-Jackson/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-11-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-jmod-file-format</feedburner:origLink>
		<title>JMOD File Format in Java</title>
		<link>https://feeds.feedblitz.com/~/953193368/0/baeldung~JMOD-File-Format-in-Java</link>
					<comments>https://feeds.feedblitz.com/~/953193368/0/baeldung~JMOD-File-Format-in-Java#respond</comments>
		
		<dc:creator><![CDATA[Olayemi Michael]]></dc:creator>
		<pubDate>Fri, 03 Apr 2026 18:29:17 +0000</pubDate>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JAR]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/java-jmod-file-format</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 what a JMOD file is, how it differs from a JAR file, and when to use it.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/953193368/0/baeldung~JMOD-File-Format-in-Java">JMOD File Format in Java</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953193368/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953193368/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 title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953193368/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953193368/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953193368/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-jmod-file-format#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-jmod-file-format/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-10.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p>In this tutorial, we&#8217;ll deep dive into what JMOD is and how it differs from JAR files. Then, we&#8217;ll create a sample modular project, package it as a JMOD file, and use <em>jlink</em> to generate a minimal Java runtime tailored specifically to the application.</p>
<h2 id="bd-whats-jmod" data-id="whats-jmod">2. What&#8217;s JMOD?</h2>
<div class="bd-anchor" id="whats-jmod"></div>
<p>Before Java 9, Java applications were packaged primarily as <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-create-jar">JAR files</a> with build tools such as Maven and Gradle. With the introduction of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-modularity">Java Platform Module System</a> in Java 9, Java gained a formal module system and introduced the JMOD file format.</p>
<p><strong>A JMOD file is a packaging format for Java modules that are intended to be used during compile and link time, but not directly at runtime</strong>.</p>
<p>Unlike JAR files, we can&#8217;t place JMOD files on the classpath or module path when running an application.</p>
<p>Like JAR files, JMOD files can contain compiled class files and resources. However, JMOD files can also include additional artifacts such as native libraries, configuration files, and legal notices.</p>
<h3 id="bd-1-its-relationship-with-jpms" data-id="1-its-relationship-with-jpms">2.1. Its Relationship With JPMS</h3>
<div class="bd-anchor" id="1-its-relationship-with-jpms"></div>
<p>Moreover, JMOD is tightly related to JPMS. It serves as a packaging format for modules that may need more than just compiled bytecode files.</p>
<p>In the JPMS architecture, source code is compiled into bytecode. Then, the bytecode is packaged into modules (JAR or JMOD). Then, at link time, we can use <em>jlink</em> to assemble modules into a custom runtime image.</p>
<p>Link time is the phase between compilation and execution. During this phase, the Java linker analyzes module dependencies and bundles only the required modules into a runtime image. JMOD files are specifically designed to participate in the linking process.</p>
<h3 id="bd-2-the-purpose-of-jmod-and-how-it-differs-from-modular-jars" data-id="2-the-purpose-of-jmod-and-how-it-differs-from-modular-jars">2.2. The Purpose of JMOD and How It Differs From Modular JARs</h3>
<div class="bd-anchor" id="2-the-purpose-of-jmod-and-how-it-differs-from-modular-jars"></div>
<p>While JPMS supports modular JAR files, JMOD was introduced to address the limitations of JAR packaging. A modular JAR contains compiled classes and resources and can be used directly at runtime.</p>
<p>On the other hand, a JMOD file may contain native code and additional metadata. It&#8217;s intended for link-time processing and can&#8217;t be executed directly. Unlike a JAR file, it isn&#8217;t meant to be published to repositories such as Maven Central.</p>
<p>Its primary purpose is to support the creation of custom runtime images using <em>jlink</em>. For instance, packaging an application together with a full JRE can significantly increase distribution size. Instead, JMOD enables the creation of a custom Java runtime that contains only the modules required by the application. This results in a smaller and more learner-friendly distribution size.</p>
<h2 id="bd-practical-example-creating-a-custom-jre" data-id="practical-example-creating-a-custom-jre">3. Practical Example: Creating a Custom JRE</h2>
<div class="bd-anchor" id="practical-example-creating-a-custom-jre"></div>
<p>To better understand how the JMOD file format works, let&#8217;s build a simple modular Java application and use it to create a custom runtime image.</p>
<h3 id="bd-1-sample-application" data-id="1-sample-application">3.1. Sample Application</h3>
<div class="bd-anchor" id="1-sample-application"></div>
<p>Let&#8217;s bootstrap a simple Java project that logs &#8220;<em>Hello Baeldung!</em>&#8221; to the console:</p>
<pre><code class="language-java">public class Hello {
    private static final Logger LOG = Logger.getLogger(Main.class.getName());
    public static void main(String[] args) {
        LOG.info("Hello Baeldung!");
    }
}</code></pre>
<p>In the code above, we define a class named <em>Hello</em> and use the <em>java.util.logging</em> API to log a message to the console.</p>
<p>Next, let&#8217;s make the project a Java module by adding a <em>module-info.java</em> file at the root of the module source directory:</p>
<pre><code class="language-java">module com.baeldung.jmod_sample {
    requires java.logging;
}</code></pre>
<p>Here, <em>com.baeldung.jmod_sample</em> is the module name. While every module implicitly depends on <em>java.base</em>, additional modules such as <em>java.logging</em> must be implicitly declared.</p>
<p>Next, let&#8217;s compile our program:</p>
<pre><code class="language-bash">$ javac -d output $(find -name \*.java)</code></pre>
<p>The command above locates all <em>.java</em> files inside the <em>src</em> directory and compiles them. Then, it outputs the generated <em>.class</em> files into the <em>output</em> directory.</p>
<h3 id="bd-2-packaging-to-jmod" data-id="2-packaging-to-jmod">3.2. Packaging to JMOD</h3>
<div class="bd-anchor" id="2-packaging-to-jmod"></div>
<p>Next, let&#8217;s prepare our modular application for a custom runtime creation by packaging it into a JMOD file.</p>
<p>To create a JMOD file, we can use the <em>jmod create</em> command:</p>
<pre><code class="language-bash">$ jmod create \
  --class-path output/ \
  --main-class com.baeldung.jmod_sample.Hello \
  --module-version 1.0.0 -p output hello.jmod</code></pre>
<p>Here, <em>&#8211;class-path output/</em> specifies the directory containing the compiled classes. Also, we define the entry point of the module and the name of the generated JMOD file.</p>
<p>After executing the command, a <em>hello.jmod</em> file is created in the current directory.</p>
<p>Next, let&#8217;s inspect the JMOD file to see what it contains by running the <em>jmod describe</em> command:</p>
<pre><code class="language-bash">$ jmod describe hello.jmod</code></pre>
<p>This outputs the content of the file to the console:</p>
<pre><code class="language-bash">com.baeldung.jmod_sample@1.0.0
requires java.base mandated
requires java.logging
contains com.baeldung.jmod_sample
main-class com.baeldung.jmod_sample.Hello</code></pre>
<p>This confirms that the JMOD file correctly encapsulates the compile module and its metadata, making it ready for use during link time with <em>jlink</em>.</p>
<h3 id="bd-3-creating-a-custom-runtime-using-jlink" data-id="3-creating-a-custom-runtime-using-jlink">3.3. Creating a Custom Runtime Using <em>jlink</em></h3>
<div class="bd-anchor" id="3-creating-a-custom-runtime-using-jlink"></div>
<p>Now that we have generated our JMOD file, let&#8217;s use it create a custom Java runtime using <em>jlink</em>:</p>
<pre><code class="language-bash">$ jlink \
  --module-path $JAVA_HOME/jmods:hello.jmod \
  --add-modules com.baeldung.jmod_sample \
  --launcher hello=com.baeldung.jmod_sample/com.baeldung.jmod_sample.Hello \
  --strip-debug \
  --compress=2 \
  --no-header-files \
  --no-man-pages \
  --output custom-runtime-min</code></pre>
<p>Here, the  <em>&#8211;module-path $JAVA_HOME/jmods:hello.jmod</em> specifies where jlink should locate require modules. Next, the <em>&#8211;add-modules</em> option indicates the root module to include in the runtime image.</p>
<p>Then we use the <em>launcher</em> option to create a launcher script that runs the <em>Hello</em> class. After executing the command, <em>jlink</em> produces a new directory named &#8220;<em>custom-runtme-min</em>&#8220;.</p>
<p>We can check the size of the generated runtime image:</p>
<pre><code class="language-bash">$ du -sh custom-runtime-min</code></pre>
<p>Here&#8217;s the output:</p>
<pre><code class="language-bash">35M     custom-runtime-min/</code></pre>
<p>The resulting runtime is approximately 35 MB, significantly smaller than a full JDK installation, which typically exceeds 400 MB.</p>
<p>Let&#8217;s verify which modules were bundled into the runtime image:</p>
<pre><code class="language-bash">$ ./custom-runtime-min/bin/java --list-modules</code></pre>
<p>Here&#8217;s the output:</p>
<pre><code class="language-bash">com.baeldung.jmod_sample@1.0.0
java.base@25.0.2
java.logging@25.0.2</code></pre>
<p>This confirms that only the required modules were included.</p>
<p>Finally, let&#8217;s run the generate launcher:</p>
<pre><code class="language-bash">$ ./custom-runtime-min/bin/hello</code></pre>
<p>The command above produces:</p>
<pre><code class="language-bash">Mar 01, 2026 10:15:38 AM com.baeldung.jmod_sample.Hello main
INFO: Hello Baeldung!</code></pre>
<p>The application runs successfully using the custom runtime image.</p>
<h2 id="bd-conclusion" data-id="conclusion">4. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we learned what a JMOD file is, what it can contain, how it differs from a  JAR file, and when it should be used. Also, we built a simple modular Java application, packaged it into a JMOD file, and used it with <em>jlink</em> to generate a lean, custom Java runtime image tailored specifically to our application.</p>
<p>As usual, the complete source code for the example is available <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-9-jigsaw">over on GitHub</a>.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-jmod-file-format">JMOD File Format in Java</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/953193368/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953193368/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953193368/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 title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953193368/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953193368/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953193368/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-jmod-file-format#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-jmod-file-format/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/~/953193368/0/baeldung~JMOD-File-Format-in-Java/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-weekly-640</feedburner:origLink>
		<title>Java Weekly, Issue 640</title>
		<link>https://feeds.feedblitz.com/~/953174849/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/953174849/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 03 Apr 2026 13:52:02 +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=203370</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>Instrument your code quickly and document your high-level technical context carfully.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/953174849/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 640</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953174849/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953174849/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953174849/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953174849/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953174849/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-640#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-640/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://blog.frankel.ch/tip-opentelemetry-projects/">&gt;&gt; One tip for successful OpenTelemetry projects</a></strong> [<span style="color: #993300;">frankel.ch</span>]</p>
<p>Adopting OpenTelemetry doesn&#8217;t have to mean instrumenting the entire codebase. Here&#8217;s a pragmatic way to get <strong>observability up and running fast</strong>.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/grails-isnt-done-yet-part-2-eol-spring-boot-and-what-comes-next/" target="_blank" rel="noopener"><strong>Grails Isn&#8217;t Done Yet (Part 2): EOL, Spring Boot, and What Comes Next</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/03/using-spring-data-jpa-with-kotlin/" target="_blank" rel="noopener"><strong>Using Spring Data JPA with Kotlin</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/03/ai-assisted-java-application-development-with-agent-skills/" target="_blank" rel="noopener"><strong>AI-Assisted Java Application Development with Agent Skills</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.jooq.org/managing-sensitive-data-in-jooq-3-21-logs/" target="_blank" rel="noopener"><strong>Managing Sensitive Data in jOOQ 3.21+ Logs</strong></a> [<span style="color: #800000;">jooq.org</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://spring.io/blog/2026/03/26/a-bootiful-podcast-dgm" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Daniel Garnier-Moiroux on MCP Security</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://toomuchcoding.com/publication/2026-03-30-goto-scc-contract/" target="_blank" rel="noopener"><strong>A Typo Led to the Creation of Spring Cloud Contract</strong></a> [<span style="color: #800000;">toomuchcoding.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://nipafx.dev/inside-java-newscast-109/" target="_blank" rel="noopener"><strong>Analysing Crashed JVMs &#8211; Inside Java Newscast #109</strong></a> [<span style="color: #800000;">nipafx.dev</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/03/27/spring-modulith-2-1-m4-2-0-5-and-1-4-10-released" target="_blank" rel="noopener"><strong>Spring Modulith 2.1 M4, 2.0.5, and 1.4.10 released</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/03/26/spring-ai-2-0-0-M4-and-1-1-4-and-1-0-5-available" target="_blank" rel="noopener"><strong>Spring AI 2.0.0-M4, 1.1.4 and 1.0.5 are available now</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/amper/2026/03/amper-0-10/" target="_blank" rel="noopener"><strong>Amper 0.10 – JDK Provisioning, a Maven Converter, Custom Compiler Plugins, and More</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/eclipse/jetty.project/releases/tag/jetty-12.1.8" target="_blank" rel="noopener"><strong>Jetty 12.1.8</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse/jetty.project/releases/tag/jetty-12.0.34" target="_blank" rel="noopener"><strong>12.0.34</strong></a> [<span style="color: #800000;">github.com/eclipse</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.0.10" target="_blank" rel="noopener"><strong>Eclipse Vert.x 5.0.10</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/quarkusio/quarkus/releases/tag/3.34.2" target="_blank" rel="noopener"><strong>Quarkus 3.34.2</strong></a> [<span style="color: #800000;">github.com/quarkusio</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.20" target="_blank" rel="noopener"><strong>Micronaut 4.10.20</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/micronaut-projects/micronaut-core/releases/tag/v4.10.19" target="_blank" rel="noopener"><strong>4.10.19</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/Netflix/zuul/releases/tag/v3.5.7" target="_blank" rel="noopener"><strong>Netflix Zuul 3.5.7</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/grails/grails-core/releases/tag/v7.0.10" target="_blank" rel="noopener"><strong>Grails 7.0.10</strong></a> [<span style="color: #800000;">github.com/grails</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/articles/reduce-friction-ai/encoding-team-standards.html">&gt;&gt; Encoding Team Standards</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>
<p>Instead of relying on tacit knowledge held by senior engineers, encode your team&#8217;s coding standards as executable AI instructions stored in version control. Words to live (or code) by <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<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.scottlogic.com/2026/04/01/beyond-the-hype-is-ai-taking-fun-out-of-software-development.html" target="_blank" rel="noopener"><strong>Beyond the Hype: Is AI taking the fun out of 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://event-driven.io/en/intro_to_example_mapping/" target="_blank" rel="noopener"><strong>The one where Oskar explains Example Mapping</strong></a> [<span style="color: #800000;">event-driven.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/best-practices-for-working-with-ai-agents-subagents-skills-and-mcp/" target="_blank" rel="noopener"><strong>5 Best Practices for Working with AI Agents, Subagents, Skills and MCP</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://dandreamsofcoding.com/2026/03/31/understanding-performance/" target="_blank" rel="noopener"><strong>Understanding Performance</strong></a> [<span style="color: #800000;">dandreamsofcoding.com</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-pick-of-the-week" data-id="pick-of-the-week">3.<strong> Pick of the Week</strong></h2>
<div class="bd-anchor" id="pick-of-the-week"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://kk.org/thetechnium/1000-true-fans/">&gt;&gt; 1,000 True Fans</a></strong> [<span style="color: #800000;">kk.org</span>]</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-640">Java Weekly, Issue 640</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/953174849/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/953174849/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/953174849/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/953174849/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/953174849/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/953174849/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-640#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-640/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/~/953174849/0/baeldung~Java-Weekly-Issue/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-boot-spa-redirect-404</feedburner:origLink>
		<title>Configure Spring Boot to Redirect 404 to a Single Page Application</title>
		<link>https://feeds.feedblitz.com/~/952480355/0/baeldung~Configure-Spring-Boot-to-Redirect-to-a-Single-Page-Application</link>
					<comments>https://feeds.feedblitz.com/~/952480355/0/baeldung~Configure-Spring-Boot-to-Redirect-to-a-Single-Page-Application#respond</comments>
		
		<dc:creator><![CDATA[Hamid Reza Sharifi]]></dc:creator>
		<pubDate>Tue, 31 Mar 2026 15:46:13 +0000</pubDate>
				<category><![CDATA[Spring MVC]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203319</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2017/08/Spring-MVC-1.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 configure Spring Boot so that all unknown routes are redirected to the SPA's index.html</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/952480355/0/baeldung~Configure-Spring-Boot-to-Redirect-to-a-Single-Page-Application">Configure Spring Boot to Redirect 404 to a Single Page Application</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/952480355/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/952480355/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2017%2f08%2fSpring-MVC-1.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/952480355/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/952480355/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/952480355/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-spa-redirect-404#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-spa-redirect-404/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/2017/08/Spring-MVC-1.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/2017/08/Spring-MVC-1.jpg 952w, https://www.baeldung.com/wp-content/uploads/2017/08/Spring-MVC-1-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2017/08/Spring-MVC-1-768x402.jpg 768w" 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>Single-page applications (SPAs) such as those built with <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-react-crud">React</a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-angular-web">Angular</a>, or <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-vue-js">Vue</a> often rely on client-side routing. When served from a Spring Boot backend, refreshing the page or accessing a wrong link sends a request for a non-existent server path. Spring Boot then returns a 404 error.</p>
<p>To make the SPA work correctly, we need to forward all unmatched requests to <em>index.html</em> with HTTP status 200. This lets the frontend router handle the path.</p>
<p>In this tutorial, we&#8217;ll learn how to configure Spring Boot so that all unknown routes are redirected to the SPA&#8217;s <em>index.html</em>.</p>
<h2 id="bd-maven-dependency" data-id="maven-dependency">2. Maven Dependency</h2>
<div class="bd-anchor" id="maven-dependency"></div>
<p>Let’s start by importing the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web"><em>spring-boot-starter-web</em></a> dependency to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;version&gt;3.3.2&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-setting-up-a-basic-spa-with-spring-boot" data-id="setting-up-a-basic-spa-with-spring-boot">3. Setting Up a Basic SPA with Spring Boot</h2>
<div class="bd-anchor" id="setting-up-a-basic-spa-with-spring-boot"></div>
<p>Let&#8217;s consider an SPA deployed with Spring Boot, where the compiled frontend files are placed inside the <em>resources</em> directory:</p>
<pre><code class="language-plaintext">src/main/resources/static</code></pre>
<p>For example, a minimal <em>index.html</em> could look like:</p>
<pre><code class="language-xml">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Baeldung&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;Home Page&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>Next, we create a simple controller to forward the root path:</p>
<pre><code class="language-java">@Controller
public class HomeController {
    @RequestMapping("/")
    public String getHome(){
        return "forward:/index.html";
    }
}</code></pre>
<p>When a user accesses the root path <em>http://localhost:8080/</em><span style="margin: 0px;padding: 0px">, </span>Spring Boot serves <em>index.html</em> correctly.</p>
<p>However, if the user refreshes a client-side route like <em>http://localhost:8080/dashboard,</em> Spring Boot tries to find a server mapping for <em>/dashboard</em> and returns <em>404 Not Found</em>. Instead, we want Spring Boot to return <em>index.html</em> so the SPA router can handle the route.</p>
<h2 id="bd-redirecting-404-not-founderrors" data-id="redirecting-404-not-founderrors">4. Redirecting 404 Not Found Errors</h2>
<div class="bd-anchor" id="redirecting-404-not-founderrors"></div>
<p>In this section, we describe some solutions for redirecting unmatched requests to <em>index.html</em>.</p>
<h3 id="bd-1-using-a-controller-to-forward-unknown-routes" data-id="1-using-a-controller-to-forward-unknown-routes">4.1. Using a Controller to Forward Unknown Routes</h3>
<div class="bd-anchor" id="1-using-a-controller-to-forward-unknown-routes"></div>
<p>Let&#8217;s create a controller that forwards all unmapped paths to <em>index.html</em>:</p>
<pre><code class="language-java">@Controller
public class SpaForwardController {
    @RequestMapping(value = "/{path:[^\\.]*}")
    public String redirect() {
        return "forward:/";
    }
}</code></pre>
<p>The unmatched requests without a file extension are forwarded to &#8220;/&#8221;, which returns <em>index.html</em>.</p>
<p><strong>If a specific mapping exists (for example, <em>@GetMapping(&#8220;/dashboard&#8221;)</em>), Spring selects it with higher precedence, and the catch-all pattern is ignored</strong>. For any other path without a matching handler (such as <em>/settings</em>), the request falls through to the catch-all mapping and gets forwarded to <em>index.html</em>.</p>
<p>Also, we need to support deeper routes like <em>/dashboard/settings</em>. To do this, we can expand the controller mapping:</p>
<pre><code class="language-java">@Controller
public class SpaForwardController {
    @RequestMapping(value = {
        "/{path:[^\\.]*}",
        "/{path:[^\\.]*}/**/{subpath:[^\\.]*}"
    })
    public String redirect() {
        return "forward:/";
    }
}</code></pre>
<h3 id="bd-2-using-a-custom-error-controller" data-id="2-using-a-custom-error-controller">4.2. Using a Custom Error Controller</h3>
<div class="bd-anchor" id="2-using-a-custom-error-controller"></div>
<p><strong>Another approach is to intercept 404 errors and redirect them to the SPA entry point</strong>:</p>
<pre><code class="language-java">@Controller
public class SpaErrorController implements ErrorController {
    @RequestMapping("/error")
    public String handleError() {
        return "forward:/index.html";
    }
}</code></pre>
<p>This method ensures that any unknown route eventually loads the SPA.</p>
<p>However, this approach may also intercept real backend 404 errors, which may not always be desirable.</p>
<h3 id="bd-3-using-webmvcconfigurer" data-id="3-using-webmvcconfigurer">4.3. Using <em>WebMvcConfigurer</em></h3>
<div class="bd-anchor" id="3-using-webmvcconfigurer"></div>
<p><strong>Another approach is to centralize error handling and avoid the need to explicitly match route patterns</strong>.</p>
<p>We can achieve this by implementing the <em>WebMvcConfigurer</em> interface and registering a custom error page:</p>
<pre><code class="language-java">@Configuration
public class WebApplicationConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/notFound")
          .setViewName("forward:/index.html");
    }
    @Bean
    public WebServerFactoryCustomizer&lt;ConfigurableServletWebServerFactory&gt; containerCustomizer() {
        return container -&gt; {
            container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notFound"));
        };
    }
}
</code></pre>
<p>In this configuration, any request that results in a <em>404 Not Found</em> error is redirected to <em>/notFound</em>. This endpoint then forwards the request to <em>index.html</em>.</p>
<p>However, it also means that all 404 errors—including those from backend APIs—are redirected to the SPA, which may not always be desirable.</p>
<h2 id="bd-test-1" data-id="test-1">5. Test</h2>
<div class="bd-anchor" id="test-1"></div>
<p>After starting the Spring Boot application, we can verify the configuration by accessing different URLs.</p>
<p>First, we request the root path:</p>
<pre><code class="language-plaintext">$ curl -i http://localhost:8080/</code></pre>
<p>The response returns <em>200 </em>along with the contents of <em>index.html</em>:</p>
<pre><code class="language-plaintext">HTTP/1.1 200
&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Baeldung&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;Home Page&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>Next, we simulate accessing a client-side route:</p>
<pre><code class="language-plaintext">$ curl -i http://localhost:8080/dashboard</code></pre>
<p>Instead of returning a <em>404 Not Found</em>, the application responds with <em>200</em> and serves <em>index.html</em>. This confirms that the request is forwarded and handled by the SPA router.</p>
<h2 id="bd-conclusion" data-id="conclusion">6. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this article, we explored how to forward unknown routes to <em>index.html</em> when serving a single-page application with Spring Boot. We examined multiple approaches, including using a custom controller, configuring <em>WebMvcConfigurer</em>, and handling errors via a custom error mapping.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-spa-redirect-404">Configure Spring Boot to Redirect 404 to a Single Page Application</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/952480355/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/952480355/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/952480355/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2017%2f08%2fSpring-MVC-1.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/952480355/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/952480355/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/952480355/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-spa-redirect-404#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-spa-redirect-404/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/~/952480355/0/baeldung~Configure-Spring-Boot-to-Redirect-to-a-Single-Page-Application/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2017/08/Spring-MVC-1-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-sonars-transient-serializable-warning</feedburner:origLink>
		<title>Fix Sonar&#8217;s &#8220;Make Transient or Serializable&#8221; Warning in Java</title>
		<link>https://feeds.feedblitz.com/~/952480358/0/baeldung~Fix-Sonars-Make-Transient-or-Serializable-Warning-in-Java</link>
					<comments>https://feeds.feedblitz.com/~/952480358/0/baeldung~Fix-Sonars-Make-Transient-or-Serializable-Warning-in-Java#respond</comments>
		
		<dc:creator><![CDATA[Andrei Branza]]></dc:creator>
		<pubDate>Tue, 31 Mar 2026 15:43:00 +0000</pubDate>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Serialization]]></category>
		<category><![CDATA[Sonar]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203315</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 about Transient or Serializable in Java to overcome Sonar's related warnings.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/952480358/0/baeldung~Fix-Sonars-Make-Transient-or-Serializable-Warning-in-Java">Fix Sonar’s “Make Transient or Serializable” Warning in Java</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/952480358/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/952480358/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/952480358/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/952480358/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/952480358/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-sonars-transient-serializable-warning#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-sonars-transient-serializable-warning/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction">1. Introduction</h2>
<div class="bd-anchor" id="introduction"></div>
<p>When working with Java&#8217;s native <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-serialization">serialization</a> combined with SonarQube&#8217;s code evaluation tool, sometimes we encounter <strong>&#8220;Fields in a &#8216;Serializable&#8217; class should either be &#8216;transient&#8217; or &#8216;Serializable'&#8221; (<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://next.sonarqube.com/sonarqube/coding_rules?open=java%3AS1948&amp;rule_key=java%3AS1948">rule key: java:S1948</a>) </strong>warning. This rule is an important guardrail and it prevents a common runtime failure: the <em>NotSerializableException.</em></p>
<p>In this tutorial, we&#8217;ll explore why this warning occurs. Also, we&#8217;ll talk about the underlying mechanics of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/get-started-with-java-series">Java</a> serialization, and the best strategies to resolve it.</p>
<h2 id="bd-understanding-the-serialization-contract" data-id="understanding-the-serialization-contract">2. Understanding the Serialization Contract</h2>
<div class="bd-anchor" id="understanding-the-serialization-contract"></div>
<p>To make a Java object serializable, its class must implement the <em>java.io.Serializable</em> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-interfaces">interface</a>. This is a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-marker-interfaces">marker interface</a> that tells the JVM the object&#8217;s state is converted into a byte stream for storage or network transfer.</p>
<p>As such, the fundamental rule of this contract is recursive: <strong>if a class is serializable, all its non-static and non-transient member fields must also be serializable.</strong> If the serialization mechanism encounters a field that doesn&#8217;t implement <em>Serializable</em> and isn&#8217;t marked as <em>transient</em>, the process will fail at runtime. <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/sonar-qube">Sonar</a> flags these fields during static analysis and helps us catch this error before the code even runs.</p>
<p><strong>In addition, it&#8217;s important to remember that serialization isn&#8217;t just about the top-level class; it&#8217;s about the entire object graph.</strong></p>
<h2 id="bd-reproducing-the-sonar-warning" data-id="reproducing-the-sonar-warning">3. Reproducing the Sonar Warning</h2>
<div class="bd-anchor" id="reproducing-the-sonar-warning"></div>
<p>Let&#8217;s look at a typical scenario that triggers the java:s1948 warning. First, let&#8217;s add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.slf4j/slf4j-api/2.0.17">SLF4J dependency</a> to our pom.xml:</p>
<pre><code class="language-java">&lt;dependency&gt;
    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
     &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
     &lt;version&gt;2.0.17&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Now, let&#8217;s create a <em>User</em> class that we want to store in a distributed session or cache. This class also contains a reference to another class, <em>Address, </em>which does not implement the <em>Serializable </em>interface. We&#8217;ll start with a simple implementation:</p>
<pre><code class="language-java">public class User implements Serializable {
    private static final long serialVersionUID = 1L;
         
    private String username;
    private Address address; // Sonar Warning: Make "address" transient or serializable
    private final Logger logger = LoggerFactory.getLogger(User.class); // Sonar Warning
         
     public User(String username, Address address) {
        this.username = username;
        this.address = address;
     }
}</code></pre>
<p>In this example, the <em>User</em> class correctly implements <em>Serializable</em>. However, if the <em>Address</em> class is defined without the interface, Sonar will flag the <em>address</em> field. Similarly, since the SLF4J <em>Logger</em> does not implement <em>Serializable</em>, it will also trigger the warning. <strong>Furthermore, if we try to serialize an instance of <em>User</em>, the JVM will throw a <em>NotSerializableException</em> at runtime.</strong> This is exactly what Sonar is trying to help us avoid. Now, let&#8217;s look at some ways we can tackle this warning.</p>
<h2 id="bd-making-the-field-serializable" data-id="making-the-field-serializable">4. Making the Field Serializable</h2>
<div class="bd-anchor" id="making-the-field-serializable"></div>
<p><strong>The most straightforward fix is to ensure that the nested class also implements the <em>Serializable</em> interface</strong>. This is the preferred approach when the field represents a core part of the object&#8217;s state that must be preserved. As such, if we own the source code of the nested object, we should simply update the class definition:</p>
<pre><code class="language-java">public class Address implements Serializable {
    private static final long serialVersionUID = 1L;
        
    private String street;
    private String city;
        
    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }
}</code></pre>
<p>By adding implements <em>Serializable</em> and providing a <em>serialVersionUID</em>, we satisfy the contract requirements.<strong> It&#8217;s a best practice to always include <em>serialVersionUID</em> to ensure compatibility during the deserialization process. </strong>Especially if the class structure evolves over time.</p>
<h2 id="bd-using-the-staticmodifier" data-id="using-the-staticmodifier">5. Using the <em>static </em>Modifier</h2>
<div class="bd-anchor" id="using-the-staticmodifier"></div>
<p>Another effective way to resolve the warning for certain fields is to declare them as <em>static</em>. <strong>In Java, static fields are not serialized because they belong to the class itself rather than a specific instance.</strong> Since they are excluded from the serialization process by the JVM, SonarQube does not flag them.</p>
<p>This is the standard solution for loggers and constants:</p>
<pre><code class="language-java">public class User implements Serializable {
    private static final long serialVersionUID = 1L;
       
    private static final Logger logger = LoggerFactory.getLogger(User.class); // No Warning
    private String username;
    private Address address;
    // getters, setters ...
 }</code></pre>
<p>By making the logger static, the warning disappears, and we follow the common Java pattern for logger declarations. This approach is ideal for any field that is shared across all instances of the class and does not represent the unique state of an individual object.</p>
<h2 id="bd-leveraging-the-transient-keyword" data-id="leveraging-the-transient-keyword">6. Leveraging the <em>transient </em>Keyword</h2>
<div class="bd-anchor" id="leveraging-the-transient-keyword"></div>
<p><strong>If a field is instance-specific but shouldn&#8217;t be serialized</strong> (like a reference to a temporary cache, or a non-serializable third-party object) we use the <em>transient</em> keyword. This modifier tells the JVM to skip the field during the serialization process:</p>
<pre><code class="language-java">public class User implements Serializable {
    private static final long serialVersionUID = 1L;
       
    private String username;
    private Address address;
    private transient List&lt;String&gt; temporaryCache; // Warning Resolved
    // getters and setters ...
}</code></pre>
<p>We need to take into consideration that when an object is deserialized, all transient fields are initialized to their default values: <em>null</em> for objects and <em>0</em> for primitives. As such, if a transient field is required for the object to function after being restored, we must re-initialize it. A way to achieve this is to use the <em>readObject</em> method:</p>
<pre><code class="language-java">private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    this.temporaryCache = new ArrayList&lt;&gt;();
}</code></pre>
<h2 id="bd-handling-framework-dependencies" data-id="handling-framework-dependencies">7. Handling Framework Dependencies</h2>
<div class="bd-anchor" id="handling-framework-dependencies"></div>
<p>In modern Java frameworks like <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-tutorial">Spring</a>, this warning often appears in <em>@SessionScoped</em> beans when injecting a <em>@Service</em> or <em>@Repository</em>. Since these services are managed by the container and typically aren&#8217;t serializable, they should be marked as transient:</p>
<pre><code class="language-java">@SessionScoped
public class UserPreferences implements Serializable {
    @Autowired
    private transient PreferenceService service; // Fixed by marking it with transient
}</code></pre>
<p><strong>Spring handles the dependency injection, so it will re-inject the service when the bean is restored from the session, provided the framework&#8217;s proxy mechanism supports it.</strong> This keeps our session-scoped beans serializable while allowing them to interact with stateless services.</p>
<p>Furthermore, we might encounter situations where we use third-party library classes that do not implement <em>Serializable</em>. If we cannot modify their source code, we have two main options:</p>
<ul>
<li>Wrap the object: Create a serializable DTO that holds only the necessary primitive data.\</li>
<li>Custom Serialization: Use <em>transient</em> for the third-party object and manually handle its state during serialization using the <em>writeObject</em> and <em>readObject</em> methods.</li>
</ul>
<p>For example, if we have a non-serializable <em>Metadata</em> object, we can store its state as a serializable <em>Map</em> or and reconstruct it upon deserialization. This keeps our domain models serializable while still utilizing powerful third-party tools.</p>
<h2 id="bd-conclusion" data-id="conclusion">8. Conclusion</h2>
<div class="bd-anchor" id="conclusion"></div>
<p>In this tutorial, we&#8217;ve covered how to handle the Sonar warning &#8220;Make Transient or Serializable&#8221;. Although the warning is helpful to ensure runtime stability in Java applications, it can cause confusion. As such, by understanding the recursive nature of Java serialization, we can choose the correct fix based on the field&#8217;s role in our application.</p>
<p>Implement <em>Serializable </em>if the field is a core part of the object&#8217;s state. Use <em>static </em>for loggers, constants and shared class-level members. Mark as <em>transient </em>if the field is a resource, or temporary data. Refactor the design if we&#8217;re trying to serialize stateless services or third-party objects that aren&#8217;t meant to be persisted.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-sonars-transient-serializable-warning">Fix Sonar’s “Make Transient or Serializable” Warning in Java</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/952480358/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/952480358/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/952480358/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f07%2fJava-Featured-13-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/952480358/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/952480358/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/952480358/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-sonars-transient-serializable-warning#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-sonars-transient-serializable-warning/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/~/952480358/0/baeldung~Fix-Sonars-Make-Transient-or-Serializable-Warning-in-Java/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/07/Java-Featured-13-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/apache-seata-distributed-transaction-management</feedburner:origLink>
		<title>Distributed Transaction Management Using Apache Seata</title>
		<link>https://feeds.feedblitz.com/~/951988322/0/baeldung~Distributed-Transaction-Management-Using-Apache-Seata</link>
					<comments>https://feeds.feedblitz.com/~/951988322/0/baeldung~Distributed-Transaction-Management-Using-Apache-Seata#respond</comments>
		
		<dc:creator><![CDATA[Graham Cox]]></dc:creator>
		<pubDate>Fri, 27 Mar 2026 20:56:55 +0000</pubDate>
				<category><![CDATA[Data]]></category>
		<category><![CDATA[Persistence]]></category>
		<category><![CDATA[popular]]></category>
		<category><![CDATA[Transactions]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/apache-seata-distributed-transaction-management</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Learn how to use Apache Seata to implement distributed database transactions across multiple Spring applications.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/951988322/0/baeldung~Distributed-Transaction-Management-Using-Apache-Seata">Distributed Transaction Management Using Apache Seata</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/951988322/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/951988322/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-03-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/951988322/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/951988322/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/951988322/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/apache-seata-distributed-transaction-management#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/apache-seata-distributed-transaction-management/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-introduction" data-id="introduction"><strong>1. Introduction</strong></h2>
<div class="bd-anchor" id="introduction"></div>
<p>In this tutorial, we&#8217;re going to take a look at <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://seata.apache.org/">Apache Seata</a>, formally from Alibaba but now part of the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://incubator.apache.org/">Apache Incubator</a> project. We’ll see what it is, how to use it and what we can do with it.</p>
<h2 id="bd-why-distributed-transactions" data-id="why-distributed-transactions"><strong>2. Why Distributed Transactions?</strong></h2>
<div class="bd-anchor" id="why-distributed-transactions"></div>
<p><strong>To write robust applications, we often make use of <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-transactions">database transactions</a> to ensure that any changes to our data are atomic.</strong> That is, either the entire change happens, or none of it does. This helps ensure that our data remains in a valid state at all times.</p>
<p>When we have a single service that manages our data, this is easy to achieve. We start a new transaction when a request comes into our system. All data changes occur within this transaction, and we commit only if the entire request succeeds.</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/03/single-service.png"><img loading="lazy" decoding="async" class="alignnone wp-image-242131 size-full" src="https://www.baeldung.com/wp-content/uploads/2026/03/single-service.png" alt="Sequence diagram showing calls between various different services within the same application, all happening within the same database transaction." width="691" height="532" /></a>
<p>Here, if something goes wrong when recording the bill for the user, the order and inventory changes are reverted, and the system remains in the correct state.</p>
<p>If we move towards running this as many distributed services, suddenly our transactions are distributed as well:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/03/distributed-services.png"><img loading="lazy" decoding="async" class="alignnone wp-image-242132 size-full" src="https://www.baeldung.com/wp-content/uploads/2026/03/distributed-services.png" alt="Sequence diagram showing calls between various different services each within separate applications. Each application has it's own database transaction that isn't shared." width="981" height="594" /></a>
<p><strong>This is exactly the same flow, but by splitting our inventory, order and billing services into separate applications, we&#8217;ve also split them into separate transactions.</strong> Now, if recording the bill fails, the inventory and order changes have already been committed and cannot easily be reverted.</p>
<p>This is where distributed transactions come in. If we have a way to maintain our database transactions across multiple applications, we get both the benefits of splitting our system up, as well as the benefits of a single transaction for the entire user action.</p>
<h2 id="bd-what-is-apache-seata" data-id="what-is-apache-seata"><strong>3. What Is Apache Seata?</strong></h2>
<div class="bd-anchor" id="what-is-apache-seata"></div>
<p><strong>Apache Seata is an open source project, originally part of Alibaba group, that helps us to manage distributed transactions in our Java microservices applications.</strong></p>
<p>When using Seata, we run an additional service that acts as the Transaction Coordinator. When a request comes into our application, the originating service, acting as the Transaction Manager, starts a new distributed transaction within the transaction coordinator. All other services then take part in this same transaction until it either gets persisted or reverted:</p>
<a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/wp-content/uploads/2026/03/distributed-transactions-1.png"><img loading="lazy" decoding="async" class="alignnone wp-image-242133 size-large" src="https://www.baeldung.com/wp-content/uploads/2026/03/distributed-transactions-1-1024x751.png" alt="Sequence diagram showing calls between various different services each within separate applications. Each application has it's own database transaction but all take part in a larger distributed transaction." width="1024" height="751" /></a>
<p><strong>Here, our flow is essentially the same, but we&#8217;ve also added in our transaction coordinator and wrapped everything in a single distributed transaction.</strong> This will ensure that all three databases either commit or rollback together, and so our overall system remains in a valid state.</p>
<h2 id="bd-seata-server" data-id="seata-server"><strong>4. Seata Server</strong></h2>
<div class="bd-anchor" id="seata-server"></div>
<p><strong>Before we can use Seata, we need to ensure that we have a running Seata Server. This acts as the transaction coordinator in our overall system.</strong></p>
<p>The easiest way to get this working is as a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://hub.docker.com/r/apache/seata-server">Docker container</a> that we can run in our environment. For example, we can include it in a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/ops/docker-compose">Docker Compose</a> file as follows:</p>
<pre><code class="language-yaml">services:
  seata-server:
    image: apache/seata-server:2.6.0
</code></pre>
<p>By default, this listens on port 8091 and uses the local filesystem within the container to track distributed transactions.</p>
<p>We&#8217;re then ready to set up our application to work with Seata.</p>
<h2 id="bd-using-spring-boot" data-id="using-spring-boot"><strong>5. Using Spring Boot</strong></h2>
<div class="bd-anchor" id="using-spring-boot"></div>
<p><strong>Seata provides a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.apache.seata/seata-spring-boot-starter">Spring Boot starter</a> that we can use to set it up.</strong> If we’re using Maven, we can include this dependency in our <em>pom.xml</em> file:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.seata&lt;/groupId&gt;
    &lt;artifactId&gt;seata-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;2.6.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<h3 id="bd-1-configuring-seata" data-id="1-configuring-seata"><strong>5.1. Configuring Seata</strong></h3>
<div class="bd-anchor" id="1-configuring-seata"></div>
<p><strong>We then need to provide a configuration file for Seata.</strong> This needs to be present on the classpath, so we&#8217;ll create it as <em>src/main/resources/seata.conf</em>:</p>
<pre><code class="language-apache">transport {
  type = "TCP"
  server = "NIO"
  heartbeat = true
  thread-factory {
    boss-thread-prefix = "NettyBoss"
    worker-thread-prefix = "NettyServerNIOWorker"
    server-executor-thread-size = 100
    share-boss-worker = false
    client-selector-thread-size = 1
    client-selector-thread-prefix = "NettyClientSelector"
    client-worker-thread-prefix = "NettyClientWorkerThread"
  }
  shutdown {
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  vgroupMapping.my_tx_group = "default"
  default.grouplist = "seata-server:8091"
  enableDegrade = false
  disableGlobalTransaction = false
}
client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
    sagaBranchRegisterEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
    defaultGlobalTransactionTimeout = 60000
    degradeCheck = false
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
    compress {
      enable = true
      type = "zip"
      threshold = "64k"
    }
  }
  log {
    exceptionRate = 100
  }
}</code></pre>
<p>Most of this is standard, but note we have to configure the host and port of the Seata server in the field <em>service.default.grouplist</em>.</p>
<p><strong>We also need to add some configuration to Spring to enable it to work with Seata.</strong> We do this within our <em>application.properties </em>file:</p>
<pre><code class="language-properties">seata.enabled=true
seata.application-id=${spring.application.name}
seata.tx-service-group=my_tx_group
seata.registry.type=file
seata.registry.file.name=seata.conf
seata.config.type=file
seata.config.file.name=seata.conf
seata.service.vgroup-mapping.my_tx_group=default
seata.service.grouplist.default=seata-server:8091
seata.data-source-proxy-mode=AT
seata.enable-auto-data-source-proxy=true
</code></pre>
<p>This also contains the host and port of the Seata server in the <em>seata.service.grouplist.default</em> property. We also need to ensure that several of the properties match up with the Seata configuration file, and that the <em>seata.registry.file.name </em>and <em>seata.config.file.name </em>properties point to our <em>seata.conf</em> file.</p>
<p><strong>Finally, if we&#8217;re <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://seata.apache.org/docs/user/mode/at">using AT mode</a> as configured here, we&#8217;ll need to create a special <em>undo_log</em> table in our service database:</strong></p>
<pre><code class="language-sql">CREATE TABLE IF NOT EXISTS undo_log (
    id            BIGSERIAL    NOT NULL,
    branch_id     BIGINT       NOT NULL,
    xid           VARCHAR(128) NOT NULL,
    context       VARCHAR(128) NOT NULL,
    rollback_info BYTEA        NOT NULL,
    log_status    INT          NOT NULL,
    log_created   TIMESTAMP(0) NOT NULL,
    log_modified  TIMESTAMP(0) NOT NULL,
    CONSTRAINT pk_undo_log PRIMARY KEY (id),
    CONSTRAINT ux_undo_log UNIQUE (xid, branch_id)
);</code></pre>
<p>We configure the exact table name in our <em>seata.conf</em> file.</p>
<p>At this point, Seata integrates with our service. If we start our project now, we&#8217;ll see several log messages indicating this:</p>
<pre><code class="language-plaintext">2026-03-14T07:53:37.728Z  INFO 1 --- [apache-seata-a] [           main] o.a.s.s.b.a.SeataAutoConfiguration       : Automatically configure Seata
2026-03-14T07:53:37.802Z  INFO 1 --- [apache-seata-a] [           main] ServiceLoader$InnerEnhancedServiceLoader : Load compatible class io.seata.spring.annotation.ScannerChecker
2026-03-14T07:53:37.984Z  INFO 1 --- [apache-seata-a] [           main] ServiceLoader$InnerEnhancedServiceLoader : Load compatible class io.seata.integration.tx.api.remoting.RemotingParser
2026-03-14T07:53:37.996Z  INFO 1 --- [apache-seata-a] [           main] o.a.s.s.a.GlobalTransactionScanner       : Initializing Global Transaction Clients ...
.....
2026-03-14T07:53:45.533Z  INFO 1 --- [apache-seata-a] [           main] o.a.s.c.rpc.netty.RmNettyRemotingClient  : RM will register :jdbc:postgresql://postgres:5432/seata
2026-03-14T07:53:45.540Z  INFO 1 --- [apache-seata-a] [           main] o.a.s.c.rpc.netty.NettyPoolableFactory   : NettyPool create channel to transactionRole:RMROLE,address:172.18.0.2:8091,msg:&lt; RegisterRMRequest{resourceIds='jdbc:postgresql://postgres:5432/seata', version='2.6.0', applicationId='apache-seata-a', transactionServiceGroup='my_tx_group', extraData='null'} &gt;
2026-03-14T07:53:45.586Z  INFO 1 --- [apache-seata-a] [           main] o.a.s.c.rpc.netty.RmNettyRemotingClient  : register RM success. client version:2.6.0, server version:2.6.0,channel:[id: 0x0a28dceb, L:/172.18.0.6:39884 - R:172.18.0.2/172.18.0.2:8091]
2026-03-14T07:53:45.590Z  INFO 1 --- [apache-seata-a] [           main] o.a.s.c.rpc.netty.NettyPoolableFactory   : register success, cost 34 ms, version:2.6.0,role:RMROLE,channel:[id: 0x0a28dceb, L:/172.18.0.6:39884 - R:172.18.0.2/172.18.0.2:8091]
2026-03-14T07:53:45.634Z  INFO 1 --- [apache-seata-a] [           main] .s.s.a.d.SeataAutoDataSourceProxyCreator : Auto proxy data source 'dataSource' by 'AT' mode.</code></pre>
<h3 id="bd-2-global-transactions" data-id="2-global-transactions"><strong>5.2. Global Transactions</strong></h3>
<div class="bd-anchor" id="2-global-transactions"></div>
<p>Once Spring fully integrates with Seata, we can start using it. <strong>We do this with the <em>@GlobalTransaction</em> annotation, which we use to mark the start of a transaction that should be distributed between services:</strong></p>
<pre><code class="language-java">@PostMapping("/a/{mode}")
@GlobalTransactional
public void handle() {
    // Controller logic here
}</code></pre>
<p>We can use this anywhere that we&#8217;d typically use the <em>@Transactional </em>annotation, and this will start a new database transaction. This transaction registers with Seata and can span multiple services instead of remaining local.</p>
<p><strong>Note that we only include this annotation at the start of the global transaction.</strong> Subsequent services in the same transaction needn&#8217;t include it. We&#8217;ll manage them differently, as we’ll see shortly.</p>
<p>If we wish, we can also provide some configuration for our transaction in a familiar way to the standard <em>@Transactional</em> annotation:</p>
<pre><code class="language-java">@GlobalTransactional(rollbackFor = MyException.class, timeoutMills = 10000)</code></pre>
<p>Here, we indicate that the transaction should roll back for any subclasses of <em>MyException</em>, with a 10-second timeout.</p>
<h3 id="bd-3-transaction-propagation" data-id="3-transaction-propagation"><strong>5.3. Transaction Propagation</strong></h3>
<div class="bd-anchor" id="3-transaction-propagation"></div>
<p>Unfortunately, if we try this now, then we&#8217;ll discover that the transactions don&#8217;t propagate correctly. We&#8217;d see log messages in our service indicating that it&#8217;s registered with Seata, but subsequent services wouldn&#8217;t do anything.</p>
<p><strong>Seata manages this by passing a special <em>XID </em>value between services. Typically, this goes in the HTTP header <em>TX_XID </em>on the calls between our services.</strong></p>
<p>If we&#8217;re using standard Spring then we need to manage this ourselves. This includes adding it to all outgoing HTTP calls and receiving it on all incoming calls.</p>
<p><strong>If we&#8217;re using <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-restclient">Spring RestClient</a> then we can write a <em>ClientHttpRequestInterceptor</em> implementation that will do this for us:</strong></p>
<pre><code class="language-java">public class SeataXidClientInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
        throws IOException {
        String xid = RootContext.getXID();
        if (StringUtils.hasText(xid)) {
            request.getHeaders().add(RootContext.KEY_XID, xid);
        }
        return execution.execute(request, body);
    }
}</code></pre>
<p>This simply adds our XID value to the outgoing HTTP request.</p>
<p>We must then ensure that our <em>RestClient</em> always uses this:</p>
<pre><code class="language-java">@Bean
public RestClient restClient() {
    return RestClient.builder()
        .requestInterceptor(new SeataXidClientInterceptor())
        .build();
}
</code></pre>
<p>We can do the exact same with any other HTTP clients too &#8211; e.g. <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-5-webclient">WebClient</a> or <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/rest-template">RestTemplate</a>.</p>
<p>At this point, all of our outbound calls will indicate the XID for our global transactions. <strong>However, we still need to consume them in our downstream services. We can do this with a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-boot-add-filter">servlet filter</a>:</strong></p>
<pre><code class="language-java">@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SeataXidFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) req;
        String xid = httpRequest.getHeader(RootContext.KEY_XID);
        boolean bound = false;
        if (StringUtils.hasText(xid) &amp;&amp; !xid.equals(RootContext.getXID())) {
            RootContext.bind(xid);
            bound = true;
        }
        try {
            chain.doFilter(req, res);
        } finally {
            if (bound) {
                RootContext.unbind();
            }
        }
    }
}</code></pre>
<p>This does the exact opposite &#8211; if there&#8217;s an XID present on the incoming HTTP request then bind it to the local service before continuing with the request, and ensure that we unbind it at the end.</p>
<p>At this point, our transaction now correctly spans our services and the entire set will commit or roll back together.</p>
<h2 id="bd-using-spring-cloud" data-id="using-spring-cloud"><strong>6. Using Spring Cloud</strong></h2>
<div class="bd-anchor" id="using-spring-cloud"></div>
<p><strong>Unlike Spring Boot, Spring Cloud can handle some of this automatically for us.</strong></p>
<p>In a Spring Cloud setup, we need to use a <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata">different dependency</a> in our project. We also need to be careful with the versions here &#8211; the newest <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata/2025.1.0.0">2025.1.0.0 version</a> only works with Spring Boot 4, whereas the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata/2025.0.0.0">2025.0.0.0 version</a> requires Spring Boot 3.</p>
<p>This dependency comes as a BOM that we can import into our dependency management section to manage versions, and then as the actual starter dependency:</p>
<pre><code class="language-xml">&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-alibaba-dependencies&lt;/artifactId&gt;
            &lt;version&gt;2025.0.0.0&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;
...
&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;
        &lt;artifactId&gt;spring-cloud-starter-alibaba-seata&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</code></pre>
<p><strong>We still need to do the same configuration as before, using our <em>seata.conf</em> and <em>application.properties </em>files. However, the framework handles most transaction propagation for us.</strong></p>
<p>The Spring Cloud Starter will automatically set our service up so that any incoming HTTP requests will join a global transaction if required. This removes the need for our servlet filter.</p>
<p>The starter also configures <em>RestTemplate </em>beans to automatically forward the <em>XID </em>value to downstream services, so if we&#8217;re using this, then we don&#8217;t need additional setup here either. Unfortunately, it doesn&#8217;t work with <em>RestClient </em>or <em>WebClient</em>, so if we&#8217;re using those, then we&#8217;ll still need to configure it manually.</p>
<h2 id="bd-summary" data-id="summary"><strong>7. Summary</strong></h2>
<div class="bd-anchor" id="summary"></div>
<p>In this article, we&#8217;ve taken a quick look at Apache Seata. We&#8217;ve seen what it is, and how we can use it in our applications. Next time you&#8217;re writing transactional services, why not give it a go?</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/apache-seata-distributed-transaction-management">Distributed Transaction Management Using Apache Seata</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/951988322/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/951988322/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/951988322/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fPersistence-Featured-Image-03-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/951988322/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/951988322/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/951988322/baeldung"><img height="20" src="https://assets.feedblitz.com/i/rss20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a rel="NOFOLLOW" title="View Comments" href="https://www.baeldung.com/apache-seata-distributed-transaction-management#respond"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/comments20.png"></a>&#160;<a title="Follow Comments via RSS" href="https://www.baeldung.com/apache-seata-distributed-transaction-management/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/~/951988322/0/baeldung~Distributed-Transaction-Management-Using-Apache-Seata/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Persistence-Featured-Image-03-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/java-weekly-639</feedburner:origLink>
		<title>Java Weekly, Issue 639</title>
		<link>https://feeds.feedblitz.com/~/951970496/0/baeldung~Java-Weekly-Issue</link>
					<comments>https://feeds.feedblitz.com/~/951970496/0/baeldung~Java-Weekly-Issue#respond</comments>
		
		<dc:creator><![CDATA[baeldung]]></dc:creator>
		<pubDate>Fri, 27 Mar 2026 15:14:09 +0000</pubDate>
				<category><![CDATA[Weekly Review]]></category>
		<category><![CDATA[no-ads]]></category>
		<category><![CDATA[no-after-post]]></category>
		<category><![CDATA[no-before-post]]></category>
		<category><![CDATA[no-optins]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203293</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>Our Spring Sale is live, Java 26 is almost out and OAuth is getting a core issue fixed. A good week</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/951970496/0/baeldung~Java-Weekly-Issue">Java Weekly, Issue 639</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/951970496/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/951970496/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/951970496/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/951970496/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/951970496/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-639#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-639/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://foojay.io/today/java-for-scripting/">&gt;&gt; JavaScript (No, Not That One): Modern Automation with Java</a></strong> [<span style="color: #993300;">foojay.io</span>]</p>
<p>Modern Java is a thing to behold :). It has quietly become <strong>quite a capable scripting language</strong> — single-file execution, JBang, and Picocli make it a practical alternative to Bash or Python for automation. Good stuff.</p>
<h4><strong>Also worth reading:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/spring-boot-actuator-health-for-microprofile-developers/" target="_blank" rel="noopener"><strong>Spring Boot Actuator Health for MicroProfile Developers</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/how-we-built-a-java-ai-agent-by-connecting-the-dots-the-ecosystem-already-had/" target="_blank" rel="noopener"><strong>How We Built a Java AI Agent by Connecting the Dots the Ecosystem Already Had</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/grails-isnt-done-yet-part-1-inside-the-asf-reboot/" target="_blank" rel="noopener"><strong>Grails Isn&#8217;t Done Yet (Part 1): Inside the ASF Reboot</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/does-language-still-matter-in-the-age-of-ai-yes-but-the-tradeoff-has-changed/" target="_blank" rel="noopener"><strong>Does Language Still Matter in the Age of AI? Yes — But the Tradeoff Has Changed</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/how-is-leyden-improving-java-performance-part-3-of-3/" target="_blank" rel="noopener"><strong>How is Leyden improving Java Performance? Part 3 of 3</strong></a> [<span style="color: #800000;">foojay.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/03/19/jdk26-security-enhancements/" target="_blank" rel="noopener"><strong>JDK 26 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://quarkus.io/blog/quarkus-polaris/" target="_blank" rel="noopener"><strong>Apache Polaris is powered by Quarkus</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
</ul>
<h4><strong>Webinars and presentations:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/03/19/a-bootiful-podcast-cay-horstmann" target="_blank" rel="noopener"><strong>A Bootiful Podcast: Cay Horstmann, legendary Java professor, author, lecturer</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/03/26/podcast-052/" target="_blank" rel="noopener"><strong>Episode 52 &#8220;Carrier Classes &amp; Discussing Syntax&#8221; [AtA]</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/kotlin/2026/03/kotlinconf-26-speakers-in-conversation-with-josh-long/" target="_blank" rel="noopener"><strong>KotlinConf&#8217;26 Speakers: In Conversation with Josh Long</strong></a> [<span style="color: #800000;">jetbrains.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://inside.java/2026/03/25/javaone-javafx/" target="_blank" rel="noopener"><strong>JavaOne and JavaFX</strong></a> [<span style="color: #800000;">inside.java</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.youtube.com/watch?v=FUFsul26rgA" target="_blank" rel="noopener"><strong>Moritz Halbritter on Spring Boot</strong></a> [<span style="color: #800000;">youtube.com</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.youtube.com/watch?v=Kourq_Lz03U" target="_blank" rel="noopener"><strong>The IntelliJ IDEA Documentary</strong></a> [<span style="color: #800000;">youtube.com</span>]</li>
</ul>
<h4><strong>Time to upgrade:</strong></h4>
<ul>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/03/26/spring-boot-4-1-0-M4-available-now" target="_blank" rel="noopener"><strong>Spring Boot 4.1.0-M4</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/03/26/spring-boot-4-0-5-available-now" target="_blank" rel="noopener"><strong>4.0.5</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/03/26/spring-boot-3-5-13-available-now" target="_blank" rel="noopener"><strong>3.5.13</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/03/23/spring-cloud-config-5-0-2-4-3-2-4-2-6-4-1-9-3-1-13-released" target="_blank" rel="noopener"><strong>Spring Cloud Config 5.0.2, 4.3.2, 4.2.6, 4.1.9, 3.1.13 Released, includes fix for CVE-2026-22739</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/03/20/spring-boot-4-1-0-M3-available-now" target="_blank" rel="noopener"><strong>Spring Boot 4.1.0-M3</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/03/19/spring-boot-4-0-4-available-now" target="_blank" rel="noopener"><strong>4.0.4</strong></a>, and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/03/19/spring-boot-3-5-12-available-now" target="_blank" rel="noopener"><strong>3.5.12</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/03/19/spring-security-6-5-9-and-7-0-4-and-7-1-0-M3-available-now" target="_blank" rel="noopener"><strong>Spring Security 6.5.9, 7.0.4, and 7.1.0-M3 available now</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://spring.io/blog/2026/03/18/spring-batch-6-0-3-and-5-2-5-available-now" target="_blank" rel="noopener"><strong>Spring Batch 6.0.3 and 5.2.5 available now</strong></a> [<span style="color: #800000;">spring.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jetbrains.com/idea/2026/03/intellij-idea-2026-1/" target="_blank" rel="noopener"><strong>IntelliJ IDEA 2026.1</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-34-released/" target="_blank" rel="noopener"><strong>Quarkus 3.34</strong></a>, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-33-released/" target="_blank" rel="noopener"><strong>3.33 LTS</strong></a> [<span style="color: #800000;">quarkus.io</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-27-3-released/" target="_blank" rel="noopener"><strong>Quarkus 3.27.3</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://quarkus.io/blog/quarkus-3-20-6-released/" target="_blank" rel="noopener"><strong>3.20.6</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://in.relation.to/2026/03/24/hibernate-reactive-4_3_0_Final/" target="_blank" rel="noopener"><strong>Hibernate Reactive 4.3.0.Final</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://in.relation.to/2026/03/20/hibernate-search-8-3-0-Final/" target="_blank" rel="noopener"><strong>Hibernate Search 8.3.0.Final</strong></a> [<span style="color: #800000;">in.relation.to</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.3.2" target="_blank" rel="noopener"><strong>Elasticsearch 9.3.2</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v9.2.7" target="_blank" rel="noopener"><strong>9.2.7</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/elastic/elasticsearch/releases/tag/v8.19.13" target="_blank" rel="noopener"><strong>8.19.13</strong></a> [<span style="color: #800000;">github.com/elastic</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.5.1" target="_blank" rel="noopener"><strong>Netflix Zuul 3.5.1</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/Netflix/zuul/releases/tag/v3.5.2" target="_blank" rel="noopener"><strong>3.5.2</strong></a> [<span style="color: #800000;">github.com/Netflix</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/apache/camel/releases/tag/camel-4.18.1" target="_blank" rel="noopener"><strong>Apache Camel 4.18.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/eclipse-vertx/vert.x/releases/tag/5.0.9" target="_blank" rel="noopener"><strong>Eclipse Vert.x 5.0.9</strong></a> and <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://github.com/eclipse-vertx/vert.x/releases/tag/4.5.26" target="_blank" rel="noopener"><strong>4.5.26</strong></a> [<span style="color: #800000;">github.com/eclipse-vertx</span>]</li>
</ul>
<h2 style="text-align: left;" id="bd-technical-amp-musings" data-id="technical-amp-musings">2.<strong> Technical &amp; Musings</strong></h2>
<div class="bd-anchor" id="technical-amp-musings"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://martinfowler.com/bliki/ArchitectureDecisionRecord.html">&gt;&gt; Bliki: Architecture Decision Record</a></strong> [<span style="color: #993300;">martinfowler.com</span>]</p>
<p>This is a lightweight document capturing <strong>a log of architectural decisions</strong> along with its context and trade-offs, stored in version control alongside code. Quite useful and often missed.</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/software-architect-elevator/" target="_blank" rel="noopener"><strong>The Software Architect Elevator</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://lucumr.pocoo.org/2026/3/20/some-things-just-take-time/" target="_blank" rel="noopener"><strong>Some Things Just Take Time</strong></a> [<span style="color: #800000;">lucumr.pocoo.org</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://blog.jbrains.ca/permalink/your-inbox-as-options-not-obligations" target="_blank" rel="noopener"><strong>Your Inbox As Options, Not Obligations</strong></a> [<span style="color: #800000;">jbrains.ca</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.mnot.net/blog/2026/03/25/using_ai" target="_blank" rel="noopener"><strong>Using AI to Evaluate Internet Standards (Part Two)</strong></a> [<span style="color: #800000;">mnot.net</span>]</li>
<li><strong>&gt;&gt;</strong> <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://foojay.io/today/testbox-7-real-time-feedback-a-browser-based-ide-and-modern-testing-workflows-on-the-jvm/" target="_blank" rel="noopener"><strong>TestBox 7: Real-Time Feedback, a Browser-Based IDE, and Modern Testing Workflows on the JVM</strong></a> [<span style="color: #800000;">foojay.io</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>And our sale is about to end:</p>
<p><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/members/pricing"><strong>&gt;&gt; All Access for 4 More Days</strong></a></p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/java-weekly-639">Java Weekly, Issue 639</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/951970496/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/951970496/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/951970496/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2016%2f10%2fsocial-Weekly-Reviews-4.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/951970496/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/951970496/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/951970496/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-639#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-639/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/~/951970496/0/baeldung~Java-Weekly-Issue/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2016/10/social-Weekly-Reviews-4-150x150.jpg</webfeeds:featuredImage></item>
<item>
<feedburner:origLink>https://www.baeldung.com/spring-ai-testing-mcp-tools</feedburner:origLink>
		<title>Testing Model Context Protocol (MCP) Tools in Spring AI</title>
		<link>https://feeds.feedblitz.com/~/951832256/0/baeldung~Testing-Model-Context-Protocol-MCP-Tools-in-Spring-AI</link>
					<comments>https://feeds.feedblitz.com/~/951832256/0/baeldung~Testing-Model-Context-Protocol-MCP-Tools-in-Spring-AI#respond</comments>
		
		<dc:creator><![CDATA[Manfred Ng]]></dc:creator>
		<pubDate>Wed, 25 Mar 2026 05:12:43 +0000</pubDate>
				<category><![CDATA[Spring AI]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[LLM]]></category>
		<guid isPermaLink="false">https://www.baeldung.com/?p=203238</guid>
					<description><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="max-width:100% !important;height:auto !important;float: left; margin-right: 5px;" loading="lazy" /><p>Explore how to test MCP tools on MCP servers in Spring AI using different test strategies.</p>
The post <a rel="NOFOLLOW" href="https://feeds.feedblitz.com/~/951832256/0/baeldung~Testing-Model-Context-Protocol-MCP-Tools-in-Spring-AI">Testing Model Context Protocol (MCP) Tools in Spring AI</a> first appeared on <a rel="NOFOLLOW" href="https://www.baeldung.com">Baeldung</a>.<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/951832256/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/951832256/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/951832256/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/951832256/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/951832256/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-testing-mcp-tools#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-testing-mcp-tools/feed"><img height="20" style="border:0;margin:0;padding:0;" src="https://assets.feedblitz.com/i/commentsrss20.png"></a>&#160;</div>]]>
</description>
										<content:encoded><![CDATA[<img src="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-1024x536.jpg" class="webfeedsFeaturedVisual wp-post-image" alt="" style="float: left; margin-right: 5px;" decoding="async" loading="lazy" srcset="https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-1024x536.jpg 1024w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-300x157.jpg 300w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-768x402.jpg 768w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-100x52.jpg 100w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-600x314.jpg 600w, https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11.jpg 1200w" sizes="auto, (max-width: 580px) 100vw, 580px" /><h2 id="bd-overview" data-id="overview">1. Overview</h2>
<div class="bd-anchor" id="overview"></div>
<p><strong><a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-model-context-protocol-mcp">Model Context Protocol (MCP)</a> is an open standard protocol that defines how a large language model (LLM) discovers and invokes external tools to extend its capabilities.</strong> MCP is a client-server architecture that allows the MCP client, usually an LLM integrated application, to interact with one or more MCP servers that expose tools for invocation.</p>
<p>Testing tools exposed by MCP servers are crucial to validate that they are correctly registered on MCP servers and are discoverable by MCP clients. Unlike LLM responses, which are non-deterministic, MCP tools behave deterministically because they&#8217;re just normal application code, which enables us to write automated tests to verify correctness.</p>
<p>In this tutorial, we&#8217;ll explore how to test MCP tools on MCP servers in <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai">Spring AI</a> using different test strategies.</p>
<h2 id="bd-maven-dependencies" data-id="maven-dependencies">2. Maven Dependencies</h2>
<div class="bd-anchor" id="maven-dependencies"></div>
<p>We&#8217;ll test the MCP tools in a Spring Boot application. Hence, we must add the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-mcp-server">Spring AI MCP server</a> dependency to our <em>pom.xml</em>:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-starter-mcp-server&lt;/artifactId&gt;
    &lt;version&gt;1.1.2&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>We&#8217;ll also require the <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test">Spring Boot Test</a> dependency for our testing:</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;</code></pre>
<h2 id="bd-creating-a-sample-mcp-tool" data-id="creating-a-sample-mcp-tool">3. Creating a Sample MCP Tool</h2>
<div class="bd-anchor" id="creating-a-sample-mcp-tool"></div>
<p>In this section, we&#8217;ll implement a simple MCP tool using Spring AI and demonstrate different testing strategies.</p>
<p>First, let&#8217;s create a simple <em>ExchangeRateService</em> that uses a third-party open-source service, <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://frankfurter.dev/">Frankfurter</a>, to fetch the currency exchange rate using an HTTP GET request. This API requires a mandatory query parameter <em>base</em>:</p>
<pre><code class="language-java">@Service
public class ExchangeRateService {
    private static final String FRANKFURTER_URL = "https://api.frankfurter.dev/v1/latest?base={base}";
    private final RestClient restClient;
    public ExchangeRateService(RestClient.Builder restClientBuilder) {
        this.restClient = restClientBuilder.build();
    }
    public ExchangeRateResponse getLatestExchangeRate(String base) {
        if (base == null || base.isBlank()) {
            throw new IllegalArgumentException("base is required");
        }
        return restClient.get()
          .uri(FRANKFURTER_URL, base.trim().toUpperCase())
          .retrieve()
          .body(ExchangeRateResponse.class);
    }
}
</code></pre>
<p>The API response looks something similar to:</p>
<pre><code class="language-">{
  "amount": 1,
  "base": "GBP",
  "date": "2026-03-06",
  "rates": {
    "AUD": 1.9034,
    "BRL": 7.0366,
    ......
  }
}</code></pre>
<p>Thus, we create the following Java record to map the JSON response:</p>
<pre><code class="language-java">public record ExchangeRateResponse(double amount, String base, String date, Map&lt;String, Double&gt; rates) {
}</code></pre>
<p>Now, let&#8217;s create an MCP tool that invokes the <em>ExchangeRateService</em> to return currency exchange rates based on the base currency.</p>
<p><strong>The tool description explains what the parameter is for, such that MCP clients know what they should provide when calling it:</strong></p>
<pre><code class="language-java">@Component
public class ExchangeRateMcpTool {
    private final ExchangeRateService exchangeRateService;
    public ExchangeRateMcpTool(ExchangeRateService exchangeRateService) {
        this.exchangeRateService = exchangeRateService;
    }
    @McpTool(description = "Get latest exchange rates for a base currency")
    public ExchangeRateResponse getExchangeRate(
        @McpToolParam(description = "Base currency code, e.g. GBP, USD", required = true) String base) {
        return exchangeRateService.getLatestExchangeRate(base);
    }
}</code></pre>
<h2 id="bd-unit-test" data-id="unit-test">4. Unit Test</h2>
<div class="bd-anchor" id="unit-test"></div>
<p><strong>We could verify the <em>ExchangeRateMcpTool</em> logic in isolation by unit test. Therefore, we mock the external dependency so that we can provide a mocked response.</strong></p>
<p>The verification process is fairly simple to validate that the service gets invoked correctly, and also the response is returned as expected:</p>
<pre><code class="language-java">class ExchangeRateMcpToolUnitTest {
    @Test
    void whenBaseIsNotBlank_thenGetExchangeRateShouldReturnResponse() {
        ExchangeRateService exchangeRateService = mock(ExchangeRateService.class);
        ExchangeRateResponse expected = new ExchangeRateResponse(1.0, "GBP", "2026-03-08",
          Map.of("USD", 1.27, "EUR", 1.17));
        when(exchangeRateService.getLatestExchangeRate("gbp")).thenReturn(expected);
        ExchangeRateMcpTool tool = new ExchangeRateMcpTool(exchangeRateService);
        ExchangeRateResponse actual = tool.getExchangeRate("gbp");
        assertThat(actual).isEqualTo(expected);
        verify(exchangeRateService).getLatestExchangeRate("gbp");
    }
}</code></pre>
<h2 id="bd-creating-an-mcp-test-client" data-id="creating-an-mcp-test-client">5. Creating an MCP Test Client</h2>
<div class="bd-anchor" id="creating-an-mcp-test-client"></div>
<p><strong>If we want to test the MCP tools end-to-end, we could create an MCP client that connects to the MCP server.</strong></p>
<p>HTTP-based MCP servers expose different endpoints depending on the protocol configuration property <em>spring.ai.mcp.server.protocol</em> in the <em>application.yml</em>. Spring AI uses the SSE by default if we don&#8217;t set the property explicitly:</p>
<table class="table-styled" style="border-collapse: collapse;width: 50%">
<tbody>
<tr>
<th>Protocol</th>
<th>Endpoint</th>
</tr>
<tr>
<td>Server-Sent Events (SSE)</td>
<td>/sse</td>
</tr>
<tr>
<td style="width: 60%">Streamable HTTP</td>
<td style="width: 40%">/mcp</td>
</tr>
</tbody>
</table>
<p>In addition to the different endpoints, each protocol requires a different <em>McpClientTransport</em> instance to create the <em>McpSyncClient</em>.</p>
<p>Since Spring AI does not provide a factory class that automatically creates the client based on the protocol, we create a test component, <em>TestMcpClientFactory, </em>handling the <em>McpSyncClient</em> creation, to simplify our testing:</p>
<pre><code class="language-java">@Component
public class TestMcpClientFactory {
    private final String protocol;
    public TestMcpClientFactory(@Value("${spring.ai.mcp.server.protocol:sse}") String protocol) {
        this.protocol = protocol;
    }
    public McpSyncClient create(String baseUrl) {
        String resolvedProtocol = protocol.trim().toLowerCase();
        return switch (resolvedProtocol) {
            case "sse" -&gt; McpClient.sync(HttpClientSseClientTransport.builder(baseUrl)
              .sseEndpoint("/sse")
              .build()
            ).build();
            case "streamable" -&gt; McpClient.sync(HttpClientStreamableHttpTransport.builder(baseUrl)
              .endpoint("/mcp")
              .build()
            ).build();
            default -&gt; throw new IllegalArgumentException("Unknown MCP protocol: " + protocol);
        };
    }
}</code></pre>
<p>We support the SSE and streamable protocol only in our factory class to demonstrate the idea.</p>
<h2 id="bd-verifying-tool-registration" data-id="verifying-tool-registration">6. Verifying Tool Registration</h2>
<div class="bd-anchor" id="verifying-tool-registration"></div>
<p><strong>The MCP server exposes an HTTP endpoint to list all available tools that MCP clients can invoke. As such, we could initialize an MCP client to verify the tool&#8217;s registration on the MCP server.</strong></p>
<div>The following is our base code for initializing and closing an <em>McpSyncClient</em>:</div>
<pre><code class="language-">@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ExchangeRateMcpToolIntegrationTest {
    @LocalServerPort
    private int port;
    @Autowired
    private TestMcpClientFactory testMcpClientFactory;
    @MockBean
    private ExchangeRateService exchangeRateService;
    private McpSyncClient client;
    @BeforeEach
    void setUp() {
        client = testMcpClientFactory.create("http://localhost:" + port);
        client.initialize();
    }
    @AfterEach
    void cleanUp() {
        client.closeGracefully();
    }
}</code></pre>
<p>Once we initialize an MCP client, we invoke the client <em>listTools()</em> method to find out all tools that an MCP server has registered:</p>
<pre><code class="language-java">@Test
void whenMcpClientListTools_thenTheToolIsRegistered() {
    boolean registered = client.listTools().tools().stream()
      .anyMatch(tool -&gt; Objects.equals(tool.name(), "getLatestExchangeRate"));
    assertThat(registered).isTrue();
}</code></pre>
<p>The test returns a list of registered tools, and we assert that <em>getLatestExchangeRate</em> is one of them to confirm successful registration.</p>
<h2 id="bd-testing-tool-invocation" data-id="testing-tool-invocation">7. Testing Tool Invocation</h2>
<div class="bd-anchor" id="testing-tool-invocation"></div>
<p><strong>In addition, we could also verify the MCP tool by invoking it from an MCP client. </strong>We mock the <em>ExchangeRateService</em> in this test to avoid making a genuine HTTP call to the Frankfurter API<em>.</em></p>
<p>The invocation flow includes discovering the tools from the MCP server, building a <em>CallToolRequest</em> with all required arguments, and calling it to obtain the response from the server:</p>
<pre><code class="language-java">@Test
void whenMcpClientCallTool_thenTheToolReturnsMockedResponse() {
    when(exchangeRateService.getLatestExchangeRate("GBP")).thenReturn(
      new ExchangeRateResponse(1.0, "GBP", "2026-03-08", Map.of("USD", 1.27))
    );
    McpSchema.Tool exchangeRateTool = client.listTools().tools().stream()
      .filter(tool -&gt; "getLatestExchangeRate".equals(tool.name()))
      .findFirst()
      .orElseThrow();
    String argumentName = exchangeRateTool.inputSchema().properties().keySet().stream()
      .findFirst()
      .orElseThrow();
    McpSchema.CallToolResult result = client.callTool(
      new McpSchema.CallToolRequest("getLatestExchangeRate", Map.of(argumentName, "GBP"))
    );
    assertThat(result).isNotNull();
    assertThat(result.isError()).isFalse();
    assertTrue(result.toString().contains("GBP"));
}</code></pre>
<p>The assertions ensure the tool call returns a valid response without errors.</p>
<h2 id="bd-conclusions" data-id="conclusions">8. Conclusions</h2>
<div class="bd-anchor" id="conclusions"></div>
<p>In this article, we created a sample MCP server tool, validated its correctness, ensured the MCP server registered with it, and tested the tool invocation via an MCP client.</p>
<p>With both unit tests and integration tests in place, we can be confident that the tool is working correctly and is exposed properly so that MCP clients can invoke it.</p>The post <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com/spring-ai-testing-mcp-tools">Testing Model Context Protocol (MCP) Tools in Spring AI</a> first appeared on <a href="http://feeds.feedblitz.com/~/t/0/0/baeldung/~https://www.baeldung.com">Baeldung</a>.<Img align="left" border="0" height="1" width="1" alt="" style="border:0;float:left;margin:0;padding:0;width:1px!important;height:1px!important;" hspace="0" src="https://feeds.feedblitz.com/~/i/951832256/0/baeldung">
<div style="clear:both;padding-top:0.2em;"><a title="Like on Facebook" href="https://feeds.feedblitz.com/_/28/951832256/baeldung"><img height="20" src="https://assets.feedblitz.com/i/fblike20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Pin it!" href="https://feeds.feedblitz.com/_/29/951832256/baeldung,https%3a%2f%2fwww.baeldung.com%2fwp-content%2fuploads%2f2024%2f11%2fSpring-Featured-Image-11-1024x536.jpg"><img height="20" src="https://assets.feedblitz.com/i/pinterest20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Post to X.com" href="https://feeds.feedblitz.com/_/24/951832256/baeldung"><img height="20" src="https://assets.feedblitz.com/i/x.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by email" href="https://feeds.feedblitz.com/_/19/951832256/baeldung"><img height="20" src="https://assets.feedblitz.com/i/email20.png" style="border:0;margin:0;padding:0;"></a>&#160;<a title="Subscribe by RSS" href="https://feeds.feedblitz.com/_/20/951832256/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-testing-mcp-tools#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-testing-mcp-tools/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/~/951832256/0/baeldung~Testing-Model-Context-Protocol-MCP-Tools-in-Spring-AI/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<webfeeds:featuredImage>https://www.baeldung.com/wp-content/uploads/2024/11/Spring-Featured-Image-11-150x150.jpg</webfeeds:featuredImage></item>
</channel></rss>

