skip to content

What is @JsonView and how do you use it in Spring MVC to expose different fields on different endpoints?

level: seniorimportance: should knowfreq 45%

answer

  1. Marker classes/interfaces as view tags; inheritance widens
  2. @JsonView on field + on controller method
  3. AbstractJackson2HttpMessageConverter -> writerWithView
  4. DEFAULT_VIEW_INCLUSION=true leaks untagged fields
  5. Also filters @RequestBody input

basics

~10 s

@JsonView lets one class expose different field subsets per endpoint. You define marker (view) classes, tag fields with @JsonView(View.class), and put @JsonView on the controller method to pick which view serializes.

solid answer

~40 s

@JsonView solves 'same DTO, different visibility per endpoint' without separate classes. You define marker interfaces/classes (e.g. Views.Public, Views.Internal extends Public), annotate model fields with @JsonView(Views.Public.class), and annotate the controller handler with @JsonView(Views.Internal.class). Spring MVC's AbstractJackson2HttpMessageConverter reads the method-level @JsonView and activates that serialization view, so only fields tagged with that view (or a superview via inheritance) are written. A key gotcha: MapperFeature.DEFAULT_VIEW_INCLUSION defaults to true in Jackson, meaning fields with NO @JsonView are still included even when a view is active; set spring.jackson.mapper.default-view-inclusion=false to make untagged fields hidden. @JsonView also works on @RequestBody for input filtering. It's great for admin-vs-public projections but can get unwieldy versus purpose-built DTOs.

code

java · 24 lines
java
public class Views {
    public interface Public {}
    public interface Internal extends Public {}
}

public class User {
    @JsonView(Views.Public.class)   private Long id;
    @JsonView(Views.Public.class)   private String username;
    @JsonView(Views.Internal.class) private String email;   // hidden from public
    // getters/setters omitted
}

@RestController
class UserController {
    @GetMapping("/users/{id}")
    @JsonView(Views.Public.class)          // only Public-tagged fields serialize
    public User publicView(@PathVariable Long id) { return load(id); }

    @GetMapping("/admin/users/{id}")
    @JsonView(Views.Internal.class)        // Public + Internal fields serialize
    public User adminView(@PathVariable Long id) { return load(id); }
}
// application.properties:
// spring.jackson.mapper.default-view-inclusion=false  (hide untagged fields under a view)

go deeper

for a junior

Know it exists to show different fields per endpoint via marker classes + @JsonView on the method.

for a middle

Wire it end-to-end: view classes, field tags, method annotation, and view inheritance.

for a senior

Explain the DEFAULT_VIEW_INCLUSION leak, input-side filtering, converter mechanics, and DTO trade-offs.

for a principal

Set a team policy: when views are acceptable vs mandated DTOs, and enforce default-view-inclusion=false to prevent data leaks.

## The problem it solves You often have one domain/DTO object but want different endpoints to expose different subsets of its fields — e.g. a public profile endpoint hides email and internal flags, while an admin endpoint shows everything. Creating separate DTO classes works but duplicates code. `@JsonView` lets a single class carry per-view visibility. ## Defining views Views are just marker types (classes or interfaces) used as tags. Inheritance defines widening scopes: ```java public class Views { public interface Public {} public interface Internal extends Public {} // Internal sees Public fields too } ``` ## Tagging fields ```java public class User { @JsonView(Views.Public.class) private Long id; @JsonView(Views.Public.class) private String username; @JsonView(Views.Internal.class) private String email; @JsonView(Views.Internal.class) private boolean locked; } ``` A field tagged with `Views.Public` is visible in both Public and Internal views (because Internal extends Public). A field tagged `Views.Internal` is visible only in Internal. ## Activating a view in Spring MVC Put `@JsonView` on the handler method: ```java @GetMapping("/users/{id}") @JsonView(Views.Public.class) public User publicView(@PathVariable Long id) { ... } @GetMapping("/admin/users/{id}") @JsonView(Views.Internal.class) public User adminView(@PathVariable Long id) { ... } ``` Spring's `AbstractJackson2HttpMessageConverter`/`MappingJackson2HttpMessageConverter` detects the method-level `@JsonView` (via `JsonViewResponseBodyAdvice`) and calls `objectMapper.writerWithView(...)`, so only the matching fields serialize. For programmatic responses you can also return `MappingJacksonValue` and call `setSerializationView(...)`. ## The DEFAULT_VIEW_INCLUSION gotcha (critical) `MapperFeature.DEFAULT_VIEW_INCLUSION` in core Jackson defaults to **true**: properties with **no** `@JsonView` annotation are included in **every** view. So if you forget to tag a sensitive field, it leaks into the Public view. To make untagged fields excluded when a view is active, disable it: ``` spring.jackson.mapper.default-view-inclusion=false ``` With it false, only explicitly-tagged fields appear under a view. ## Input side `@JsonView` on a `@RequestBody` parameter filters which fields are honored during deserialization (Spring uses `JsonViewRequestBodyAdvice`), letting you reject/ignore fields a caller shouldn't set. ## Edge cases & gotchas - No `@JsonView` on the method -> no view is active -> all fields serialize normally (view tags are ignored). - View inheritance is one-directional: a subview sees superview fields, not vice versa. - Combining `@JsonView` with `@JsonUnwrapped` or polymorphism can behave surprisingly; test it. - Only one active view per response. ## When to use vs alternatives - Good for a small number of orthogonal projections of the same shape (public/internal/summary/detail). - Prefer dedicated DTOs (or MapStruct/records) when projections diverge structurally, when field names differ, or when you want compile-time clarity over annotation magic. Views can become hard to reason about as they multiply.

  • If a field has no @JsonView and an endpoint activates the Public view, is that field serialized?
    By default yes, because MapperFeature.DEFAULT_VIEW_INCLUSION is true — untagged fields appear in all views. Set spring.jackson.mapper.default-view-inclusion=false to exclude untagged fields when a view is active. This is a common accidental-leak source.
  • When would you choose separate DTOs over @JsonView?
    When projections differ structurally (different field names/shapes/nesting), when you want compile-time clarity, or when views multiply and become hard to reason about. @JsonView shines only for a few orthogonal subsets of the same shape.

saying these in an interview costs you the question

  • Assuming untagged fields are automatically hidden under a view (they aren't by default)
  • Thinking @JsonView requires separate classes to work
  • Not knowing @JsonView also applies to @RequestBody input
  • Believing you can activate multiple views on one response

context