Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hibernate tries to reflectively access Service methods not registered for reflection #45525

Closed
zakkak opened this issue Jan 13, 2025 · 5 comments · Fixed by #45649
Closed

Hibernate tries to reflectively access Service methods not registered for reflection #45525

zakkak opened this issue Jan 13, 2025 · 5 comments · Fixed by #45649
Assignees
Labels
area/hibernate-orm Hibernate ORM area/native-image kind/bug Something isn't working
Milestone

Comments

@zakkak
Copy link
Contributor

zakkak commented Jan 13, 2025

Describe the bug

Hibernate seems to be reflectively accessing methods from service classes that we don't register for reflection in native-mode.

The methods are accessed in https://github.com/hibernate/hibernate-orm/blob/9d52e3c9dfc49a9be804220c2382055d5901daa9/hibernate-core/src/main/java/org/hibernate/service/internal/AbstractServiceRegistryImpl.java#L293

Relates to #41995

Expected behavior

All reflective accesses should be registered.

Actual behavior

Reflective accesses are not registered resulting in warnings like the following when using -H:ThrowMissingRegistrationErrors=:

...
org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively access

   org.hibernate.stat.internal.StatisticsImpl.getMethods()

without it being registered for runtime reflection. Add org.hibernate.stat.internal.StatisticsImpl.getMethods() to the reflection metadata to solve this problem. See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection for help.
  java.base@25/java.lang.Class.getMethods(DynamicHub.java:1269)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.applyInjections(AbstractServiceRegistryImpl.java:293)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:284)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:244)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
  org.hibernate.service.internal.SessionFactoryServiceRegistryImpl.getService(SessionFactoryServiceRegistryImpl.java:114)
  org.hibernate.service.ServiceRegistry.requireService(ServiceRegistry.java:68)
  org.hibernate.internal.SessionFactoryImpl.getStatistics(SessionFactoryImpl.java:1091)
  org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.resolveHqlInterpretation(QueryInterpretationCacheStandardImpl.java:108)
  org.hibernate.query.internal.NamedObjectRepositoryImpl.checkNamedQueries(NamedObjectRepositoryImpl.java:249)
  org.hibernate.query.internal.NamedObjectRepositoryImpl.validateNamedQueries(NamedObjectRepositoryImpl.java:219)
  org.hibernate.query.internal.QueryEngineImpl.validateNamedQueries(QueryEngineImpl.java:215)
  io.quarkus.hibernate.orm.runtime.observers.SessionFactoryObserverForNamedQueryValidation.sessionFactoryCreated(SessionFactoryObserverForNamedQueryValidation.java:27)
  org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:35)
  org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:324)
  io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder.build(FastBootEntityManagerFactoryBuilder.java:87)
  io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.createEntityManagerFactory(FastBootHibernatePersistenceProvider.java:72)
  jakarta.persistence.Persistence.createEntityManagerFactory(Persistence.java:80)
  jakarta.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
  io.quarkus.hibernate.orm.runtime.JPAConfig$LazyPersistenceUnit.get(JPAConfig.java:154)
org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively access

   org.hibernate.tool.schema.internal.HibernateSchemaManagementTool.getMethods()

without it being registered for runtime reflection. Add org.hibernate.tool.schema.internal.HibernateSchemaManagementTool.getMethods() to the reflection metadata to solve this problem. See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection for help.
  java.base@25/java.lang.Class.getMethods(DynamicHub.java:1269)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.applyInjections(AbstractServiceRegistryImpl.java:293)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:284)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:244)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
  org.hibernate.service.internal.SessionFactoryServiceRegistryImpl.getService(SessionFactoryServiceRegistryImpl.java:114)
  org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:117)
  io.quarkus.hibernate.orm.runtime.observers.SessionFactoryObserverForSchemaExport.sessionFactoryCreated(SessionFactoryObserverForSchemaExport.java:21)
  org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:35)
  org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:324)
  io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder.build(FastBootEntityManagerFactoryBuilder.java:87)
  io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.createEntityManagerFactory(FastBootHibernatePersistenceProvider.java:72)
  jakarta.persistence.Persistence.createEntityManagerFactory(Persistence.java:80)
  jakarta.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
  io.quarkus.hibernate.orm.runtime.JPAConfig$LazyPersistenceUnit.get(JPAConfig.java:154)
  io.quarkus.hibernate.orm.runtime.JPAConfig$1.run(JPAConfig.java:61)
  java.base@25/java.lang.Thread.runWith(Thread.java:1460)
  java.base@25/java.lang.Thread.run(Thread.java:1447)
  org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:832)
  org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:808)
org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively access

   io.quarkus.hibernate.orm.runtime.service.QuarkusRuntimeInitDialectResolver.getMethods()

without it being registered for runtime reflection. Add io.quarkus.hibernate.orm.runtime.service.QuarkusRuntimeInitDialectResolver.getMethods() to the reflection metadata to solve this problem. See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection for help.
  java.base@25/java.lang.Class.getMethods(DynamicHub.java:1269)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.applyInjections(AbstractServiceRegistryImpl.java:293)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:284)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:244)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
  org.hibernate.service.ServiceRegistry.requireService(ServiceRegistry.java:68)
  org.hibernate.tool.schema.internal.HibernateSchemaManagementTool.resolveJdbcContext(HibernateSchemaManagementTool.java:304)
  org.hibernate.tool.schema.internal.SchemaDropperImpl.doDrop(SchemaDropperImpl.java:115)
  org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:238)
  org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.lambda$process$5(SchemaManagementToolCoordinator.java:144)
  java.base@25/java.util.HashMap.forEach(HashMap.java:1430)
  org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:141)
  io.quarkus.hibernate.orm.runtime.observers.SessionFactoryObserverForSchemaExport.sessionFactoryCreated(SessionFactoryObserverForSchemaExport.java:21)
  org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:35)
  org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:324)
  io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder.build(FastBootEntityManagerFactoryBuilder.java:87)
  io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.createEntityManagerFactory(FastBootHibernatePersistenceProvider.java:72)
  jakarta.persistence.Persistence.createEntityManagerFactory(Persistence.java:80)
  jakarta.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
  io.quarkus.hibernate.orm.runtime.JPAConfig$LazyPersistenceUnit.get(JPAConfig.java:154)
org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively access

   org.hibernate.tool.schema.internal.script.MultiLineSqlScriptExtractor.getMethods()

without it being registered for runtime reflection. Add org.hibernate.tool.schema.internal.script.MultiLineSqlScriptExtractor.getMethods() to the reflection metadata to solve this problem. See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection for help.
  java.base@25/java.lang.Class.getMethods(DynamicHub.java:1269)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.applyInjections(AbstractServiceRegistryImpl.java:293)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:284)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:244)
  org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
  org.hibernate.tool.schema.internal.SchemaDropperImpl.getCommandExtractor(SchemaDropperImpl.java:200)
  org.hibernate.tool.schema.internal.SchemaDropperImpl.performDrop(SchemaDropperImpl.java:177)
  org.hibernate.tool.schema.internal.SchemaDropperImpl.doDrop(SchemaDropperImpl.java:156)
  org.hibernate.tool.schema.internal.SchemaDropperImpl.doDrop(SchemaDropperImpl.java:116)
  org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:238)
  org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.lambda$process$5(SchemaManagementToolCoordinator.java:144)
  java.base@25/java.util.HashMap.forEach(HashMap.java:1430)
  org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:141)
  io.quarkus.hibernate.orm.runtime.observers.SessionFactoryObserverForSchemaExport.sessionFactoryCreated(SessionFactoryObserverForSchemaExport.java:21)
  org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:35)
  org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:324)
  io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder.build(FastBootEntityManagerFactoryBuilder.java:87)
  io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.createEntityManagerFactory(FastBootHibernatePersistenceProvider.java:72)
  jakarta.persistence.Persistence.createEntityManagerFactory(Persistence.java:80)
  jakarta.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
...

How to Reproduce?

./mvnw clean package -Dnative -Dnative.surefire.skip \
  -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-23 \
  -pl integration-tests/jpa-db2/ \
  -Dquarkus.native.additional-build-args=-H:ThrowMissingRegistrationErrors=

./integration-tests/jpa-db2/target/quarkus-integration-test-jpa-db2-999-SNAPSHOT-runner -XX:MissingRegistrationReportingMode=Warn

Output of uname -a or ver

No response

Output of java -version

21.0.5

Mandrel or GraalVM version (if different from Java)

Mandrel-24.1.1.0-Final

Quarkus version or git rev

No response

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

No response

@zakkak zakkak added area/native-image kind/bug Something isn't working labels Jan 13, 2025
@quarkus-bot quarkus-bot bot added the area/hibernate-orm Hibernate ORM label Jan 13, 2025
Copy link

quarkus-bot bot commented Jan 13, 2025

/cc @Karm (native-image), @galderz (native-image), @gsmet (hibernate-orm)

@zakkak
Copy link
Contributor Author

zakkak commented Jan 13, 2025

/cc @yrodiere

@yrodiere
Copy link
Member

This is used for the @InjectService feature in Hibernate ORM, which allows injecting Hibernate ORM services into other Hibernate ORM services.

This feature is only used in one built-in service in Hibernate ORM (DatasourceConnectionProviderImpl) and none in Hibernate Reactive. And DatasourceConnectionProviderImpl service is in general replaced by QuarkusConnectionProvider in Quarkus. Perhaps it can be used when using persistence.xml, but I doubt it (it would require JNDI, which I'm not even sure works in Quarkus).

We could enable reflection on all service classes, but that's a shame, since it'll only be useful for a single service class -- and custom services, if that's even usable in Quarkus (in native mode, I doubt it).

What's more, there's a perfectly reasonable alternative that doesn't require reflection: implementing ServiceRegistryAwareService.

We unfortunately can't enable reflection only on service classes that do use @InjectService, because AbstractServiceRegistryImpl would still try to use reflection on other classes (just to know whether they do use @InjectService).

I'd be tempted to just document @InjectService as not supported in Quarkus, with perhaps a build-time check to fail if the annotation is used, and to override org.hibernate.service.internal.AbstractServiceRegistryImpl#injectDependencies in all service registry implementations we use in Quarkus. But that could involve duplicating quite a few classes.

@zakkak what's the timeline for your changes? If they can wait until Hibernate ORM 7.0, we could solve this much more cleanly (e.g. by providing a way to disable @InjectService in Hibernate ORM). If not, we'll likely need to add reflection all over the place.
Also, would there be a way to revert to the behavior before your changes, on a per-class basis? E.g. we'd look for @InjectService on all services, enable reflection on those that use it, and revert to the legacy behavior ("have Class#getMethods return an empty array") for the rest.

@yrodiere yrodiere changed the title Hibernate tries to reflectively access methods not registered for reflection Hibernate tries to reflectively access Service methods not registered for reflection Jan 16, 2025
@zakkak
Copy link
Contributor Author

zakkak commented Jan 16, 2025

@zakkak what's the timeline for your changes?

It's not clear, as per oracle/graal#5173 (comment) -H:ThrowMissingRegistrationErrors= (which is the same as --exact-reachability-metadata) :

 ...  will be promoted to the default behavior as the community adopts it.

If they can wait until Hibernate ORM 7.0, we could solve this much more cleanly (e.g. by providing a way to disable @InjectService in Hibernate ORM).

What's the ETA for the ORM 7.0 release, and respectively for Quarkus transitioning to it?

If not, we'll likely need to add reflection all over the place.

It's not clear to me what you mean by this? Do you refer to enablung reflection for all service classes?

Also, would there be a way to revert to the behavior before your changes, on a per-class basis? E.g. we'd look for @InjectService on all services, enable reflection on those that use it, and revert to the legacy behavior ("have Class#getMethods return an empty array") for the rest.

There is a discussion about Relaxing --exact-reachability-metadata so the answer is not clear yet although it would probably be negative, especially for a per-class exception.

We could enable reflection on all service classes, but that's a shame, since it'll only be useful for a single service class -- and custom services, if that's even usable in Quarkus (in native mode, I doubt it).

Note that the classes are already reachable, so enabling reflection for getMethods is expected to have a small impact on the binary size as it would only add some metadata for the methods. If you point me to a way of getting all the supported services in a Quarkus BuildStep I could have a go with it.

@yrodiere
Copy link
Member

It's not clear to me what you mean by this? Do you refer to enablung reflection for all service classes?

Yes.

What's the ETA for the ORM 7.0 release, and respectively for Quarkus transitioning to it?

CR1 is being discussed, so I imagine a few weeks, 2 months tops. (this is not a promise, just a guess :) )

Note that the classes are already reachable, so enabling reflection for getMethods is expected to have a small impact on the binary size as it would only add some metadata for the methods.

Okay, if you're fine with it, so am I :)

If you point me to a way of getting all the supported services in a Quarkus BuildStep I could have a go with it.

If we want to automate this and never think about it ever again, we'd have to look (through Jandex?) for all implementations of org.hibernate.service.Service, and register those for reflection.

That's dozens of classes though, some of which may not even be reachable. If we want to be more precise, we'd have to somehow maintain a hardcoded list of all service classes are are not retrieved through reflections. io.quarkus.hibernate.orm.deployment.ClassNames would be the place to put that list, but what to put in there is a more complex question. I'd have to look, but I suspect it would be fragile.

Alternatively, we could leverage a GraalVM "feature" that registers the methods of a Service class for reflection only if it's reachable? I know it's doable as I've done something like it there:

for (Class<?> builderSubClass : access.reachableSubtypes(WithJsonObjectBuilderBase.class)) {
enableBuilderWithJson(builderSubClass, access);
}
Would this make sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/hibernate-orm Hibernate ORM area/native-image kind/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants