Home | Send Feedback | Share on Bluesky |

Using Jackson @JsonView for Multiple JSON Representations

Published: 10. September 2025  •  java, jackson, json

When working with JSON in Java, Jackson is a popular choice for serializing and deserializing objects. A common requirement is to produce different JSON representations of the same object, such as a public view with a subset of fields and an internal view with all fields. While you could use multiple Data Transfer Objects (DTOs), this approach often leads to boilerplate code due to field duplication and the need for conversion logic.

Jackson's @JsonView annotation offers a cleaner solution by allowing you to define multiple views for a single Java class. This feature lets you control which properties are included in the JSON output based on the active view.

This example demonstrates how to use @JsonView for serialization and deserialization with different views.

Setting up the Project

We'll start with a small Maven project. The only dependency we need is jackson-databind.

    <dependency>
        <groupId>tools.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>3.0.2</version>
    </dependency>

pom.xml

Defining Views

First, we need to define the views. Views are typically empty static inner classes within a container class. In this example, we'll create two views: Public and Internal. Internal extends Public, so it includes all fields from the Public view as well as its own.

public class View {

  public static class Public {}

  public static class Internal extends Public {}
}

View.java

Annotating the Model

Next, we annotate the properties of the model class with @JsonView. A field can belong to one or more views. You can annotate either instance variables or getter methods. @JsonView works with both Java classes and records.

public record User(
    @JsonView(View.Public.class) String name,
    @JsonView(View.Public.class) String email,
    @JsonView({View.Public.class}) String username,
    @JsonView(View.Internal.class) String ssn,
    @JsonView(View.Internal.class) String internalId) {}

User.java

Serialization with @JsonView

Now, let's serialize the object using different views. We can use ObjectMapper.writerWithView() to select the view for serialization.

    ObjectMapper mapper = new ObjectMapper();

    User user = new User("John Doe", "john@example.com", "johndoe", "123-45-6789", "INT001");

    String publicJson = mapper.writerWithView(View.Public.class).writeValueAsString(user);
    System.out.println(publicJson);
    // Output: {"name":"John Doe","email":"john@example.com","username":"johndoe"}

    String internalJson = mapper.writerWithView(View.Internal.class).writeValueAsString(user);
    System.out.println(internalJson);
    // Output: {"name":"John Doe","email":"john@example.com","username":"johndoe","ssn":"123-45-6789","internalId":"INT001"}

    String fullJson = mapper.writeValueAsString(user);
    System.out.println(fullJson);
    // Output: {"name":"John Doe","email":"john@example.com","username":"johndoe","ssn":"123-45-6789","internalId":"INT001"}

    String fullJsonForDeser =
        "{\"name\":\"Jane Smith\",\"email\":\"jane@example.com\",\"username\":\"janesmith\",\"ssn\":\"987-65-4321\",\"internalId\":\"INT002\"}";

App.java

When the application serializes the User object with the Public view, only the Public view properties appear in the JSON output. When using the Internal view, all properties from both Public and Internal views are included because Internal extends Public. If no view is specified, all properties are serialized.

DEFAULT_VIEW_INCLUSION

When a view is active, Jackson 3, by default, ingores all properties that are not annotated with @JsonView. Note that this is different from Jackson 2, where unannotated properties were always included in all views.

Let's demonstrate this with a simple example. In the Article record below, the title field is annotated with @JsonView, while notes is not.

public record Article(@JsonView(View.Public.class) String title, String notes) {}

Article.java

When an Article instance is serialized with the Public view, the output will include only title because notes is not annotated with any view.

    ObjectMapper defaultNoInclusion = new ObjectMapper();
    String withViewDefault =
      defaultNoInclusion.writerWithView(View.Public.class).writeValueAsString(article);
    System.out.println(withViewDefault);
    // Output: {"title":"Hello Views"}

App.java

This behavior is controlled by the MapperFeature.DEFAULT_VIEW_INCLUSION feature, which is disabled by default. If you enable it, Jackson includes all unannotated properties in every view.

    ObjectMapper withInclusion =
        JsonMapper.builder().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true).build();
    String withViewDisabled =
      withInclusion.writerWithView(View.Public.class).writeValueAsString(article);
    System.out.println(withViewDisabled);
    // Output: {"title":"Hello Views","notes":"internal notes"}

App.java

Note that DEFAULT_VIEW_INCLUSION only affects serialization when a view is active. If you serialize without a view, it has no effect, and all properties will be included in the output.

    String noView = mapper.writeValueAsString(article);
    System.out.println(noView);
    // Output: {"title":"Hello Views","notes":"internal notes"}

App.java

Deserialization with @JsonView

@JsonView is also applicable for deserialization. When you deserialize with a view, only the properties included in that view are populated in the resulting object. All other properties are ignored and set to their default values.

    String fullJsonForDeser =
        "{\"name\":\"Jane Smith\",\"email\":\"jane@example.com\",\"username\":\"janesmith\",\"ssn\":\"987-65-4321\",\"internalId\":\"INT002\"}";

    User publicUser =
        mapper.readerWithView(View.Public.class).forType(User.class).readValue(fullJsonForDeser);
    System.out.println(publicUser);
    // Output: User[name=Jane Smith, email=jane@example.com, username=janesmith, ssn=null, internalId=null]

    User internalUser =
        mapper.readerWithView(View.Internal.class).forType(User.class).readValue(fullJsonForDeser);
    System.out.println(internalUser);
    // Output: User[name=Jane Smith, email=jane@example.com, username=janesmith, ssn=987-65-4321, internalId=INT002]

    User fullUser = mapper.readValue(fullJsonForDeser, User.class);
    System.out.println(fullUser);
    // Output: User[name=Jane Smith, email=jane@example.com, username=janesmith, ssn=987-65-4321, internalId=INT002]

App.java

In the first example, the application deserializes the JSON string using the Public view. As a result, only the name, email, and username properties are populated in the User object. The ssn and internalId properties, which are not part of the Public view, are set to null.

Deserializing with the Internal view populates all properties since Internal includes both Public properties and its own.

When no view is specified for deserialization, all matching properties from the JSON populate the object.


DEFAULT_VIEW_INCLUSION also affects deserialization. By default, properties without a @JsonView annotation are not deserialized when a view is active. If you enable DEFAULT_VIEW_INCLUSION, unannotated properties will be included during deserialization when a view is active.

    String articleJson = "{\"title\":\"Sample Article\",\"notes\":\"secret notes\"}";

    Article publicArticleDefault =
        mapper.readerWithView(View.Public.class).forType(Article.class).readValue(articleJson);
    System.out.println(publicArticleDefault);
    // Output: Article[title=Sample Article, notes=null]

    withInclusion =
        JsonMapper.builder().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true).build();
    Article publicArticleNoDefault =
      withInclusion
            .readerWithView(View.Public.class)
            .forType(Article.class)
            .readValue(articleJson);
    System.out.println(publicArticleNoDefault);
    // Output: Article[title=Sample Article, notes=secret notes]

    Article fullArticle = mapper.readValue(articleJson, Article.class);
    System.out.println(fullArticle);
    // Output: Article[title=Sample Article, notes=secret notes]

App.java

When not not using a view, the flag DEFAULT_VIEW_INCLUSION has no effect, and all properties are populated.

Conclusion

Jackson's @JsonView is a powerful tool for controlling serialization and deserialization, allowing you to define multiple views on the same model. This is particularly useful for exposing different representations of an object. Instead of creating multiple DTOs, you can use @JsonView to annotate the properties in a single Java class or record, which helps reduce boilerplate and keeps your codebase cleaner.

However, it is important to be careful with this feature and always use a reader (readerWithView()) or writer (writerWithView()) with a view. If you forget to do so, all properties will be serialized or deserialized, which could lead to data leaks.