Definition
- 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 aSummary-tagged field automatically appears inDetailandAdminviews. - 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.
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.
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.
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);
}
}
// 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.
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.
// 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 |
// 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.
// 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
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.
---