Avoid Jackson serialization on non fetched lazy objects

JavaSpringHibernateSerializationJackson

Java Problem Overview


I have a simple controller that return a User object, this user have a attribute coordinates that have the hibernate property FetchType.LAZY.

When I try to get this user, I always have to load all the coordinates to get the user object, otherwise when Jackson try to serialize the User throws the exception: >com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy - no Session

This is due to Jackson is trying to fetch this unfetched object. Here are the objects:

public class User{

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
	@JsonManagedReference("user-coordinate")
	private List<Coordinate> coordinates;
}

public class Coordinate {

	@ManyToOne
	@JoinColumn(name = "user_id", nullable = false)
	@JsonBackReference("user-coordinate")
	private User user;
}

And the controller:

@RequestMapping(value = "/user/{username}", method=RequestMethod.GET)
public @ResponseBody User getUser(@PathVariable String username) {
		
	User user = userService.getUser(username);
		
	return user;
		
}

There is a way to tell Jackson to not serialize the unfetched objects? I've been looking other answers posted 3 years ago implementing jackson-hibernate-module. But probably it could be achieved with a new jackson feature.

My versions are:

  • Spring 3.2.5
  • Hibernate 4.1.7
  • Jackson 2.2

Thanks in advance.

Java Solutions


Solution 1 - Java

I finally found the solution! thanks to indybee for giving me a clue.

The tutorial Spring 3.1, Hibernate 4 and Jackson-Module-Hibernate have a good solution for Spring 3.1 and earlier versions. But since version 3.1.2 Spring have his own MappingJackson2HttpMessageConverter with almost the same functionality as the one in the tutorial, so we don't need to create this custom HTTPMessageConverter.

With javaconfig we don't need to create a HibernateAwareObjectMapper too, we just need to add the Hibernate4Module to the default MappingJackson2HttpMessageConverter that Spring already have and add it to the HttpMessageConverters of the application, so we need to:

  1. Extend our spring config class from WebMvcConfigurerAdapter and override the method configureMessageConverters.

  2. On that method add the MappingJackson2HttpMessageConverter with the Hibernate4Module registered in a previus method.

Our config class should look like this:

@Configuration
@EnableWebMvc
public class MyConfigClass extends WebMvcConfigurerAdapter{

	//More configuration....
	
	/* Here we register the Hibernate4Module into an ObjectMapper, then set this custom-configured ObjectMapper
	 * to the MessageConverter and return it to be added to the HttpMessageConverters of our application*/
	public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
		MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
		
		ObjectMapper mapper = new ObjectMapper();
		//Registering Hibernate4Module to support lazy objects
		mapper.registerModule(new Hibernate4Module());
		
		messageConverter.setObjectMapper(mapper);
		return messageConverter;
		
	}

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		//Here we add our custom-configured HttpMessageConverter
		converters.add(jacksonMessageConverter());
		super.configureMessageConverters(converters);
	}
	
	//More configuration....
}

If you have an xml configuration, you don't need to create your own MappingJackson2HttpMessageConverter either, but you do need to create the personalized mapper that appears in the tutorial (HibernateAwareObjectMapper), so your xml config should look like this:

<mvc:message-converters>
	<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
		<property name="objectMapper">
			<bean class="com.pastelstudios.json.HibernateAwareObjectMapper" />
		</property>
	</bean>
</mvc:message-converters>

Hope this answer be understandable and helps someone find the solution for this problem, any questions feel free to ask!

Solution 2 - Java

As of Spring 4.2 and using Spring Boot and javaconfig, registering the Hibernate4Module is now as simple as adding this to your configuration:

@Bean
public Module datatypeHibernateModule() {
  return new Hibernate4Module();
}

ref: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring

Solution 3 - Java

This is similar to accepted solution by @rick.

If you don't want to touch existing message converters configuration you can just declare a Jackson2ObjectMapperBuilder bean like:

@Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
    return new Jackson2ObjectMapperBuilder()
        .modulesToInstall(Hibernate4Module.class);
}

Do not forget to add the following dependency to your Gradle file (or Maven):

compile 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:2.4.4'

Useful if you have a [tag:spring-boot] application and you want to keep the ability to modify Jackson features from application.properties file.

Solution 4 - Java

In the case of Spring Data Rest then, while the solution posted by @r1ckr works, all that is required is to add one of the following dependencies depending on your Hibernate version:

<dependency>
	<groupId>com.fasterxml.jackson.datatype</groupId>
	<artifactId>jackson-datatype-hibernate4</artifactId>
	<version>${jackson.version}</version>
</dependency>

or

<dependency>
	<groupId>com.fasterxml.jackson.datatype</groupId>
	<artifactId>jackson-datatype-hibernate5</artifactId>
	<version>${jackson.version}</version>
</dependency>

Within Spring Data Rest there is a class:

org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper

which will auto-detect and register the Module on application start-up.

There is however an issue:

https://stackoverflow.com/questions/42395831/issue-serializing-lazy-manytoone

Solution 5 - Java

I've spent whole day trying to solve the same problem. You can do it without changing existing message converters configuration.

In my opinion the easiest way to solve this problem only with 2 steps with help of jackson-datatype-hibernate:

> kotlin example (same as java):

  1. Add In build.gradle.kts:
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
  1. Create @Bean
   @Bean
   fun hibernate5Module(): Module = Hibernate5Module()

  • Notice that Module is com.fasterxml.jackson.databind.Module, not java.util.Module

  • Also good practice is to add @JsonBackReference & @JsonManagedReference to @OneToMany & @ManyToOne relationships. @JsonBackReference could be only 1 in class.

Solution 6 - Java

If you use XML config and use <annotation-driven />, I found that you have to nest <message-converters> inside <annotation-driven> as recommended on the Jackson Github account.

Like so:

<annotation-driven>
  <message-converters>
    <beans:bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
      <beans:property name="objectMapper">
        <beans:bean class="com.pastelstudios.json.HibernateAwareObjectMapper" />
      </beans:property>
    </beans:bean> 
  </message-converters>
</annotation-driven>

`

Solution 7 - Java

For those who came here looking to find the solution for Apache CXF-based RESTful service, the configuration that fixes it is below:

<jaxrs:providers>
    <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider">
        <property name="mapper" ref="objectMapper"/>
    </bean>
</jaxrs:providers>

<bean id="objectMapper" class="path.to.your.HibernateAwareObjectMapper"/>

Where HibernateAwareObjectMapper is defined as:

public class HibernateAwareObjectMapper extends ObjectMapper {
    public HibernateAwareObjectMapper() {
        registerModule(new Hibernate5Module());
    }
}

The following dependency is required as of June 2016 (provided you're using Hibernate5):

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
	<version>2.7.4</version>
</dependency>  

Solution 8 - Java

The following solution is for Spring 4.3, (non-boot) & Hibernate 5.1 where we transitioned all fetchtypes to fetch=FetchType.LAZY from cases of fetch=FetchType.EAGER for performance reasons. Immediately we saw the com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy exception due to the lazy load issue.

First, we add the following maven dependency:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>  

Then the following is added to our Java MVC configuration file:

@Configuration
@EnableWebMvc 
public class MvcConfig extends WebMvcConfigurerAdapter {

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

	Hibernate5Module h5module = new Hibernate5Module();
	h5module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
	h5module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);

	for (HttpMessageConverter<?> mc : converters){
		if (mc instanceof MappingJackson2HttpMessageConverter || mc instanceof MappingJackson2XmlHttpMessageConverter) {
			((AbstractJackson2HttpMessageConverter) mc).getObjectMapper().registerModule(h5module);
		}
	}
	return;
}

Notes:

  • You need to create and configure the Hibernate5Module to get behavior similar to Jackson without this module. The default makes incompatible assumptions.

  • Our WebMvcConfigurerAdapter has a lot of other configuration in it and we wanted to avoid another configuration class, which is why we didn't use the WebMvcConfigurationSupport#addDefaultHttpMessageConverters function that has been referred to on other posts.

  • WebMvcConfigurerAdapter#configureMessageConverters disables all of Spring's internal configuration of message converters. We preferred to avoid the potential issues around this.

  • Using extendMessageConverters enabled access to all the automatically-configured Jackson classes without losing the configuration of all other message converters.

  • Using getObjectMapper#registerModule we were able to add the Hibernate5Module to the existing converters.

  • The module was added to both the JSON and XML processors

This addition solved the issue with Hibernate and lazy loading but caused a residual issue with the generated JSON format. As reported in this github issue, the hibernate-jackson lazy load module currently ignores the @JsonUnwrapped annotation, leading to potential data errors. This happens regardless of the force-loading feature setting. The problem has been there since 2016.


Note

It appears that by adding the following to classes that are lazy-loaded, the built-in ObjectMapper works without adding the hibernate5 module:

@JsonIgnoreProperties(  {"handler","hibernateLazyInitializer"} )
public class Anyclass {

Solution 9 - Java

You can use the following helper from Spring Data Rest project:

Jackson2DatatypeHelper.configureObjectMapper(objectMapper);

Solution 10 - Java

I made a very simple solution to this problem.

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Pendency> getPendencies() {
    return Hibernate.isInitialized(this.pendencies) ? Collections.unmodifiableSet(this.pendencies) : new HashSet<>();
}

In my case I was giving the error because whenever I was returning a pendencies, as a good practice I converted it to a list that cannot be modified, but how it could or might not be in lazy depending on the method I used to get instance (with or without fetch), I do a test before it was initialized by Hibernate and add the annotation that prevents serializing an empty property and that solved my problem.

Solution 11 - Java

We couldn’t work around the problem that we had to call Hibernate.initialize(objX.getAttributeY()) for all Attributes. So here we wrote a utils class to call the initialize methods on all attributes that have the Entity annotation. Collections still must be handled separately.

import org.hibernate.Hibernate;

import javax.persistence.Entity;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class HibernateInitialiseUtil<T> {

    private final List<Method> entityGetterMethods;

    public HibernateInitialiseUtil(Class<T> clazz) {
        this.entityGetterMethods = this.getGettersMethodsOfEntityAttributes(clazz);
    }

    public void initializeAllEntityAttributes(T obj) throws InvocationTargetException, IllegalAccessException {
        for (Method getter : this.entityGetterMethods) {
            Hibernate.initialize(getter.invoke(obj));
        }
    }

    private List<String> getNameOfEntityAttributes(final Class<T> targetClass) {
        Field[] attributes =  targetClass.getDeclaredFields();
        List<String> fields = new ArrayList<>();
        for (Field attribute : attributes) {
            if (attribute.getType().isAnnotationPresent(Entity.class)) {
                fields.add(attribute.getName());
            }
        }
        return fields;
    }

    private List<Method> getGettersMethodsOfEntityAttributes(final Class<T> targetClass) {
        List<Method> resultList = new ArrayList<>();
        for (String attributeName : getNameOfEntityAttributes(targetClass)) {
            try {
                PropertyDescriptor pd = new PropertyDescriptor(attributeName, targetClass);
                Method getter = pd.getReadMethod();
                resultList.add(getter);
            } catch (IllegalArgumentException | IntrospectionException e) {
                e.printStackTrace();
            }
        }
        return Collections.unmodifiableList(resultList);
    }
}

Solution 12 - Java

I tried @rick's useful answer, but ran into the problem that "well-known modules" such as jackson-datatype-jsr310 weren't automatically registered despite them being on the classpath. (This blog post explains the auto-registration.)

Expanding on @rick's answer, here's a variation using Spring's Jackson2ObjectMapperBuilder to create the ObjectMapper. This auto-registers the "well-known modules" and sets certain features in addition to installing the Hibernate4Module.

@Configuration
@EnableWebMvc
public class MyWebConfig extends WebMvcConfigurerAdapter {

	// get a configured Hibernate4Module
	// here as an example with a disabled USE_TRANSIENT_ANNOTATION feature
	private Hibernate4Module hibernate4Module() {
		return new Hibernate4Module().disable(Hibernate4Module.Feature.USE_TRANSIENT_ANNOTATION);
	}

	// create the ObjectMapper with Spring's Jackson2ObjectMapperBuilder
	// and passing the hibernate4Module to modulesToInstall()
	private MappingJackson2HttpMessageConverter jacksonMessageConverter(){
		Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
							.modulesToInstall(hibernate4Module());
    	return new MappingJackson2HttpMessageConverter(builder.build());
  }

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		converters.add(jacksonMessageConverter());
		super.configureMessageConverters(converters);
	}
}

Solution 13 - Java

Although this question is slightly different to this one : https://stackoverflow.com/questions/4362104/strange-jackson-exception-being-thrown-when-serializing-hibernate-object/, the underlying problem can be fixed in the same way with this code:

@Provider
public class MyJacksonJsonProvider extends JacksonJsonProvider {
    public MyJacksonJsonProvider() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        setMapper(mapper);
    }
}

Solution 14 - Java

I tried this, and worked:

// custom configuration for lazy loading

public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {

    @Override
    public void serialize(JavassistLazyInitializer initializer, JsonGenerator jsonGenerator,
            SerializerProvider serializerProvider)
            throws IOException, JsonProcessingException {
        jsonGenerator.writeNull();
    }
}

and configure mapper:

    mapper = new JacksonMapper();
    SimpleModule simpleModule = new SimpleModule(
            "SimpleModule", new Version(1,0,0,null)
    );
    simpleModule.addSerializer(
            JavassistLazyInitializer.class,
            new HibernateLazyInitializerSerializer()
    );
    mapper.registerModule(simpleModule);

Solution 15 - Java

Another solution for spring boot: configuration: spring.jpa.open-in-view=true

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionr1ckrView Question on Stackoverflow
Solution 1 - Javar1ckrView Answer on Stackoverflow
Solution 2 - JavachrismarxView Answer on Stackoverflow
Solution 3 - JavaLuis BellochView Answer on Stackoverflow
Solution 4 - JavaAlan HayView Answer on Stackoverflow
Solution 5 - JavaDmitry KaltovichView Answer on Stackoverflow
Solution 6 - JavablackwoodView Answer on Stackoverflow
Solution 7 - Javauser2968675View Answer on Stackoverflow
Solution 8 - JavasdwView Answer on Stackoverflow
Solution 9 - JavaPrzemek NowakView Answer on Stackoverflow
Solution 10 - JavaPedro BacchiniView Answer on Stackoverflow
Solution 11 - JavaJonas_HessView Answer on Stackoverflow
Solution 12 - JavaMarkus PscheidtView Answer on Stackoverflow
Solution 13 - JavanevsterView Answer on Stackoverflow
Solution 14 - JavaahllView Answer on Stackoverflow
Solution 15 - JavabianbianView Answer on Stackoverflow