I've run into an issue with the jakarta.persistence.Convert
annotation with GraalVM and Spring Native, see the stacktrace below.
I'm a bit unsure if this issue should be reported here, with the Hibernate project or elsewhere .
Could not resolve matching constructor on bean class [.....EmailConverter] (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1751) ~[demo:6.0.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[demo:6.0.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[demo:6.0.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[demo:6.0.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[demo:6.0.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[demo:6.0.3]
The Converter
is implemented as shown below and is applied via @Convert(converter = ....)
.
The issue does not present itself if I remove the @Convert
and instead auto apply it via @Converter(autoApply = true)
.
@Column(name = "email", columnDefinition = "varchar(255) not null", nullable = false)
@Convert(converter = EmailConverter.class)
@NaturalId
private Email email;
public final class EmailConverter implements AttributeConverter<Email, String> {
public EmailConverter() {}
@Override
public String convertToDatabaseColumn(final Email attribute) {
return attribute.getValue();
}
@Override
public Email convertToEntityAttribute(final String dbData) {
return new Email(dbData);
}
}
You can reproduce the issue with https://github.com/NicklasWallgren/spring-boot-native-issue
Comment From: wilkinsona
Thanks for the sample. Unfortunately, it doesn't compile:
./gradlew nativeCompile
Starting a Gradle Daemon, 3 incompatible Daemons could not be reused, use --status for details
> Task :compileJava FAILED
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:10: error: cannot find symbol
public final class Email extends ValueObject<String> {
^
symbol: class ValueObject
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:30: error: cannot find symbol
if (value != null) {
^
symbol: variable value
location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:31: error: cannot find symbol
return value;
^
symbol: variable value
location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:37: error: method does not override or implement a method from a supertype
@Override
^
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:40: error: cannot find symbol
throw ValidationErrorException.of(ValidationError.of("invalid email address", value, "email"));
^
symbol: variable value
location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:50: error: cannot find symbol
if (value == null) {
^
symbol: variable value
location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:54: error: cannot find symbol
final int splitPosition = value.lastIndexOf('@');
^
symbol: variable value
location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:59: error: cannot find symbol
final String localPart = value.substring(0, splitPosition);
^
symbol: variable value
location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:60: error: cannot find symbol
final String domainPart = value.substring(splitPosition + 1);
^
symbol: variable value
location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/EmailConverter.java:19: error: cannot find symbol
return attribute.getValue();
^
symbol: method getValue()
location: variable attribute of type Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/EmailConverter.java:26: error: constructor Email in class Email cannot be applied to given types;
return new Email(dbData);
^
required: String
found: String
reason: Email(String) has private access in Email
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
11 errors
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileJava'.
> Compilation failed; see the compiler error output for details.
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 13s
1 actionable task: 1 executed
Comment From: NicklasWallgren
@wilkinsona Sorry, I forgot to push the latest changes. It should compile now.
Comment From: wilkinsona
Running the app with debug logging enabled shows that both SpringBeanContainer
and Hibernate itself fail to create an instance of EmailConverter
:
2023-01-05T13:14:14.868Z DEBUG 50100 --- [ Thread-1] o.s.orm.hibernate5.SpringBeanContainer : Falling back to Hibernate's default producer after bean creation failure for class com.example.demo.EmailConverter: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.example.demo.EmailConverter': Could not resolve matching constructor on bean class [com.example.demo.EmailConverter] (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)
2023-01-05T13:14:14.868Z DEBUG 50100 --- [ Thread-1] o.s.orm.hibernate5.SpringBeanContainer : Fallback producer failed for class com.example.demo.EmailConverter: org.hibernate.InstantiationException: Could not instantiate managed bean directly : com.example.demo.EmailConverter
This is due to a lack of reflection hints which means that the converter class cannot be created reflectively in a native image. You could work around the problem by using @ImportRuntimeHints
to import a RuntimeHintsRegistrar
implementation that registers EmailConverter
allowing invocation of its public constructors. We can also look into doing this automatically.
Comment From: NicklasWallgren
@wilkinsona Thanks for the clarification. I will look in to RuntimeHintsRegistrar
.
Comment From: wilkinsona
As discussed with @sdeleuze, PersistenceManagedTypesBeanRegistrationAotProcessor
looks like a logical place for us to deal with this. We've transferred this to the Framework repository (thanks, Brian!) for their further consideration.
Comment From: wilkinsona
@NicklasWallgren Your sample app starts when this change is applied to its main class:
diff --git a/src/main/java/com/example/demo/DemoApplication.java b/src/main/java/com/example/demo/DemoApplication.java
index 64b538a..76b9b00 100644
--- a/src/main/java/com/example/demo/DemoApplication.java
+++ b/src/main/java/com/example/demo/DemoApplication.java
@@ -1,13 +1,29 @@
package com.example.demo;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ImportRuntimeHints;
+
+import com.example.demo.DemoApplication.ConverterRuntimeHints;
@SpringBootApplication
+@ImportRuntimeHints(ConverterRuntimeHints.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
+ static class ConverterRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ hints.reflection().registerType(EmailConverter.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
+ }
+
+ }
+
}
Comment From: NicklasWallgren
@wilkinsona Great, thanks for the help!