<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://mehmandarov.com/tag/spring-boot/feed.xml" rel="self" type="application/atom+xml"/><link href="https://mehmandarov.com/tag/spring-boot/" rel="alternate" type="text/html"/><updated>2026-06-21T09:00:00+02:00</updated><id>https://mehmandarov.com/tag/spring-boot/feed.xml</id><title type="html">Rustam Mehmandarov - tag: spring boot</title><subtitle type="text">Posts tagged &quot;spring boot&quot; on Rustam Mehmandarov.</subtitle><author><name>Rustam Mehmandarov</name></author><entry><title type="html">Unknown JSON fields in Java REST clients: JSON-B, Jackson, Quarkus, and Spring Boot</title><link href="https://mehmandarov.com/unknown-json-fields-in-java-rest-clients/" rel="alternate" type="text/html" title="Unknown JSON fields in Java REST clients: JSON-B, Jackson, Quarkus, and Spring Boot"/><published>2026-06-21T09:00:00+02:00</published><updated>2026-06-21T09:00:00+02:00</updated><id>https://mehmandarov.com/unknown-json-fields-in-java-rest-clients</id><content type="html" xml:base="https://mehmandarov.com/unknown-json-fields-in-java-rest-clients/"><![CDATA[<p><em>You call an API with the MicroProfile REST Client, map the response onto a small DTO, and one day the API starts returning a few extra fields you never asked for. Does your client shrug and carry on, or does it blow up with a deserialization error? The honest answer is &#8220;it depends on your JSON provider&#8221; &#8211; and the defaults are not the same across the board. Let&#8217;s pin down what actually happens, and point to the spec or docs for each case.</em></p>

<ul>
  <li><a href="#introduction">Introduction</a></li>
  <li><a href="#why-this-bites-people">Why this bites people</a></li>
  <li><a href="#show-me-the-code">Show me the code</a></li>
  <li><a href="#1-the-default-json-b-yasson">1. The default: JSON-B (Yasson)</a></li>
  <li><a href="#2-jackson-strict-by-default">2. Jackson: strict by default</a></li>
  <li><a href="#3-quarkus-jackson-but-lenient">3. Quarkus: Jackson, but lenient</a></li>
  <li><a href="#4-spring-boot-also-lenient">4. Spring Boot: also lenient</a></li>
  <li><a href="#a-note-on-the-other-direction-server-receiving-extra-fields">A note on the other direction (server receiving extra fields)</a></li>
  <li><a href="#summary-comparison">Summary Comparison</a></li>
  <li><a href="#conclusion">Conclusion</a></li>
  <li><a href="#whats-next">What&#8217;s Next?</a></li>
</ul>

<hr />

<h2 id="introduction">Introduction</h2>

<p>Imagine a small REST client. You are consuming a &#8220;room&#8221; resource from some conference API, and you only care about three fields &#8211; <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">name</code>, and <code class="language-plaintext highlighter-rouge">capacity</code>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">record</span> <span class="nf">Room</span><span class="o">(</span><span class="nc">String</span> <span class="n">id</span><span class="o">,</span> <span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">int</span> <span class="n">capacity</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span>
</code></pre></div></div>

<p>You wire it up with the <a href="https://download.eclipse.org/microprofile/microprofile-rest-client-3.0/microprofile-rest-client-spec-3.0.html">MicroProfile REST Client</a>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RegisterRestClient</span><span class="o">(</span><span class="n">baseUri</span> <span class="o">=</span> <span class="s">"https://conf.example.com/api"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">RoomsClient</span> <span class="o">{</span>

    <span class="nd">@GET</span>
    <span class="nd">@Path</span><span class="o">(</span><span class="s">"/rooms/{id}"</span><span class="o">)</span>
    <span class="nd">@Produces</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
    <span class="nc">Room</span> <span class="nf">getById</span><span class="o">(</span><span class="nd">@PathParam</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>This works fine. Then, a few sprints later, the API team <strong>adds</strong> <code class="language-plaintext highlighter-rouge">building</code>, <code class="language-plaintext highlighter-rouge">floor</code>, and <code class="language-plaintext highlighter-rouge">accessibility</code> to the room payload. Your DTO still declares three fields. The response now looks like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"room-7"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Hall A"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"capacity"</span><span class="p">:</span><span class="w"> </span><span class="mi">120</span><span class="p">,</span><span class="w">
  </span><span class="nl">"building"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Main"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"floor"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
  </span><span class="nl">"accessibility"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"wheelchair"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The question is simple: what does <code class="language-plaintext highlighter-rouge">getById("room-7")</code> do now? And the answer, annoyingly, is that it depends entirely on which JSON provider is doing the deserialization. The MicroProfile REST Client spec does not decide this for you &#8211; it delegates the actual JSON binding to whatever provider is on the classpath.</p>

<h2 id="why-this-bites-people">Why this bites people</h2>

<p>This is worth a whole post because the default behaviour is <em>inconsistent</em> between providers, and the failure shows up at the worst possible time &#8211; in production, when someone else&#8217;s API changes underneath you.</p>

<p>A JSON library can reasonably do one of two things when it meets a field that has no home in your DTO:</p>

<ul>
  <li><strong>Be lenient (tolerant reader):</strong> ignore the unknown field and move on. This means an additive change on the server side does not break your client.</li>
  <li><strong>Be strict:</strong> treat an unknown field as a mistake worth reporting, and throw.</li>
</ul>

<p>This idea is not something REST people invented later. It goes back to early Internet protocol design: TCP&#8217;s <a href="https://www.rfc-editor.org/rfc/rfc793#section-2.10">robustness principle</a> (<a href="https://en.wikipedia.org/wiki/Robustness_principle">overview</a>) says to be conservative in what you send and liberal in what you accept from others. For this particular JSON-client case, the practical reading is simple: if the response gives you all the fields you asked for, extra fields should usually be ignored by the consumer. The modern caveat is important, though: this is not a license to accept malformed or unsafe input. Newer <a href="https://www.rfc-editor.org/rfc/rfc9413">protocol guidance</a> explicitly warns that applying the robustness principle too broadly can create interoperability and security problems.</p>

<p>Neither is wrong. But you really want to <em>know</em> which one you have, because the strict default is the one that turns a backwards-compatible server change into a client-side outage.</p>

<blockquote>
  <p>&#128161; <em><strong>Note:</strong> &#8220;Additive response changes should be safe&#8221; is one of the practical compatibility expectations of REST-style JSON APIs. It only holds if your consumers are tolerant readers. A strict deserializer quietly opts you out of that contract.</em></p>
</blockquote>

<h2 id="show-me-the-code">Show me the code</h2>

<p>I have added a small demo to my <a href="https://github.com/mehmandarov/api-guide-java">API Guide for Java</a> repository. The endpoint in <a href="https://github.com/mehmandarov/api-guide-java/blob/main/src/main/java/com/mehmandarov/confapi/unknownfields/UnknownFieldsResource.java"><code class="language-plaintext highlighter-rouge">UnknownFieldsResource.java</code></a> serves a deliberately over-stuffed room payload at <code class="language-plaintext highlighter-rouge">GET /api/unknown-fields/{id}</code> (the six fields from the introduction), and the <a href="https://github.com/mehmandarov/api-guide-java/blob/main/src/test/java/com/mehmandarov/confapi/unit/Ch7_UnknownFieldsTest.java"><code class="language-plaintext highlighter-rouge">Ch7_UnknownFieldsTest</code></a> unit test shows what each provider does when that payload is mapped onto the three-field <code class="language-plaintext highlighter-rouge">Room</code>. Below, I walk through the defaults and the switch that changes each one.</p>

<hr />

<h2 id="1-the-default-json-b-yasson">1. The default: JSON-B (Yasson)</h2>

<h3 id="what-it-looks-like">What it looks like</h3>

<p>On a typical Jakarta EE / MicroProfile stack without Jackson &#8211; think Open Liberty, Helidon, Payara, and friends &#8211; JSON mapping is commonly handled through <strong>JSON-B</strong>. <a href="https://eclipse-ee4j.github.io/yasson/">Yasson</a> is the JSON-B reference implementation, and it is also what the demo test uses.</p>

<p>The good news: JSON-B <strong>ignores unknown properties by default</strong>. The <code class="language-plaintext highlighter-rouge">Room</code> record above deserializes happily, <code class="language-plaintext highlighter-rouge">building</code> and <code class="language-plaintext highlighter-rouge">floor</code> are dropped on the floor, and your client keeps working.</p>

<p>This is not an accident or an implementation detail of Yasson &#8211; it is in the spec. The <a href="https://jakarta.ee/specifications/jsonb/3.0/jakarta-jsonb-spec-3.0.html">Jakarta JSON Binding specification</a> states that during deserialization, any JSON property that does not map to a class member is ignored.</p>

<h3 id="how-to-call-it">How to call it</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> GET http://localhost:8080/api/unknown-fields/room-7 <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span>
</code></pre></div></div>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET http://localhost:8080/api/unknown-fields/room-7
Accept: application/json
</span></code></pre></div></div>

<p>The HTTP endpoint still returns the six-field payload. The important part happens on the client side: JSON-B maps the fields it knows about into <code class="language-plaintext highlighter-rouge">Room</code> and ignores <code class="language-plaintext highlighter-rouge">building</code>, <code class="language-plaintext highlighter-rouge">floor</code>, and <code class="language-plaintext highlighter-rouge">accessibility</code>.</p>

<p><strong>&#9989; Pros:</strong></p>
<ul>
  <li>Tolerant reader by default &#8211; additive server changes don&#8217;t break you.</li>
  <li>No configuration needed; it&#8217;s the platform default.</li>
  <li>Matches the behaviour most people <em>expect</em> from a REST client.</li>
</ul>

<p><strong>&#10060; Cons:</strong></p>
<ul>
  <li>If you <em>want</em> strictness (e.g. to catch a typo in a field name during development), JSON-B gives you less help there.</li>
  <li>Silently dropping fields can hide the fact that the API has grown, and you&#8217;re missing data you might actually want.</li>
</ul>

<p>&#128269; <em><strong>However:</strong> &#8220;Lenient by default&#8221; is the behaviour you usually want for a consumer. Just be aware it is a deliberate choice &#8211; you are trading early failure for compatibility.</em></p>

<hr />

<h2 id="2-jackson-strict-by-default">2. Jackson: strict by default</h2>

<h3 id="what-it-looks-like-1">What it looks like</h3>

<p>The moment you use an unconfigured Jackson mapper or a bare Jackson provider &#8211; for example a plain <code class="language-plaintext highlighter-rouge">ObjectMapper</code>, <code class="language-plaintext highlighter-rouge">resteasy-jackson</code>, or <code class="language-plaintext highlighter-rouge">jersey-media-json-jackson</code> without framework-level configuration &#8211; the default <strong>flips</strong>.</p>

<p>Jackson&#8217;s <code class="language-plaintext highlighter-rouge">ObjectMapper</code> enables <a href="https://fasterxml.github.io/jackson-databind/javadoc/2.18/com/fasterxml/jackson/databind/DeserializationFeature.html#FAIL_ON_UNKNOWN_PROPERTIES"><code class="language-plaintext highlighter-rouge">DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES</code></a> <strong>by default</strong>. An unknown field throws <code class="language-plaintext highlighter-rouge">UnrecognizedPropertyException</code> (a subclass of <code class="language-plaintext highlighter-rouge">JsonMappingException</code>), which typically surfaces through the REST Client as a response-processing/deserialization failure. Your three-field <code class="language-plaintext highlighter-rouge">Room</code> no longer deserializes the six-field payload &#8211; it fails.</p>

<p>There are three common ways to make Jackson lenient, from most local to most global:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1. Per DTO/type - the local fix:</span>
<span class="nd">@JsonIgnoreProperties</span><span class="o">(</span><span class="n">ignoreUnknown</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">record</span> <span class="nf">Room</span><span class="o">(</span><span class="nc">String</span> <span class="n">id</span><span class="o">,</span> <span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">int</span> <span class="n">capacity</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span>
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 2. Per ObjectMapper - the application-wide fix:</span>
<span class="nc">ObjectMapper</span> <span class="n">mapper</span> <span class="o">=</span> <span class="nc">JsonMapper</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
        <span class="o">.</span><span class="na">disable</span><span class="o">(</span><span class="nc">DeserializationFeature</span><span class="o">.</span><span class="na">FAIL_ON_UNKNOWN_PROPERTIES</span><span class="o">)</span>
        <span class="o">.</span><span class="na">build</span><span class="o">();</span>
</code></pre></div></div>

<p>For the MicroProfile REST Client specifically, you&#8217;d expose that configured <code class="language-plaintext highlighter-rouge">ObjectMapper</code> through a <code class="language-plaintext highlighter-rouge">ContextResolver&lt;ObjectMapper&gt;</code> so the client picks it up:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 3. Hand the configured mapper to the REST Client via a ContextResolver:</span>
<span class="nd">@Provider</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LenientJacksonProvider</span> <span class="kd">implements</span> <span class="nc">ContextResolver</span><span class="o">&lt;</span><span class="nc">ObjectMapper</span><span class="o">&gt;</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ObjectMapper</span> <span class="n">mapper</span> <span class="o">=</span> <span class="nc">JsonMapper</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
            <span class="o">.</span><span class="na">disable</span><span class="o">(</span><span class="nc">DeserializationFeature</span><span class="o">.</span><span class="na">FAIL_ON_UNKNOWN_PROPERTIES</span><span class="o">)</span>
            <span class="o">.</span><span class="na">build</span><span class="o">();</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">ObjectMapper</span> <span class="nf">getContext</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">mapper</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>In a real MicroProfile REST Client, make sure this provider is actually registered with the client, for example with <code class="language-plaintext highlighter-rouge">@RegisterProvider</code>, MicroProfile REST Client configuration, or your runtime&#8217;s provider discovery mechanism.</p>

<p>The <a href="https://fasterxml.github.io/jackson-annotations/javadoc/2.18/com/fasterxml/jackson/annotation/JsonIgnoreProperties.html"><code class="language-plaintext highlighter-rouge">@JsonIgnoreProperties(ignoreUnknown = true)</code></a> annotation is the one most people reach for first, because it is right next to the DTO and easy to read.</p>

<h3 id="how-to-call-it-1">How to call it</h3>

<p>The HTTP call is identical &#8211; the difference is entirely server-payload vs. client-config. With strict Jackson and the six-field payload, the response is still valid JSON, but a Jackson-backed client trying to deserialize it into the three-field <code class="language-plaintext highlighter-rouge">Room</code> now fails:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> GET http://localhost:8080/api/unknown-fields/room-7 <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span>
</code></pre></div></div>

<p><strong>&#9989; Pros:</strong></p>
<ul>
  <li>Catches typos and contract drift early &#8211; a renamed field shows up as a loud failure, not silent data loss.</li>
  <li>Explicit: you opt in to every field you accept.</li>
</ul>

<p><strong>&#10060; Cons:</strong></p>
<ul>
  <li>An additive, backwards-compatible server change breaks your client. This is the one that surprises people.</li>
  <li>The fix lives in client code/config, which means you can&#8217;t always fix it quickly if you don&#8217;t own the client.</li>
</ul>

<p>&#9888;&#65039; <em><strong>Caution:</strong> If you consume third-party APIs with raw Jackson defaults, you are one additive change away from an incident. Either set <code class="language-plaintext highlighter-rouge">@JsonIgnoreProperties(ignoreUnknown = true)</code> on your DTOs, or disable <code class="language-plaintext highlighter-rouge">FAIL_ON_UNKNOWN_PROPERTIES</code> globally &#8211; and do it deliberately, not by accident.</em></p>

<hr />

<h2 id="3-quarkus-jackson-but-lenient">3. Quarkus: Jackson, but lenient</h2>

<h3 id="what-it-looks-like-2">What it looks like</h3>

<p>Here is where it gets interesting, and where a lot of confusion comes from. Quarkus uses Jackson for a great deal of its JSON handling &#8211; but it does <strong>not</strong> keep Jackson&#8217;s strict default.</p>

<p>Quarkus ships with <code class="language-plaintext highlighter-rouge">quarkus.jackson.fail-on-unknown-properties=false</code> as its <strong>default</strong>, which means a Quarkus app with Jackson <strong>ignores unknown properties out of the box</strong> &#8211; the opposite of what you&#8217;d get from a bare <code class="language-plaintext highlighter-rouge">ObjectMapper</code>. This is documented in the <a href="https://quarkus.io/guides/all-config#quarkus-jackson_quarkus-jackson-fail-on-unknown-properties">Quarkus Jackson configuration reference</a> and the <a href="https://quarkus.io/guides/rest-json">Quarkus JSON guide</a>.</p>

<p>So the same Jackson library behaves differently depending on whether Quarkus configured it for you or you <code class="language-plaintext highlighter-rouge">new</code>-ed up an <code class="language-plaintext highlighter-rouge">ObjectMapper</code> yourself. If you want the strict behaviour back, you flip the property:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># application.properties - opt back in to strict deserialization
</span><span class="py">quarkus.jackson.fail-on-unknown-properties</span><span class="p">=</span><span class="s">true</span>
</code></pre></div></div>

<p>&#8230;or override it for a single class with the same <code class="language-plaintext highlighter-rouge">@JsonIgnoreProperties</code> annotation from &#167;2.</p>

<h3 id="how-to-call-it-2">How to call it</h3>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET http://localhost:8080/api/unknown-fields/room-7
Accept: application/json
</span></code></pre></div></div>

<p>On Quarkus defaults, this succeeds even with the six-field payload, because Quarkus pre-configured Jackson to be lenient.</p>

<p><strong>&#9989; Pros:</strong></p>
<ul>
  <li>Sensible &#8220;tolerant reader&#8221; default for a consumer, even though the underlying library is Jackson.</li>
  <li>One property toggles the behaviour for the whole app.</li>
</ul>

<p><strong>&#10060; Cons:</strong></p>
<ul>
  <li>It diverges from &#8220;stock Jackson&#8221;, which trips up anyone who knows Jackson&#8217;s default and assumes it applies here.</li>
  <li>Behaviour now depends on <em>where</em> the <code class="language-plaintext highlighter-rouge">ObjectMapper</code> comes from (Quarkus-managed vs. hand-rolled).</li>
</ul>

<p>&#129514; <em><strong>Observation:</strong> This is a perfect example of why &#8220;we use Jackson&#8221; is not enough information. The framework around Jackson decides the default, and Quarkus and a plain <code class="language-plaintext highlighter-rouge">ObjectMapper</code> land on opposite answers.</em></p>

<hr />

<h2 id="4-spring-boot-also-lenient">4. Spring Boot: also lenient</h2>

<h3 id="what-it-looks-like-3">What it looks like</h3>

<p>Spring Boot ends up in the same place as Quarkus: it uses Jackson, but configures it to be lenient by default. Stock Jackson is strict, but in the current <a href="https://docs.spring.io/spring-boot/4.1.0/reference/features/json.html">Spring Boot 4.1 reference documentation</a>, <code class="language-plaintext highlighter-rouge">spring.jackson.deserialization.fail-on-unknown-properties=false</code> is the documented default, so Spring also <strong>ignores</strong> unknown fields out of the box.</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># application.properties - flip Spring back to strict if you want it
</span><span class="py">spring.jackson.deserialization.fail-on-unknown-properties</span><span class="p">=</span><span class="s">true</span>
</code></pre></div></div>

<p>So if you&#8217;re coming from Spring, the surprise is similar to Quarkus: you are using Jackson, but not stock Jackson defaults.</p>

<hr />

<h2 id="a-note-on-the-other-direction-server-receiving-extra-fields">A note on the other direction (server receiving extra fields)</h2>

<p>So far we&#8217;ve looked at the <em>client</em> receiving more than it expected. The mirror image is your <em>server</em> receiving a request body with extra fields &#8211; a client POSTs more than your endpoint&#8217;s DTO declares. The good news is that it is the <strong>same providers and the same switches</strong>:</p>

<ul>
  <li>On <strong>JSON-B / Yasson</strong>, the extra fields in the inbound body are ignored by default.</li>
  <li>On <strong>raw Jackson</strong>, the inbound body fails with <code class="language-plaintext highlighter-rouge">UnrecognizedPropertyException</code> unless you set <code class="language-plaintext highlighter-rouge">@JsonIgnoreProperties(ignoreUnknown = true)</code> or disable <code class="language-plaintext highlighter-rouge">FAIL_ON_UNKNOWN_PROPERTIES</code>.</li>
  <li>On <strong>Quarkus</strong> and <strong>Spring Boot</strong>, the extra fields are ignored by default, for the same reasons as above.</li>
</ul>

<p>There is one extra wrinkle worth flagging on the server side: silently ignoring unknown fields on <em>input</em> can be a mild security/robustness smell. A client sending fields you don&#8217;t recognise might be confused, might be on the wrong API version, or might be probing. Strictness on input is sometimes a feature, not a bug. This is the opposite of what is expected for a consumer.</p>

<blockquote>
  <p>&#128161; <em><strong>Note:</strong> The mental model is &#8220;tolerant on the way in is convenient, strict on the way in is defensive&#8221;. You get to choose per endpoint &#8211; just choose on purpose.</em></p>
</blockquote>

<hr />

<h2 id="summary-comparison">Summary Comparison</h2>

<p>The table below assumes the six-field JSON payload from the introduction being mapped onto the three-field <code class="language-plaintext highlighter-rouge">Room</code> DTO.</p>

<table class="bordered-table">
  <thead>
    <tr>
      <th>Provider / stack</th>
      <th>Default on unknown fields</th>
      <th>Result with extra fields</th>
      <th>How to flip it</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>JSON-B / Yasson</strong></td>
      <td>Ignore</td>
      <td>&#9989; Deserializes, extra fields dropped</td>
      <td>(Already lenient; strictness needs custom validation/deserialization logic)</td>
    </tr>
    <tr>
      <td><strong>Jackson (stock)</strong></td>
      <td><strong>Fail</strong></td>
      <td>&#10060; <code class="language-plaintext highlighter-rouge">UnrecognizedPropertyException</code></td>
      <td><code class="language-plaintext highlighter-rouge">@JsonIgnoreProperties(ignoreUnknown=true)</code> or disable the feature</td>
    </tr>
    <tr>
      <td><strong>Quarkus + Jackson</strong></td>
      <td>Ignore</td>
      <td>&#9989; Deserializes, extra fields dropped</td>
      <td><code class="language-plaintext highlighter-rouge">quarkus.jackson.fail-on-unknown-properties=true</code></td>
    </tr>
    <tr>
      <td><strong>Spring Boot + Jackson</strong></td>
      <td>Ignore</td>
      <td>&#9989; Deserializes, extra fields dropped</td>
      <td><code class="language-plaintext highlighter-rouge">spring.jackson.deserialization.fail-on-unknown-properties=true</code></td>
    </tr>
  </tbody>
</table>

<p>The one row that catches people out is <strong>Jackson (stock)</strong> &#8211; and by extension any MicroProfile REST Client where you added a bare Jackson provider without configuring it.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The MicroProfile REST Client doesn&#8217;t have an opinion on unknown fields &#8211; it hands that decision to your JSON provider, and the providers don&#8217;t agree:</p>

<ul>
  <li><strong>JSON-B / Yasson</strong> ignores them, by spec.</li>
  <li><strong>Stock Jackson</strong> fails, by its own default.</li>
  <li><strong>Quarkus and Spring Boot</strong> both use Jackson but pre-configure it to ignore them.</li>
</ul>

<p>So the practical advice is short. If you&#8217;re writing a consumer, you almost certainly want the tolerant-reader behaviour, so additive changes on the server don&#8217;t page you at 2am. On JSON-B you already have it. On raw Jackson, add <code class="language-plaintext highlighter-rouge">@JsonIgnoreProperties(ignoreUnknown = true)</code> to your DTOs (or disable <code class="language-plaintext highlighter-rouge">FAIL_ON_UNKNOWN_PROPERTIES</code> once, globally) and be explicit that you&#8217;ve made that choice. And whatever you do, know which default you&#8217;re actually running &#8211; because &#8220;we use Jackson&#8221; tells you almost nothing until you also know what configured it.</p>

<h2 id="whats-next">What&#8217;s Next?</h2>

<p>Unknown fields are one small corner of building REST clients and APIs that survive change. The same <a href="https://github.com/mehmandarov/api-guide-java">API Guide for Java</a> repo covers OpenAPI documentation, error handling, security, pagination, and versioning &#8211; see my earlier posts on <a href="/api-versioning/">API versioning in Java using JAX-RS</a> and <a href="/rfc-9457-problem-details-jakarta-ee/">RFC 9457 Problem Details</a>. The next post in this little run looks at the opposite of returning a <code class="language-plaintext highlighter-rouge">byte[]</code>: building an endpoint that <em>accepts</em> binary attachments as part of the payload.</p>

<p><strong><em>Happy (and tolerant) reading, folks!</em></strong></p>

<hr />]]></content><author><name>Rustam Mehmandarov</name></author><summary type="html">What happens when a JSON response contains more fields than your DTO declares? The answer depends on your JSON provider. A look at the defaults for JSON-B, Jackson, Quarkus, and Spring when consuming an API with the MicroProfile REST Client &#8211; with the official docs to back each one up.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://mehmandarov.com/assets/images/posts-images/computers.jpg"/><category term="blog"/><category term="english"/><category term="java"/><category term="api"/><category term="jakarta ee"/><category term="microprofile"/><category term="jax-rs"/><category term="quarkus"/><category term="rest client"/><category term="spring boot"/></entry></feed>
