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>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.20.0</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"}

App.java

As you can see, when the application serializes the User object with the Public view, only the fields annotated with @JsonView(View.Public.class) are included in the JSON output. When using the Internal view, all fields 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, by default, includes properties that are not annotated with @JsonView. This can be problematic if you forget to annotate a new property. For instance, if a new address field were added to the User record without a @JsonView annotation, it would be included in the JSON output for all views, which may not be the desired behavior.

Let's demonstrate this with a simple example. In the Article record below, only 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 both title and notes by default.

    Article article = new Article("Hello Views", "internal notes");

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

App.java

This behavior is controlled by the MapperFeature.DEFAULT_VIEW_INCLUSION feature, which is enabled by default. If you disable it, only properties explicitly annotated for the active view will be included.

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

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 = noDefaultInclusion.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).readValue(fullJsonForDeser, User.class);
    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).readValue(fullJsonForDeser, User.class);
    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 fields are populated in the User object. The ssn and internalId fields, which are not part of the Public view, are set to null.

On the other hand, deserializing with the Internal view populates all fields, since Internal includes all properties from Public as well as its own.

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


DEFAULT_VIEW_INCLUSION also affects deserialization. By default, properties without a @JsonView annotation are included in all views, meaning that if you deserialize with a view, these unannotated properties will still be populated. If you disable DEFAULT_VIEW_INCLUSION, unannotated properties will be ignored during deserialization when a view is active.

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

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

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

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

App.java

As seen in the previous example, when 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 fields 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. Additionally, consider disabling DEFAULT_VIEW_INCLUSION to avoid accidentally including unannotated properties in your views.