Back to API Design Best Practices
Q6Question 6 of 22

How does Jackson @JsonView let a single DTO/entity serialize different field subsets per endpoint

Key Concepts
  • JsonView defines named views as marker interfaces and annotates each field with the views it belongs to.
  • View hierarchies (Detail extends Summary, Admin extends Detail) let fields cascade so a Summary field appears in all views.
  • The controller activates a view with @JsonView and Jackson serializes only the matching fields.
  • MappingJacksonValue.setSerializationView chooses the view dynamically at runtime, useful for role-based visibility.
  • JsonView reduces DTO explosion by producing multiple field subsets from one class.
  • A dedicated DTO is still better when shapes differ structurally, when there are computed values, or when combining multiple models.

Definition

JsonView tags fields with named views so one class serializes different subsets per endpoint, avoiding near-identical DTOs, but a separate DTO wins when the shape, computed values, or combined sources differ.
  • JsonView – a Jackson feature that lets you define named views as marker interfaces, annotate fields with @JsonView, and activate a view per endpoint so only tagged fields are serialized.
  • View hierarchy – views can extend one another (e.g. Detail extends Summary), so a Summary-tagged field automatically appears in Detail and Admin views.
  • Static activation – the controller method uses @JsonView(Views.Summary.class) to select the view at compile time.
  • Dynamic activation – MappingJacksonValue.setSerializationView(viewClass) picks the view at runtime, for example based on the caller's role.
  • DTO explosion – the problem of creating many near-identical DTOs that differ only in which fields are visible; JsonView solves this for simple cases.

At National Bank of India, imagine one master account card with coloured tags on each line. Rajesh the customer wears green glasses and sees only the green-tagged lines: name and balance. The branch teller wears blue glasses and sees green plus blue lines: name, balance, and branch code. The fraud manager wears red glasses and sees everything including the internal risk score. JsonView is those coloured glasses: one card, different subsets for different viewers.

Understanding the Concepts

What is Jackson @JsonView?

JsonView is a Jackson feature that lets you control which fields are serialized per endpoint. You define views as marker interfaces, annotate each field with the views it belongs to, and activate a view on the controller method. Jackson serializes only the fields tagged for the active view.

JAVA
package com.bank.view;

public class Views {
    public interface Summary {}
    public interface Detail extends Summary {}   // includes Summary fields
    public interface Admin extends Detail {}     // includes Detail + Summary
}

How do you tag fields with views?

Each field is annotated with @JsonView referencing the view where it first appears. A field tagged Summary appears in all three views due to the hierarchy. A field tagged Admin appears only in the admin view.

JAVA
package com.bank.dto;

import com.bank.view.Views;
import com.fasterxml.jackson.annotation.JsonView;
import java.math.BigDecimal;

public class AccountView {
    @JsonView(Views.Summary.class) private String accountId;
    @JsonView(Views.Summary.class) private String holderName;
    @JsonView(Views.Summary.class) private BigDecimal balance;
    @JsonView(Views.Detail.class)  private String branchCode;
    @JsonView(Views.Detail.class)  private String ifscCode;
    @JsonView(Views.Admin.class)   private int internalRiskScore;
    // getters and setters
}

How do you activate a view on the controller?

You annotate the controller method with @JsonView(Views.Summary.class) and Jackson serializes only the matching fields.

JAVA
package com.bank.controller;

import com.bank.dto.AccountView;
import com.bank.view.Views;
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/accounts")
public class AccountController {

    @GetMapping("/{id}/summary")
    @JsonView(Views.Summary.class)
    public AccountView summary(@PathVariable String id) {
        return accountService.getView(id);
    }

    @GetMapping("/{id}/detail")
    @JsonView(Views.Detail.class)
    public AccountView detail(@PathVariable String id) {
        return accountService.getView(id);
    }

    @GetMapping("/{id}/admin")
    @JsonView(Views.Admin.class)
    public AccountView admin(@PathVariable String id) {
        return accountService.getView(id);
    }
}
TEXT
// GET /api/v1/accounts/SAV2001/summary
{ "accountId": "SAV2001", "holderName": "Rajesh", "balance": 150000.00 }

// GET /api/v1/accounts/SAV2001/admin
{ "accountId": "SAV2001", "holderName": "Rajesh", "balance": 150000.00,
  "branchCode": "NBI-001", "ifscCode": "NBI0000001", "internalRiskScore": 42 }

How do you choose the view dynamically at runtime?

When the view depends on the caller's role rather than the endpoint, wrap the object in MappingJacksonValue and call setSerializationView with the chosen view class.

JAVA
import org.springframework.http.converter.json.MappingJacksonValue;

@GetMapping("/{id}")
public MappingJacksonValue get(@PathVariable String id) {
    AccountView account = accountService.getView(id);
    MappingJacksonValue wrapper = new MappingJacksonValue(account);

    if (SecurityContextHolder.getContext().getAuthentication()
            .getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
        wrapper.setSerializationView(Views.Admin.class);
    } else {
        wrapper.setSerializationView(Views.Summary.class);
    }
    return wrapper;
}

How does JsonView reduce DTO explosion?

Without views, you might create AccountSummaryResponse, AccountDetailResponse, and AccountAdminResponse, three classes that are mostly the same fields plus three mappers. With JsonView, one class and tagging produce all three subsets.

JAVA
// Without JsonView – three near-identical DTOs:
record AccountSummaryResponse(String accountId, String holderName, BigDecimal balance) {}
record AccountDetailResponse(String accountId, String holderName, BigDecimal balance,
                              String branchCode, String ifscCode) {}
record AccountAdminResponse(String accountId, String holderName, BigDecimal balance,
                             String branchCode, String ifscCode, int internalRiskScore) {}

// With JsonView – one class, three views:
// (see AccountView above)

When is a dedicated DTO still better?

JsonView can only include or exclude existing fields. It cannot reshape, rename, compute, or combine data from multiple sources. A dedicated DTO is better in four cases.

| Scenario | JsonView | Dedicated DTO |
|---|---|---|
| Same fields, different visibility | Ideal | Overkill |
| Computed values (e.g. available balance) | Cannot express | Clean |
| Different structure or nesting | Cannot express | Required |
| Combining multiple models | Cannot express | Required |

JAVA
// This needs a dedicated DTO – combines Account + Customer + Transactions:
public record DashboardResponse(
        String accountId,
        String customerName,
        String customerEmail,
        BigDecimal balance,
        BigDecimal availableBalance,     // computed: balance - holds
        List<TransactionSummary> recent  // from a different entity
) {}
// No single JsonView-tagged class can express this.

What are common mistakes with JsonView?

A common mistake is using JsonView when the shapes genuinely differ structurally. Forcing views to reshape data they cannot handle leads to awkward workarounds. Another mistake is forgetting that fields without any @JsonView annotation are included in all views by default, which can accidentally expose fields.

JAVA
// Wrong – field without @JsonView is always serialized:
public class AccountView {
    @JsonView(Views.Summary.class) private String accountId;
    private String secretNote;  // no annotation → serialized in EVERY view!
}

// Correct – tag every field explicitly or set DEFAULT_VIEW_INCLUSION to false:

What should a senior engineer know?

A senior engineer configures MapperFeature.DEFAULT_VIEW_INCLUSION to false in the ObjectMapper so that unannotated fields are excluded by default, which is safer. They use JsonView for the narrow case of the same fields in overlapping subsets, especially admin versus public views. They reach for dedicated DTOs when the structure, computed values, or data sources genuinely differ. And they never apply @JsonView directly to JPA entities, instead using it on a dedicated view class that is populated by a service-layer mapper, keeping persistence and serialization concerns separate.

Visual Overview

DIAGRAM
  View Hierarchy:

  ┌───────────────────────┐
  │  Admin (extends Detail)│  sees: accountId, holderName, balance,
  │                        │        branchCode, ifscCode, internalRiskScore
  │  ┌───────────────────┐ │
  │  │ Detail (ext Summary│ │  sees: accountId, holderName, balance,
  │  │                   │ │        branchCode, ifscCode
  │  │ ┌───────────────┐ │ │
  │  │ │  Summary      │ │ │  sees: accountId, holderName, balance
  │  │ └───────────────┘ │ │
  │  └───────────────────┘ │
  └───────────────────────┘

  Controller → @JsonView(Views.Summary.class) → Jackson filters fields

Key Takeaway

Jackson JsonView lets one class serialize different field subsets per endpoint by defining named views as marker interfaces and tagging each field with the views it belongs to. You build a small hierarchy: Summary, Detail extends Summary, Admin extends Detail, so a field tagged Summary appears in all three while a field tagged Admin appears only for admins. On the controller you activate a view with the @JsonView annotation, and Jackson serializes only the matching fields.

The value is reducing DTO explosion. At National Bank of India, the team exposes one account through a public summary, an internal detail, and a fraud-team admin view. The fields overlap heavily, differing only in which are visible. Without views that would be three nearly identical DTOs and three mappers. With JsonView, one tagged model produces all three subsets, which is far less code to maintain.

But JsonView has limits. It can only include or exclude existing fields of a single model. When the shapes differ structurally, when a view needs computed values, when there is heavy transformation, or when a response combines multiple models, a dedicated DTO is better. The account dashboard combines the account, the customer name, a computed available balance, and recent transactions, which no single tagged model can express, so it uses a purpose-built DashboardResponse DTO.

The rule is simple: if the only difference between endpoints is which existing fields of one resource are visible, use JsonView. If the structure, computed values, or data sources genuinely differ, use a dedicated DTO. Choosing between them thoughtfully avoids both DTO explosion and the trap of forcing views to do reshaping they were never designed for.

Remember one sentence: JsonView is coloured glasses on one card, perfect for showing different field subsets, but a different card entirely when the structure must change.

---

Share
Use ← → arrow keys to navigate