After upgrading from 2.3.5.RELEASE to 2.4.0 instantiating a FullTextEntityManager (hibernate search) from an injected EntityManager is not possible anymore. The same code worked in the past and is derived from the official documentation or baeldung.

Code to reproduce: https://github.com/knoobie/spring-2-4-entitymanager

How to check: - run the test; test fails on service#load - you can verify that it worked, if you just change the version to 2.3.5.RELEASE again and start the test (it doesn't fail)

Stacktrace:

java.lang.ClassCastException: com.sun.proxy.$Proxy98 cannot be cast to org.hibernate.engine.spi.SessionImplementor

    at org.hibernate.search.impl.FullTextSessionImpl.<init>(FullTextSessionImpl.java:62)
    at org.hibernate.search.impl.ImplementationFactory.createFullTextSession(ImplementationFactory.java:35)
    at org.hibernate.search.Search.getFullTextSession(Search.java:45)
    at org.hibernate.search.jpa.Search.getFullTextEntityManager(Search.java:49)
    at com.example.demo.search.MyService.load(MyService.java:51)
    at com.example.demo.search.MyService$$FastClassBySpringCGLIB$$195495fd.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:371)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:134)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
    at com.example.demo.search.MyService$$EnhancerBySpringCGLIB$$9e31f121.load(<generated>)
    at com.example.demo.DemoApplicationTests.contextLoads(DemoApplicationTests.java:20)

Comment From: wilkinsona

Thanks for the sample. I believe this change in behaviour is due to this change in Spring Framework 5.3.

The entityManagerInterface has changed from org.hibernate.jpa.HibernateEntityManager to org.hibernate.Session. This has a knock-on effect when Hibernate Search attempts to unwrap the EntityManager as a Session:

private static Session getSession(EntityManager em) {
    try {
        return em.unwrap( Session.class );
    }
    catch (PersistenceException e) {
        throw new SearchException(
                "Trying to use Hibernate Search with a non-Hibernate EntityManager", e
        );
    }
}

The EntityManager (a Spring Framework created proxy) now implements Session so the proxy is returned as-is. The proxy does not implement SessionImplementor so the subsequent cast fails. In Spring Framework 5.2 the proxy wasn't an instance of Session so a different code path was taken.

We'll transfer this to the Spring Framework team so that they can take a look.

Comment From: knoobie

@wilkinsona thanks for you detailed analysis!

Comment From: jhoeller

This is arguably a mismatch in Hibernate Search where it calls EntityManager.unwrap(Session.class) but subsequently casts the result to SessionImplementor. It should really rather call EntityManager.unwrap(SessionImplementor.class) upfront if that's what it ultimately expects.

That said, I do see the point that previous unwrap(Session.class) calls did result in the native Session and could therefore be cast to the implementor interface at any point. Maybe we should simply use SessionImplementor as our EntityManager interface in HibernateJpaVendorAdapter...

Comment From: jhoeller

We're exposing SessionFactoryImplementor and SessionImplementor by default now, for all JPA proxies backed by HibernateJpaVendorAdapter. This will be available in the upcoming 5.3.2 snapshot, could you please give it a try?

Comment From: knoobie

@jhoeller I've updated my example to use the latest 5.3.2-SNAPSHOT from master. Now the exception changes to the following:


java.lang.ClassCastException: com.sun.proxy.$Proxy97 cannot be cast to org.hibernate.event.spi.EventSource

    at org.hibernate.search.impl.FullTextSessionImpl.<init>(FullTextSessionImpl.java:66)
    at org.hibernate.search.impl.ImplementationFactory.createFullTextSession(ImplementationFactory.java:35)
    at org.hibernate.search.Search.getFullTextSession(Search.java:45)
    at org.hibernate.search.jpa.Search.getFullTextEntityManager(Search.java:49)
    at com.example.demo.search.MyService.load(MyService.java:51)
    at com.example.demo.search.MyService$$FastClassBySpringCGLIB$$195495fd.invoke(<generated>)

Coming from the other cast inside hibernate search.

Spring Spring EntityManager proxy is incompatible with Hibernate Search <5.11.6 (FullTextEntityManager instantiation fails with ClassCastException)

Edit: Changing the HibernateJpaVendorAdapter to use org.hibernate.event.spi.EventSource instead of SessionImplementor worked for me. But I'm no hibernate expert to say if that's a good solution.

Comment From: jhoeller

Argh, another unguarded cast there! EventSource is even worse since it's too internal for HibernateJpaVendorAdapter to use it as a default EntityManager interface. I'll rather change our strategy then, reverting to plain Session as default EntityManager interface but handling unwrap calls differently, leaning towards returning the underlying native instance more eagerly.

Thanks for the quick turnaround, in any case! I'll try the other approach right away, will ping you once it's ready in a shapshot.

Comment From: jhoeller

This is a quite subtle issue. Our differentiated handling of unwrap calls has use cases where the proxy needs to be returned, and Session really is Hibernate's EntityManager extension now (they deprecated their old HibernateEntityManager interface), so it seems sensible to expose it at the proxy level as far as possible.

Frankly, Hibernate Search should call unwrap(SessionImplementor.class) there if it knows that it applies further downcasts later on. As of Hibernate 5.2+ where Session is an EntityManager extension, their old unwrap(Session.class) is arguably not semantically sufficient anymore. This is worth reporting to them.

I'll keep trying further what we can do about it from our end. Ideally we'd also remain compatible with older Hibernate Search versions, even if this gets refined in the Search implementation.

Comment From: yrodiere

I'll try to replace the casts with calls to unwrap in Search 5, and I think we already did in Search 6.

One important detail: can you confirm that calling .unwrap(Session.class) used to unproxy the entity manager, and that calling .unwrap(SessionImplementor.class) will do the same, both in Spring 2.3 and 2.4? I'd rather not change the behavior for existing users...

Comment From: jhoeller

Hey @yrodiere, thanks for chiming in! Indeed, unwrap(SessionImplementor.class) is going to do exactly the same against older versions of Spring since we're using an instanceof check for our proxy there, and the proxy neither implemented Session nor SessionImplementor back then, so we'd always delegate to the native EntityManager.unwrap method underneath.

Comment From: yrodiere

@jhoeller Thanks for confirming this.

For the record, here are the relevant PRs in Hibernate Search:

  • Search 5.11: https://github.com/hibernate/hibernate-search/pull/2443
  • Search 6.0: https://github.com/hibernate/hibernate-search/pull/2444 . There's a bit more work here as I tried to make sure that a SearchSession targeting a Session proxy can safely be used from multiple threads, which isn't the case with FullTextSession in Hibernate Search 5.

I'll try to release 5.11 early next week.

Comment From: jhoeller

Great news, @yrodiere - I'll leave our unwrap handling as-is then, updating our 5.3 migration notes accordingly.

Comment From: baron1405

FYI I had experienced this issue and just updated to Hibernate Search 5.11.6. Happy to report that Spring and Hibernate Search are once again happy with each other. Thanks to all who worked on identifying and fixing the issue!

Comment From: knoobie

I can confirm it as well - my application is running with Hibernate Search 5.11.6 and Spring Boot 2.4.0 without any issues. Thanks for your help!

Comment From: Toan1606

@knoobie I copy your code in github link and still has exception

java.lang.ClassCastException: class jdk.proxy4.$Proxy100 cannot be cast to class org.hibernate.engine.spi.SessionImplementor (jdk.proxy4.$Proxy100 is in module jdk.proxy4 of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @3ed2eee9; org.hibernate.engine.spi.SessionImplementor is in unnamed module of loader 'app') at org.hibernate.search.impl.FullTextSessionImpl.(FullTextSessionImpl.java:61) ~[hibernate-search-orm-5.8.2.Final.jar:5.8.2.Final] at org.hibernate.search.impl.ImplementationFactory.createFullTextSession(ImplementationFactory.java:34) ~[hibernate-search-orm-5.8.2.Final.jar:5.8.2.Final] at org.hibernate.search.Search.getFullTextSession(Search.java:44) ~[hibernate-search-orm-5.8.2.Final.jar:5.8.2.Final] at org.hibernate.search.jpa.Search.getFullTextEntityManager(Search.java:48) ~[hibernate-search-orm-5.8.2.Final.jar:5.8.2.Final] at com.example.demo.controller.PostController.fullTextSearch(PostController.java:36) ~[classes/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.21.jar:5.3.21] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.21.jar:5.3.21] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.21.jar:5.3.21] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.21.jar:5.3.21] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.21.jar:5.3.21] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.21.jar:5.3.21] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[spring-webmvc-5.3.21.jar:5.3.21] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.21.jar:5.3.21] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.21.jar:5.3.21] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.21.jar:5.3.21] at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[tomcat-embed-core-9.0.64.jar:4.0.FR] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.21.jar:5.3.21] at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.64.jar:4.0.FR] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.64.jar:9.0.64] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.21.jar:5.3.21] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.21.jar:5.3.21] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.21.jar:5.3.21] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.21.jar:5.3.21] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.21.jar:5.3.21] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.21.jar:5.3.21] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1787) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.64.jar:9.0.64] at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

Can you let me know how to fix this code ? Exception

Comment From: knoobie

@Toan1606 if you update your dependencies, the problem is gone. Never had any problems since this was fixed. All Spring Framework / Boot and Hibernate Search versions afterwards work perfectly fine.

Comment From: Toan1606

SpringBootVersion it must be done in springboot version 2.4 right?

Comment From: Toan1606

I am testing it in spring boot version 2.7.1

Comment From: knoobie

Please check the Hibernate Search version. You are using an outdated and unsupported version. The fix was introduced in Hibernate Search 5.11.6. If you are fresh starting a project, I would recommend to start with Hibernate Search 6 as well.

Comment From: Toan1606

Exception When I change HIbernate Search to 5.11.6

Comment From: Toan1606

My project is fixed in Hibernate Search 5

Comment From: Toan1606

Is the code in your github link sure it was running before?