Could not serialize object cause of HibernateProxy
JavaHibernateJakarta EeJax RsGsonJava Problem Overview
I am getting the following error response from the server.
> HTTP Status 500 - > > type Exception report > > message > > descriptionThe server encountered an internal error () that prevented > it from fulfilling this request. > > exception > > javax.servlet.ServletException: > java.lang.UnsupportedOperationException: Attempted to serialize > java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to > register a type adapter? > > root cause > > java.lang.UnsupportedOperationException: Attempted to serialize > java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to > register a type adapter?
From Java debugger:
org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer@7632012e
I am using Gson to convert my Java objects to JSON. Below I have pasted some of my code.
This is my resource:
@Stateless
@LocalBean
@Path("/autos")
@Produces(MediaType.APPLICATION_JSON)
public class AutoResource {
@EJB
private CarAssembler warehouse;
@Context
private UriInfo uriInfo;
@GET
public Response allAutos() {
// Building a context, lots of code...
// Creating a Gson instance and configures it...
final Auto auto = warehouse.list(context);
final String autoJson = gson.toJson(auto);
return Response.ok(autoJson).build();
}
}
The CarAssembler is just an service which invokes a repository. I have not pasted the code of the service here.
Repository:
@Override
public Question findById(final int id, final FetchType fetchType) {
final Auto question = getEntityManager().find(Auto.class, id);
if (fetchType == FetchType.LAZY) {
return auto;
}
Hibernate.initialize(auto.getManufacturer());
Hibernate.initialize(auto.getAssemblyHouse());
return auto;
}
As you see, I provide both lazy and eager loading of objects. I use Hibernate.initialize to eager fetch JPA associations. However, the question is how I can fix the proxy error I am getting. Why come there is only AssemblyHouse that is still attached to JavaAssist, while Manufacturer is not (I have seen the type in Java Debugger). How do I know when to unproxy objects? Should I unproxy all associations this auto may have? And in which layer of my code? Does it affect the performance of my application when I unproxy? Are there other solutions? I see from the error message that I can make a type adapter. Yes, I could but then I have to do that for all domain objects to be sure that conversion is done correctly. Maybe other objects in my domain starts failing when I try to convert it into JSON representation too, but I don't know when or why. Is it just luck that the other objects are fine?
This is the way I am unproxing objects, but I have not implemented it yet, because I don't know if this is good or bad, and in what layer to do this, and when I should do it. Should I unproxy objects all the time?
public class HibernateUtilities {
public static <T> T unproxy(T proxy) {
if (proxy == null) {
return null;
}
if (proxy instanceof HibernateProxy) {
Hibernate.initialize(proxy);
HibernateProxy hibernateProxy = (HibernateProxy) proxy;
T unproxiedObject = (T) hibernateProxy.getHibernateLazyInitializer().getImplementation();
return unproxiedObject;
}
return proxy;
}
}
Stacktrace as requested:
[#|2012-11-22T17:17:13.285+0100|WARNING|glassfish3.1.2|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=71;_ThreadName=Thread-8;|StandardWrapperValve[javax.ws.rs.core.Application]: PWC1406: Servlet.service() for servlet javax.ws.rs.core.Application threw exception java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to register a type adapter? at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:64) at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:61) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ArrayTypeAdapter.write(ArrayTypeAdapter.java:93) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:60) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195) at com.google.gson.Gson.toJson(Gson.java:586) at com.google.gson.Gson.toJson(Gson.java:565) at com.google.gson.Gson.toJson(Gson.java:520) at com.myapp.AutoResource.produceAuto(AutoResource.java:48) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.glassfish.ejb.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1052) at org.glassfish.ejb.security.application.EJBSecurityManager.invoke(EJBSecurityManager.java:1124) at com.sun.ejb.containers.BaseContainer.invokeBeanMethod(BaseContainer.java:5388) at com.sun.ejb.EjbInvocation.invokeBeanMethod(EjbInvocation.java:619) at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:800) at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:571) at com.sun.ejb.containers.interceptors.SystemInterceptorProxy.doAround(SystemInterceptorProxy.java:162) at com.sun.ejb.containers.interceptors.SystemInterceptorProxy.aroundInvoke(SystemInterceptorProxy.java:144) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:861) at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:800) at com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:370) at com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:5360) at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:5348) at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:214) at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:89) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at com.sun.jersey.spi.container.JavaMethodInvokerFactory$1.invoke(JavaMethodInvokerFactory.java:60) at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$ResponseOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:205) at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:75) at com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:288) at com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:108) at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147) at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:84) at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1469) at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1400) at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1349) at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1339) at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:416) at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537) at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:708) at javax.servlet.http.HttpServlet.service(HttpServlet.java:770) at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:281) at org.apache.catalina.core.StandardContextValve.__invoke(StandardContextValve.java:175) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java) at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655) at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595) at org.apache.catalina.core.StandardHostValve.__invoke(StandardHostValve.java:161) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java) at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231) at com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317) at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195) at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:860) at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:757) at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056) at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229) at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90) at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79) at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54) at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59) at com.sun.grizzly.ContextTask.run(ContextTask.java:71) at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532) at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513) at java.lang.Thread.run(Thread.java:722) |#]
Java Solutions
Solution 1 - Java
You can do without manually unproxying everything by using a custom TypeAdapter
.
Something along these lines:
/**
* This TypeAdapter unproxies Hibernate proxied objects, and serializes them
* through the registered (or default) TypeAdapter of the base class.
*/
public class HibernateProxyTypeAdapter extends TypeAdapter<HibernateProxy> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (HibernateProxy.class.isAssignableFrom(type.getRawType()) ? (TypeAdapter<T>) new HibernateProxyTypeAdapter(gson) : null);
}
};
private final Gson context;
private HibernateProxyTypeAdapter(Gson context) {
this.context = context;
}
@Override
public HibernateProxy read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Not supported");
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void write(JsonWriter out, HibernateProxy value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
// Retrieve the original (not proxy) class
Class<?> baseType = Hibernate.getClass(value);
// Get the TypeAdapter of the original class, to delegate the serialization
TypeAdapter delegate = context.getAdapter(TypeToken.get(baseType));
// Get a filled instance of the original class
Object unproxiedValue = ((HibernateProxy) value).getHibernateLazyInitializer()
.getImplementation();
// Serialize the value
delegate.write(out, unproxiedValue);
}
}
To use it you must first register it:
GsonBuilder b = new GsonBuilder();
...
b.registerTypeAdapterFactory(HibernateProxyTypeAdapter.FACTORY);
...
Gson gson = b.create();
Notice that this will recursively initialize every proxy you have in the object hierarchy; since however you have to serialize the whole data, you should have done that anyway.
How does this work?
GSON contains a number of TypeAdapterFactory
implementations, for various types (primitive types, common types like String
or Date
, lists, arrays...). Each factory is asked if it is able to serialize a certain Java type (the parameter to create
is a TypeToken
instead of a Class
in order to capture possible information about generic types, which Class
does not have). If the factory is able to serialize/deserialize a type, it responds with a TypeAdapter
instance; otherwise it responds with null
.
HibernateProxyTypeAdapter.FACTORY
verifies whether type implements HibernateProxy
; in that case, it returns an instance of HibernateProxyTypeAdapter
for serialization.
The write
method is called when an actual object has to be serialized; the adapter extracts the original type of the underlying object, and asks GSON for the standard TypeAdapter
for the original type, which generally is a ReflectiveTypeAdapter
.
Then it retrieves an instance of the original class, instead of directly using the proxy. This is necessary because ReflectiveTypeAdapter
accesses directly to fields, instead of using getters; accessing to the fields of a proxied object does not work, and is a classical Hibernate pitfall.
As a possible performance improvement, the delegate TypeAdapter
should be acquired in the create
method. I found out that calling getSuperclass()
on the proxy Class
appears to yield the original base class. The code can then become:
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (HibernateProxy.class.isAssignableFrom(type.getRawType())
? (TypeAdapter<T>) new HibernateProxyTypeAdapter((TypeAdapter)gson.getAdapter(TypeToken.get(type.getRawType().getSuperclass())))
: null);
}
};
private final TypeAdapter<Object> delegate;
private HibernateProxyTypeAdapter(TypeAdapter<Object> delegate) {
this.delegate = delegate;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void write(JsonWriter out, HibernateProxy value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
delegate.write(out, ((HibernateProxy) value).getHibernateLazyInitializer()
.getImplementation());
}
Solution 2 - Java
Seeing as though you mentioned that the error persists with eager loading, then the issue likely is not so much Hibernate, but possibly on the GSON implementation. I think you will need a Type when creating your JSON, not sure if it was registered, but perhaps something like this:
public String autosToJson(Auto autos) {
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.registerTypeAdapter(Auto.class, new AutoAdapter()).create();
return gson.toJson(autos);
}
Then just create an AdapterClass, for example:
public class AutoAdapter implements JsonSerializer<Auto> {
@Override
public JsonElement serialize(Auto auto, Type type, JsonSerializationContext jsc) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("auto_id", auto.getId());
jsonObject.addProperty("auto_name", auto.getAutoName());
jsonObject.addProperty("auto__manufacture_date", auto.getManufactureDate().toString());
return jsonObject;
}
}
Solution 3 - Java
In usual cases you don't want your domain objects to be exposed as XML/JSON via services, often you need to create a DTO because your Entity does not fit the needs of your consumer. And even if it does now, after internal refactoring of the database, it won't fit tomorrow. So my advice would be to create DTOs right now if you got into such a trouble. BTW, you can create those DTOs even on the Hibernate level by using Result Transformers or creating Views and mapping Hibernate entities onto these Views.
Another trick would be to use Dozer in order to copy needed fields into the other class (which actually is the same class, but without proxy).
And a note: you're using Gson, which access your fields, instead of accessors, this makes it impossible to work with Hibernate proxy because it will try to access the fields of proxy itself which always are null
.
Solution 4 - Java
Yes, you can just unproxy all the time, if it has a HibernateProxy (which won't serialize) it will be gone and replaced with the actual underlying implementation, or it will leave the class as is and give you an implementation. I think your solution should work fine. Mind you I don't really use Hibernate much, but it does make sense to me.
On the other hand you might be trusting Hibernate some more, but a simpler method might be:
Hibernate.getClass(obj);
This solution should not give you implemented/initialized classes, just the class, or that function should be offered by:
HibernateProxyHelper.getClassWithoutInitializingProxy(superClass)
I believe the latter might return the super class though, so you might start with Hibernate.getClass(obj);
also:
public static <T> T initializeAndUnproxy(T entity) {
if (entity == null) {
throw new
NullPointerException("Entity passed for initialization is null");
}
Hibernate.initialize(entity);
if (entity instanceof HibernateProxy) {
entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer()
.getImplementation();
}
return entity;
}
The above code was borrowed from: https://stackoverflow.com/questions/2216547/converting-hibernate-proxy-to-real-object the variable names from it are probably better, as they don't imply the entity is always a proxy. Also it will throw an exception to warn you, but that is up to you whether you want the exception or not.
Of course you can also get rid of lazy load, but I don't think thats the best solution.
Solution 5 - Java
Try parsing through ObjectMapper as
final Auto auto = warehouse.list(context);
final String autoJson = new ObjectMapper().writeValueAsString(auto);
return Response.ok(autoJson).build();
Solution 6 - Java
I was experiencing this very problem when I bumped into this post, which pointed me in the right direction for my situation. I realized I didn't need to serialize the entire entity, specifically for the very reason that I has marked some fields to be lazy loaded. So I sought to find a way to skip these fields and ExclusionStrategy was magical. This seems to have fixed my issue
public class ExcludeProxiedFields implements ExclusionStrategy{
@Override
public boolean shouldSkipField(FieldAttributes fa) {
return fa.getAnnotation(ManyToOne.class) != null ||
fa.getAnnotation(OneToOne.class) != null ||
fa.getAnnotation(ManyToMany.class) != null ||
fa.getAnnotation(OneToMany.class) != null ;
}
@Override
public boolean shouldSkipClass(Class<?> type) {
return false;
}
}
Then I applied this class to the GsonBuilder like this:
Gson gson = new GsonBuilder().setExclusionStrategies(new ExcludeProxiedFields()).create();