While working on a side project using a JAX-RS API and JPA I wanted to explore how the entity graph
features introduced in JPA 2.1 could be used to simplify data fetches for different endpoints. One
issue I quickly ran into was how to deal with LazyInitializationExceptions with unloaded lazy
fields.
The Hibernate module supplied by Jackson didn’t meet my needs and I couldn’t find any examples of
what I needed so it was time to delve into how Jackson filtering works. The end solution turned out
to be fairly straightforward and doesn’t rely on any vendor specifics so it’s worth sharing.
The original project was written in Scala but I have converted the code to Java for this example.
The project and example have both been written for Wildfly 20 and Jackson 2.11 but should work on
any Java/Jakarta EE 8+ server.
Initial model and resource
We are going to start with a very simple Article entity:
Calling this endpoint returns a single article with an embedded list of tags:
1
GET /articles/1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
{ "article":{ "id":1, "title":"Coding with JAX-RS and JPA for fun and profit", "tags":[ { "id":1, "tag":"jax-rs" }, { "id":2, "tag":"jpa" } ] } }
Other boilerplate such as configuring JAX-RS, JPA, and Jackson are omitted - we are using the
defaults for these.
Making tags lazy
Now we want to implement a requirement to only fetch tags if the API request explicitly requests
them with an include_tags query parameter[1]. The entity graph support in JPA 2.1 is an
ideal fit for this feature as allows switching the fetch behaviour without needing to change the
query.
We start by adding a NamedEntityGraph to the Article entity and changing the fetch mode of
tags back to the default of LAZY:
Calling the endpoint with the new query parameter set (include_tags=true) works as expected and
returns the same JSON as before. But if we remove the query parameter we get a 500 response and an
ugly stack trace from Hibernate as the tags have not been fetched:
1 2 3 4 5 6 7 8 9 10 11
UT005023: Exception handling request to /example/api/articles/1: org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.example.lazy.model.Article.tags, could not initialize proxy - no Session (through reference chain: com.example.lazy.api.ArticleResource$ExampleWrapper["article"]->com.example.lazy.model.Article["tags"]) at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:356) at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:193) ... Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.lazy.model.Article.tags, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602) ...
Jackson provides a solution to this with their Hibernate modules [2] which will
detect Hibernate proxy objects that are not initialised and handle them for us. If we enable the
Hibernate5Module and retry the request we get this response:
1
GET /articles/1?include_tags=false
1 2 3 4 5 6 7
{ "article":{ "id":1, "title":"Coding with JAX-RS and JPA for fun and profit", "tags":null } }
No stack traces and a valid JSON response - great! But the response has "tags": null which implies
that there are no tags defined. We know that there are tags by making a request with
include_tags=true so having an explicit null value here is misleading - a better solution would
be to omit the tags field entirely.
Filtering lazy fields
Jackson includes a PropertyFilter interface that can be used to dynamically
exclude properties at runtime. We can set up a filter to detect lazy fields and omit the property if
they haven’t been loaded:
Now when we make a lazy request we get a response with the tags field omitted:
1
GET /articles/1?include_tags=false
1 2 3 4 5 6
{ "article":{ "id":1, "title":"Coding with JAX-RS and JPA for fun and profit" } }
Conclusions
The Jackson Hibernate modules are a great fit for many situations but they don’t handle the scenario
where you want to completely omit lazy fields. Thankfully it is easy to implement a PropertyFilter
that uses PersistenceUtil to exclude unloaded lazy fields.
JPA 2.1 entity graphs are fantastic for handling lazy-loading across several JAX-RS endpoints. A set
of entity graphs can be easily combined and used by multiple queries, dramatically simplifying fetch
code. The only gripe so far is the way that entity graphs are selected using query hints - I feel
that extending the API for Query (and TypedQuery) would have been a cleaner solution.
Full example code for this post can be found on
GitHub.
For a real-world application this would more likely be including tags on a show endpoint and
excluding them on the list endpoint. ↩︎
Hibernate3Module, Hibernate4Module and Hibernate5Module depending your Hibernate version. ↩︎
This filter could be also added to a base entity class or applied globally using a Jackson
mixin class. ↩︎