CodeGym /Courses /Spring REST & MVC /Unknown fields in JSON: strict/ tolerant

Unknown fields in JSON: strict/ tolerant

Spring REST & MVC
Level 12, Lesson 3
Available

1. Unknown fields

If you have ever caught yourself thinking, “Well, if the JSON is valid, then everything is fine,” today you will need to let that thought go carefully. In HTTP API, correctness is split into at least two layers: syntax (the JSON can be parsed) and contract (the JSON matches the expected model). An unknown field is like a letter without mistakes, but addressed to someone else: the mail carrier certainly tried, but the letter still has nowhere obvious to go.

Malformed JSON is when JSON cannot be read at all: a comma is missing, a quote is broken, the structure is damaged. Such a request fails “at the door” because the JSON parser cannot build a tree.

Unknown field is when the JSON is perfectly readable, but it contains a key that the DTO does not expect. In other words, the structure is correct, but you added a little “creative improvisation” to the contract.

Let us take our familiar create-task request. Suppose the canonical input looks roughly like this:

{
  "title": "Fix pagination bug",
  "description": "Reproducible on page=2"
}

And the client (or tester, or “future you in two weeks”) sent this instead:

{
  "title": "Fix pagination bug",
  "description": "Reproducible on page=2",
  "createdAt": "2026-03-21T10:15:00Z"
}

The JSON is correct. But createdAt is not a field of the incoming DTO (and in general, it is often a server-managed value). This is where things get interesting: the server must decide whether to treat this as an error or simply silently “pretend nothing happened.”

This is a different problem from hiding known DTO properties with @JsonIgnore or @JsonIgnoreProperties({"..."}). There, the property already exists on the type, and we decide whether it should participate in the JSON contract. Here, on the contrary, the client brought a key that does not exist in the DTO at all.

2. Where unknown fields are caught

To avoid guessing, it is useful to see exactly where unknown fields are checked. In MVC, this happens during request body reading — where the HttpMessageConverter works (for JSON, this is usually a converter based on Jackson). And that means unknown fields are not “service logic” and not a “business rule error.” It is a question of deserializing incoming JSON.

It is convenient to keep this kind of scheme in mind (very roughly, but honestly):

flowchart TD
  A["HTTP request body: JSON"] --> B["HttpMessageConverter"]
  B --> C["Jackson: JSON -> DTO"]
  C --> D["Controller method"]
  D --> E["Service layer"]

In strict mode, at the Jackson: JSON -> DTO stage, something like this happens: Jackson sees the key createdAt, looks for a corresponding field/accessor/record component, does not find it, and says, “Sorry, I did not order that.” As a result, Spring responds to the client with an error, most often status 400 Bad Request.

To see the exact moment of failure, let us take a local DTO fragment that expects only title. What we need here is the specific target type: an unknown field becomes an error not “by itself,” but at the moment when Jackson tries to assemble a readable DTO from JSON.

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

// Local DTO fragment: for this input, Jackson expects only the title field
record StrictTaskCreateRequestExample(String title) {}

@RestController // Teaching controller for demonstrating the moment where deserialization fails
public class DebugController {

    @PostMapping("/api/v1/debug/tasks") // The endpoint should run only if JSON could be assembled into a DTO
    public String create(@RequestBody StrictTaskCreateRequestExample request) {
        System.out.println("Controller method executed"); // With strict handling, the unknown key will not reach this line
        return "ok";
    }
}

The example is still minimal: in a real API, this would be a regular request DTO, validation, and a service layer. But the principle is clear: if Jackson cannot build the target type because of an unknown key under strict policy, the controller method does not run.

From the perspective of API discipline, this is great: the error is caught as early as possible. From the perspective of client UX, it can be painful: the client sent an “extra field,” and the server got upset.

3. Strict and tolerant input

Unknown fields do not have one single correct fate. You can be strict and reject the request. You can be tolerant and ignore extra data. And both approaches can be correct — in different situations. Here we act like adults: we do not choose “what is trendier,” but “what problem are we solving.”

To avoid turning this into philosophy, let us compare the approaches as concretely as possible:

Strategy How the server behaves Pros Cons
Strict input “There is an extra field, so the request is not following the contract; I respond with an error” Quickly catches client typos and wrong expectations; the contract becomes “self-documenting” through errors Can break clients during soft changes; sometimes gets in the way of integrations where the payload is “noisy”
Tolerant input “Extra fields do not matter — I ignore them and read only the known ones” Useful for soft migrations; resilient to “noisy” clients; fewer failures because of non-essential fields May hide client errors (typos, field placed in the wrong spot), especially if the field is optional

The most typical dilemma looks like this. The client made a typo:

{ "assigneName": "Alice", "title": "..." }

If input is tolerant, assigneName is simply ignored. If assigneeName is optional, the request will pass, but the task will end up without an assignee, and later you will be asking, “Why are our tasks empty?” That is not a bug; that is your “tolerance” strategy.

If input is strict, the server will immediately return an error: “Unknown field assigneName.” The client will fix the typo, and everyone will be calmer.

On the other hand, the client may have sent extra fields not out of malice, but because they simply took an object from the response (which has id, status, createdAt) and sent it back. Technically, that is not following the contract, but humanly it is understandable. In tolerant mode, this will “sort of” work, even if it is questionable.

4. Tolerant input: @JsonIgnoreProperties

Now let us move to the main practical tool of the lecture. If you want to tell Jackson, “When you read JSON into this DTO, ignore extra fields,” then the simplest and most readable way is the @JsonIgnoreProperties(ignoreUnknown=true) annotation on the request DTO.

The annotation name is familiar, but the task is different here: we are not listing known DTO properties that should be hidden; we are setting the policy for keys that do not exist in the DTO at all.

In our Task Tracker API project, this would usually live in the api.dto.request package.

Let us take a local request DTO fragment only for this policy: there are as few fields as possible so that the behavior of ignoreUnknown is clearly visible.

package com.example.tasktracker.api.dto.request;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true) // Tell Jackson: unknown keys in JSON are not an error
public record TaskCreateRequestTolerantExample(
        String title, // DTO fields are the incoming contract: we read only what is listed here
        String description,
        String assigneeName
) {
}

Now if the client sends:

{
  "title": "Fix pagination bug",
  "description": "Reproducible on page=2",
  "createdAt": "2026-03-21T10:15:00Z",
  "debugMode": true
}

then createdAt and debugMode will simply be ignored, and the DTO will still be assembled successfully. Your controller method will receive a TaskCreateRequestTolerantExample(title=..., description=..., assigneeName=...) object as if nothing had happened.

And here it is important to say something that people often skip: ignoreUnknown=true is not “less secure.” It is about how much of a “teacher” you want to be for the client. Strict is a strict teacher: “That is not allowed.” Tolerant is a kind teacher: “Okay, I understand what you meant... probably.”

5. @JsonAlias and tolerant input

When we discussed @JsonAlias, we said: an alias is a bridge for reading input, but the contract should still have a canonical name. In real life, an alias often goes hand in hand with tolerant input, especially when client payloads are “different and beautiful in different ways.”

Let us show a neat but realistic request DTO that:

1) officially accepts assigneeName,
2) also reads assignee as an alias,
3) and at the same time does not fail on unknown fields.

Now let us take another local fragment: here we need to combine two things — an alias for a known field and tolerance for completely extra keys.

package com.example.tasktracker.api.dto.request;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties(ignoreUnknown = true) // We are tolerant of extra keys in JSON
public record TaskCreateRequestCompatibilityExample(
        @JsonProperty("title") // Explicitly fix the field name in JSON (the canonical contract name)
        String title,

        String description,

        @JsonProperty("assigneeName") // Canonical field name that we "promote" in the documentation
        @JsonAlias("assignee") // Alternative name for reading input (for migration/legacy clients, for example)
        String assigneeName
) {
}

Now several input variants are possible.

Canonical (what we want to see in the documentation and examples):

{ "title": "Write docs", "assigneeName": "Bob" }

Old/alternative (the input is read, but it does not become the “official” one):

{ "title": "Write docs", "assignee": "Bob" }

Noisy (the input is read, and the extra data is ignored):

{
  "title": "Write docs",
  "assignee": "Bob",
  "id": "I-wish-I-could-set-it",
  "createdAt": "yesterday",
  "someWeirdFlag": true
}

It is important that @JsonAlias solves one problem here (an alternative name for a known field), while ignoreUnknown=true solves another (ignoring unknown fields). These mechanisms do not compete — they complement each other.

6. Choosing a strategy for Task Tracker API

Now let us apply this to our training project. Our API is contract-driven, JSON-first, and we do not want to turn it into a “receiver of any JSON documents at the input.” So the basic idea is simple: if there is no good reason to tolerate extra data, it is better to be strict. This saves you hours of debugging and makes server behavior predictable.

At the same time, there are cases where tolerant input really improves life. For example, if you have an integration that consistently sends “extra fields” but also sends the necessary ones. In that case, ignoring them can be a pragmatic compromise: you do not break the integration because of noise, while still controlling the contract through the DTO (you do not map the extra data into the model).

The key point is that the DTO protects you from over-posting. Even if the client sends a status or createdAt field, it will not reach our domain object because our TaskCreateRequest does not have such a field. In tolerant mode, it will simply be ignored. In strict mode, the request will be rejected. This is not a “safe/unsafe” question at the data level. It is a question of “what contract behavior do we want.”

And one more rule that surprisingly often saves the day: if a field in the request DTO is optional, tolerant input can turn a simple client typo into a silent bug. Strict mode helps in such places because you catch the error where it was born, not a week later in a report saying “why is our assigneeName always null?”

7. Demo: strict vs tolerant over HTTP

To lock in the feeling, it helps to see this through the client’s eyes. Imagine a task creation endpoint (we already have it in the course) and two DTO variants: strict and tolerant.

To compare the behavior in its pure form, let us again take two small fragments of the same create input. These are not two parallel versions of the project DTO, but one and the same input point with different JSON reading policies.

Option A: strict DTO

package com.example.tasktracker.api.dto.request;

// Strict variant: any unknown field in JSON can turn into a deserialization error (depending on Jackson settings)
public record StrictTaskCreateRequest(
        String title, // Incoming contract: we expect only these fields
        String description
) {
}

Option B: tolerant DTO

package com.example.tasktracker.api.dto.request;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true) // Tolerant variant: extra fields in JSON are ignored
public record TolerantTaskCreateRequest(
        String title, // We read only known fields; "extra" data will not reach the DTO
        String description
) {
}

And here is the .http request that the client might send:

POST http://localhost:8080/api/v1/tasks
Content-Type: application/json
Accept: application/json

{
  "title": "Fix NPE",
  "description": "Null happens in TaskMapper",
  "createdAt": "2026-03-21T10:15:00Z"
}

In strict mode (if Jackson is configured so that unknown fields are treated as an error), the server will respond with an error, most often 400 Bad Request. In tolerant mode, the request will pass quietly, and createdAt will disappear as if it had never been there. Roughly like a sock after washing: only you remember it existed, while reality acts like it did not.

And once again, the important point: tolerant input does not mean the server “accepted createdAt.” The server ignored it. If you want the client to definitely understand that it sent something extra, tolerant input is not your tool.

8. Typical mistakes with strict/tolerant input

The mistakes in this topic are usually not about annotation syntax, but about the developer not fully understanding the consequences of the chosen strategy. So I will list the most common pitfalls the way they look in a real project — “it worked yesterday, today it is weird.”

Mistake #1: turn on tolerant input “just in case” and then wonder why client errors are not caught.
When ignoreUnknown=true is placed in a request DTO without a reason, the server starts “tolerating” even trivial typos. The client sends assigneName instead of assigneeName — the server stays silent, the field becomes null, and the bug surfaces later and not where it is easy to fix. If you want contract discipline, tolerance should be a rare and deliberate exception.

Mistake #2: assuming tolerant input fully solves contract migration.
Tolerance of unknown fields really helps when the client sends extra data. But it does not help when the client sends an old field that became “known but renamed.” Here you need @JsonAlias or another explicit solution. Otherwise you are simply ignoring data that the client tried to send with meaning, and that is even worse than failing with an error.

Mistake #3: using tolerant input as a “hidden repair” for a bad DTO.
Sometimes a request DTO is built in a way that is inconvenient for clients, and they keep sending extra fields (because they use one shared object for all operations). Turning on ignoreUnknown is a quick bandage, but if it becomes the norm, that is a signal: the contract should be revisited. The annotation should strengthen the design, not hide the fact that it is awkward.

Mistake #4: turn on tolerance on a response DTO and expect it to change something.
Unknown fields are an incoming JSON story. On the way out, you serialize the DTO into JSON, and fields that do not exist simply will not appear. So @JsonIgnoreProperties on a response DTO is usually either useless or misleading: it creates the feeling that we are “controlling the response,” while what we are actually controlling is deserialization behavior.

Mistake #5: confuse unknown fields with broken JSON and try to “fix” everything with one setting.
ignoreUnknown will not save you from a missing comma and will not turn broken JSON into working JSON. It solves one specific problem: “the JSON has extra keys that are not in the DTO.” If malformed JSON arrives often, the cause is usually the client or the wrong Content-Type, not Jackson annotations.

1
Task
Spring REST & MVC, level 12, lesson 3
Locked
Strict JSON deserialization with an extra field
Strict JSON deserialization with an extra field
1
Task
Spring REST & MVC, level 12, lesson 3
Locked
Tolerant JSON deserialization with an unknown key
Tolerant JSON deserialization with an unknown key
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION