How to Exclude Non-fetched Lazy Fields from Jackson Serialization in 2 Steps

Problem

While working on a project recently, I ran into an issue in which Hibernate was unnecessarily executing database queries to fetch some lazy associations. The reason is, I was using the JPA entity as the REST API response entity. Therefore, when Jackson serializes the entity to return a JSON response, it initializes the lazy associations in the entity.

This behavior may even cause initialization exceptions like the one below if there is no hibernate session during serialization.

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.javachinna.model.License.activations, could not initialize proxy - no Session

Solution

To fix this issue, there are 4 solutions:

  1. Use a POJO class in the REST API response instead of using the JPA entity directly.
  2. Use @JsonIgnore to ignore the lazy fields.
  3. Define JSON views using @JsonView
  4. Exclude the non-fetched lazy fields during serialization.

Now let’s see the drawbacks of each solution and the one I picked for this use case.

Firstly, I didn’t want to create a POJO, write a custom JPA query, and map the result to the POJO to exclude just one field. Moreover, I’m using the same JPA entity for other API responses as well. Hence, I need to apply the solution for them as well which requires a lot more effort.

Secondly, I didn’t want to use @JsonIgnore annotation either to ignore the lazy fields because I’m using the same JPA entity as a response for different REST APIs. So, some of the APIs are required to include the lazy fields as well in the response and in that case, I would have eagerly fetched those associations already to avoid executing separate queries (the famous N+1 issue) for them.

Thirdly, I didn’t want to define JSON views and pollute the entity to exclude just one lazy field as shown below

	@JsonView(Views.LicenseView.class)
	private String tag1;
	@JsonView(Views.LicenseView.class)
	private String tag2;
	@JsonView(Views.LicenseView.class)
	private String tag3;

	@JsonView(Views.ActivationView.class)
	@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "license")
	@ToString.Exclude
	private List<Activation> activations = new ArrayList<>();

Therefore, finally, I wanted to implement the fourth option. In order to do that, Jackson already provides the @JsonInclude annotation with some predefined options like JsonInclude.Include.NON_EMPTY, JsonInclude.Include.NON_NULL & JsonInclude.Include.NON_ABSENT. However, none of these options will stop the lazy fetching of associations during serialization as they are Hibernate proxies. So when Jackson invokes the getter method of the lazy association, Hibernate will fetch them from the database.

This is where the JsonInclude.Include.CUSTOM option comes into the picture in which we can define our own Jackson custom filter to exclude the non-fetched associations during serialization. So, let’s go ahead and see how to define a custom Jackson filter for excluding un-initialized lazy objects.

Creating Jackson Filter

Let’s create a custom filter that overrides the equals method to return true if the object is not initialized. So that Jackson will filter out the non-fetched lazy fields and serialize only the fetched objects.

LazyFieldsFilter.java

package com.javachinna.licenseserver.config.filters;

import jakarta.persistence.Persistence;

public class LazyFieldsFilter {
    @Override
    public boolean equals(Object obj) {
        return !Persistence.getPersistenceUtil().isLoaded(obj);
    }
}

Using @JsonInclude

Now, In our JPA/Hibernate entity, we can use the @JsonInclude annotation with JsonInclude.Include.CUSTOM value and LazyFieldsFilter.class as valueFilter

	@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = LazyFieldsFilter.class)
	@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "license")
	@ToString.Exclude
	private List<Activation> activations = new ArrayList<>();

Conclusion

That’s all folks. In this article, we have implemented a Jackson filter to exclude the non-fetched lazy fields during serialization.

Thank you for reading.

Leave a Reply