Quarkus: Injecting from another module causes ClassCastException

Created on 30 Oct 2019  路  29Comments  路  Source: quarkusio/quarkus

Describe the bug
The class to be injected is located in Maven dependency. During the runtime, injection fails with java.lang.ClassCastException. Both main project and dependency have beans.xml. If the injected class is moved to the main project, injection works.

Verified in dev mode.

Expected behavior
Class should be injected without error.

Actual behavior

When the @Inject variable is used for the first time, Quarkus throws exception:

2019-10-30 08:26:52,612 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, narayana-jta, resteasy, resteasy-jsonb, smallrye-openapi, swagger-ui]
2019-10-30 08:27:18,622 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (vert.x-worker-thread-1) HTTP Request to /person failed, error id: 96789028-ac59-4737-82a2-032c65c14f36-1: org.jboss.resteasy.spi.UnhandledException: java.lang.RuntimeException: Error injecting link.zeljko.quarkus.model.PersonRepository link.zeljko.quarkus.service.PersonService.personRepository
...
Caused by: java.lang.ClassCastException: link.zeljko.quarkus.model.PersonRepository_ClientProxy cannot be cast to link.zeljko.quarkus.model.PersonRepository
    at link.zeljko.quarkus.service.PersonService_Bean.create(PersonService_Bean.zig:151)
    ... 31 more

To Reproduce
Steps to reproduce the behavior:

  1. Create two projects, the main project, and dependency with injectable class. Add beans.xml to both.
  2. mvn install dependency
  3. Add dependency to the main project
  4. Inject class from the dependency
  5. Run main project in the dev mode.
  6. Make operation that accesses the injected variable.

Configuration

quarkus.datasource.url=jdbc:h2:file:./persons
quarkus.datasource.driver=org.h2.Driver
quarkus.hibernate-orm.database.generation=update
quarkus.http.port=8000

Screenshots
(If applicable, add screenshots to help explain your problem.)

Environment (please complete the following information):

  • Output of uname -a or ver: Microsoft Windows [Version 10.0.18362.418]
  • Output of java -version:
    java version "1.8.0_221"
    Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
    Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
  • GraalVM version (if different from Java):
  • Quarkus version or git rev: 0.26.1

Additional context
quarkus.zip

arearc kinbug

Most helpful comment

It should be, but I don't have a reproducer handy for this particular issue.

All 29 comments

@mkouba could you have a look please?

This is related to how transformation is handled in dev mode. Because PersonReposity is not in the main application arc assumes that it will be loaded by the app class loader, and so creates the client proxy in the app class loader.

However because this is transformed it actually ends up in the RuntimeClassLoader, so the client proxy and repository and in different loaders.

@mkouba if you want to handle this have a look at the logic https://github.com/quarkusio/quarkus/blob/c3e9bc7da2255ca744c818734cdf6bb0b335bdc2/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java#L273 to see what should be considered an application class (although this should be moved into a build step so it is only run once).

Otherwise I can look at it tomorrow

@stuartwdouglas So if there is at least one transformed class in an archive we consider all classes from that archive to be app classes?

yes. If we just transformed the class and left the rest in the base class loader then package private access would be broken.

Note that a temporary workaround would be to have all the modules in the same maven project, then dev mode will consider them all to be replaceable classes and will load them all in the correct ClassLoader.

Thank you for the tip! The project I am working on has one project per repo, and projects already have a parent (error: 'parent.relativePath' points at wrong local POM) so it is probably not applicable, but it might help somebody else.

Is this going to be fixed with #6411?

It should be, but I don't have a reproducer handy for this particular issue.

I just got this error too and thus would really appreciate it if this could be fixed since I'm now forced to produce a lot of code duplicates.

Stuart is currently working on a new class loading mechanism. So we should probably wait until it's merged and verify this use case afterwards. @stuartwdouglas WDYT?

If anyone has a reproducer ready it would be good to test against my branch, but I am pretty sure it will be fixed.

Hi Stuart,

I would like to test against your branch but I don't know where I can find it. Could you please give me a hint?

Just thougt it would not be that hard to write a minimal reproducer but I did not succeed until now. With my own project the issue is reproducable.

I just found your branch and am now struggling with the java version. My projects require java 11. So it could take some time.

In your branch I was missing the dependencies

quarkus-rest-client and quarkus-resteasy-jackson

but I'll try using 1.1.1.Final for these.

Hi Stuart,

unfortunately I was not successful by now. After switching to your quarkus-branch I could not install my quarkus application artifact via maven any more and when running the server it died quietly (no stack trace). It seems that the quarkus maven build now is ignoring the beans.xml in my dependent jar (before switching to your branch with Quarkus 1.1.1.Final this worked).

So at the moment I am stuck.

I did check in my own branch:

https://github.com/heike2718/minikaenguru/tree/classloader-reproducer

There you can find a file classloader-reproducer-log.md with the steps I went so far.

I would really apprechiate any help.

Hi Stuart

finally I did succeed with a minimal reproducer: see https://github.com/heike2718/classloader-reproducer

Without JPA it was not possible to reproduce the ClassCastException, but as soon as I used Hibernate JPA I got the Exception running with Quarkus 1.1.1.Final.

Switching to your branch in deed fixed the class loader issue but now I got an NPE since the EntityManager was not properly injected. I do not have a clue what went wrong with the EntityManager by now. There are very view JPA configurations in application.properties and maybe I am missing an important one.

Please ignore the repo I mentioned in the receeding comment since this is much too complex to see what is going on. This reproducer here is really easy to use.

Hi Stuart,

Now I fixed the JPA configuration problem in my reproducer too:

https://github.com/heike2718/classloader-reproducer

I was forced to include a persistence.xml into the maven dependency, being used by the quarkus artifact. Annotating EntityManager with Inject leeds to the above mentioned NPE. Annotating with PersistenceContext works but requires a persistence.xml. But this is OK

I think this issue here is really fixed by your branch and am looking forward to the next Quarkus release :)

(Edit: typos)

@heike2718 you shouldn't have to use a persistence.xml. Did you index your external module with Jandex?

@gsmet

The situation is this: the new classloader mechanism works well for CDI without JPA, so one bug vanished with the above mentioned fix. And you are right, either beans.xml or jandex.idx is sufficient.

But there is one detail, that I overlooked the last time: the EntityManager does not properly get injected inside an external dependency. No matter if there is a persistence.xml or not, it is null.

Another point: if the EntityManager it is annotated with Inject instead of PersistenceContext (as proposed by the quarkus documentation), the quarkus- application dies quietly after start (no error message). This is not the case when the EntityManager lives inside the quarkus application.

So I think there is a bug in connection with JPA and CDI in an external dependency. This can be reproduced with the reproducer.

Should I file an issue?

Do you have a beans.xml in the module consuming the EntityManager?

@gsmet

No. I changed it to generating a jandex.idx. Btw you can clone or fork the reproducer and check the configuration. It is really minimal.

I left a renamed beans.xml and persistence.xml so that you can easily test it with different configurations.

I am going to have a look into this and see what it going on

Were you using my new-class-loading2 branch? I just tested this out and I don't see any NPE, or any other failure. There are some issues with the app itself but they don't seem to be related to this bug.

@stuartwdouglas

No, I was on the master.

Just now I checked out new-class-loader2 but could'nt install it and thus cannot test with this branch: Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.6.0:exec (gradle) on project io.quarkus.gradle.plugin: Command execution failed.: Process exited with an error: 1 (Exit value: 1) -> [Help 1]
(I have openjdk version "1.8.0_232" and Gradle 3.2.1 locally installed)

What do you mean with "There are some issues with the app itself"?

@stuartwdouglas

I just fixed createUser. Please pull the new version.

But I could not check if this now works either because the entity manager is null.

It works for me now.

OK, thanks a lot.

A view minutes ago I also fixed the ID generation and the quarkus.hibernate-orm.database.generation property. I tested it without external dependency and this worked.

Now I will wait for the new Quarkus release.

I think the issue this fixes (https://github.com/quarkusio/quarkus/pull/6411)
, also fixes an issue that I have. I am seeing that a class that is being injected from a separate Maven module errors as an "Ambiguous bean" when we try to inject it. It shows the exact same class twice when we run in quarkus:dev mode, but it works fine when we run the X-runner.jar . Moving the class into the same project also fixes it.

When will this be in a build that we can test with?

Thanks!

-sean

It should go in early in the 1.3 cycle

This should have been fixed by the class loader changes, please re-open if it is still an issue.

Was this page helpful?
0 / 5 - 0 ratings