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:
- Use a POJO class in the REST API response instead of using the JPA entity directly.
- Use
@JsonIgnore
to ignore the lazy fields. - Define JSON views using
@JsonView
- 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.
This is really awesome. Even stack overflow didn’t solved my exact issue. You saved me and way of explanation with impact is really important which you have done. Great work!