Compiling a basic Vaadin 24 project using
mvn clean package native:compile -Pproduction -Pnative
works but when running the project I get
java.lang.RuntimeException: Unable to initialize com.vaadin.flow.spring.VaadinServletContextInitializer$LookupInitializerListener
at com.vaadin.flow.spring.VaadinServletContextInitializer$FailFastServletContextListener.contextInitialized(VaadinServletContextInitializer.java:191) ~[native-default-flow:na]
at com.vaadin.flow.spring.VaadinServletContextInitializer$CompositeServletContextListener.lambda$contextInitialized$0(VaadinServletContextInitializer.java:213) ~[na:na]
at java.base@19.0.1/java.util.ArrayList.forEach(ArrayList.java:1511) ~[native-default-flow:na]
at com.vaadin.flow.spring.VaadinServletContextInitializer$CompositeServletContextListener.contextInitialized(VaadinServletContextInitializer.java:213) ~[na:na]
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4710) ~[native-default-flow:10.1.5]
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5155) ~[native-default-flow:10.1.5]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[native-default-flow:10.1.5]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393) ~[na:na]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1383) ~[na:na]
at java.base@19.0.1/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[native-default-flow:na]
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[na:na]
at java.base@19.0.1/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) ~[native-default-flow:na]
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:916) ~[native-default-flow:10.1.5]
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:886) ~[na:na]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[native-default-flow:10.1.5]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393) ~[na:na]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1383) ~[na:na]
at java.base@19.0.1/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[native-default-flow:na]
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[na:na]
at java.base@19.0.1/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) ~[native-default-flow:na]
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:916) ~[native-default-flow:10.1.5]
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:252) ~[na:na]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[native-default-flow:10.1.5]
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:430) ~[na:na]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[native-default-flow:10.1.5]
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:926) ~[na:na]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[native-default-flow:10.1.5]
at org.apache.catalina.startup.Tomcat.start(Tomcat.java:485) ~[na:na]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:123) ~[na:na]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:104) ~[na:na]
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:486) ~[native-default-flow:3.0.2]
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:210) ~[native-default-flow:3.0.2]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:183) ~[native-default-flow:3.0.2]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:161) ~[native-default-flow:3.0.2]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:578) ~[native-default-flow:6.0.4]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[native-default-flow:3.0.2]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[native-default-flow:3.0.2]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432) ~[native-default-flow:3.0.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[native-default-flow:3.0.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[native-default-flow:3.0.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[native-default-flow:3.0.2]
at com.example.application.Application.main(Application.java:23) ~[native-default-flow:na]
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [/com/example/application/Application.class]
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:463) ~[native-default-flow:6.0.4]
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:317) ~[native-default-flow:6.0.4]
at java.base@19.0.1/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
at java.base@19.0.1/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625) ~[na:na]
at java.base@19.0.1/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[native-default-flow:na]
at java.base@19.0.1/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[native-default-flow:na]
at java.base@19.0.1/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:310) ~[na:na]
at java.base@19.0.1/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734) ~[native-default-flow:na]
at java.base@19.0.1/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[native-default-flow:na]
at java.base@19.0.1/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[native-default-flow:na]
at java.base@19.0.1/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[native-default-flow:na]
at java.base@19.0.1/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[native-default-flow:na]
at java.base@19.0.1/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) ~[native-default-flow:na]
at com.vaadin.flow.spring.VaadinServletContextInitializer$LookupInitializerListener.failFastContextInitialized(VaadinServletContextInitializer.java:250) ~[na:na]
at com.vaadin.flow.spring.VaadinServletContextInitializer$FailFastServletContextListener.contextInitialized(VaadinServletContextInitializer.java:187) ~[native-default-flow:na]
... 41 common frames omitted
Caused by: java.lang.IllegalArgumentException: Class org.springframework.core.annotation.TypeMappedAnnotation[] is instantiated reflectively but was never registered.Register the class by adding "unsafeAllocated" for the class in reflect-config.json.
at org.graalvm.nativeimage.builder/com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets.arrayHubErrorStub(SubstrateAllocationSnippets.java:345) ~[na:na]
at org.springframework.core.type.classreading.MergedAnnotationReadingVisitor$ArrayVisitor.visitEnd(MergedAnnotationReadingVisitor.java:183) ~[na:na]
at org.springframework.asm.ClassReader.readElementValues(ClassReader.java:3010) ~[na:na]
at org.springframework.asm.ClassReader.readElementValue(ClassReader.java:3180) ~[na:na]
at org.springframework.asm.ClassReader.readElementValues(ClassReader.java:3000) ~[na:na]
at org.springframework.asm.ClassReader.accept(ClassReader.java:610) ~[na:na]
at org.springframework.asm.ClassReader.accept(ClassReader.java:426) ~[na:na]
at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:48) ~[na:na]
at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103) ~[native-default-flow:6.0.4]
at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:122) ~[na:na]
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:435) ~[native-default-flow:6.0.4]
... 55 common frames omitted
The mentioned application class is
package com.example.application;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.theme.Theme;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@Theme(value = "native-default-flow")
@NpmPackage(value = "line-awesome", version = "1.3.0")
@NpmPackage(value = "@vaadin-component-factory/vcf-nav", version = "1.0.6")
public class Application implements AppShellConfigurator {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
If I remove the @NpmPackage
annotation the exception disappears. It is defined as
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
@Repeatable(NpmPackage.Container.class)
public @interface NpmPackage {
String value();
String version();
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
@interface Container {
NpmPackage[] value();
}
}
which seems pretty standard
Comment From: sbrannen
MergedAnnotationReadingVisitor.ArrayVisitor.visitEnd()
uses reflection to instantiate an array, which will be of type TypeMappedAnnotation[]
for an annotation attribute whose type is an array of nested annotations (which happens to be the case for the value
attribute in a repeatable annotation's container annotation).
So we are likely just missing a reflection hint for the TypeMappedAnnotation[]
type.
Comment From: sbrannen
which seems pretty standard
I'm not sure how common that construct is.
In fact, I don't know if I've ever seen a repeatable annotation container declared within the repeatable annotation itself.
Out of curiosity, what happens if you declare Container
as a top-level class/annotation?
Comment From: Artur-
Same error when the container annotation is top level. Seems like it is related to repeating the annotation and not where the container is defined
Comment From: sbrannen
Thanks for checking and reporting back, @Artur-!
It seems a bit strange to me that we've never encountered issues like this with repeatable annotations within a native image before.
However, without having debugged it, I'm guessing the difference is that ASM is being used to process the annotations on the @SpringBootApplication
class ... and it may simply be the case that you're the first person to declare repeatable annotations on a @SpringBootApplication
class and then run the app in a native image. 🤷
Comment From: snicoll
I can't reproduce with the sample above and Spring Boot 3.0.6
.
Comment From: Artur-
This is my test https://github.com/Artur-/vaadin24-native-test
Comment From: snicoll
Thanks. For the record, an AOT processed application should not run component scan at all as it has been taken care at build-time. Does the Vaadin spring integration perform component scan at runtime?
Comment From: sbrannen
Does the Vaadin spring integration perform component scan at runtime?
Yes, it does.
com.vaadin.flow.spring.VaadinServletContextInitializer.LookupInitializerListener
invokes com.vaadin.flow.spring.VaadinServletContextInitializer.findByAnnotationOrSuperType()
which uses com.vaadin.flow.spring.VaadinServletContextInitializer.ClassPathScanner
which extends Spring's ClassPathScanningCandidateComponentProvider
.
Comment From: sbrannen
@Artur-,
Changing your Application
class as follows allows your application to start up and function properly.
@SpringBootApplication
@Theme(value = "vaadin24-native")
@MyAnnotation(value = "line-awesome", version = "1.3.0")
@MyAnnotation(value = "@vaadin-component-factory/vcf-nav", version = "1.0.6")
@ImportRuntimeHints(TypeMappedAnnotationArrayRuntimeHints.class)
public class Application implements AppShellConfigurator {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
static class TypeMappedAnnotationArrayRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection().registerType(TypeReference.of("org.springframework.core.annotation.TypeMappedAnnotation[]"),
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}
}
Specifically, TypeMappedAnnotationArrayRuntimeHints
registers TypeMappedAnnotation[]
for reflection.
You can use that as a workaround; however, as @snicoll pointed out in https://github.com/spring-projects/spring-framework/issues/30071#issuecomment-1523511285, your application should not be performing component scanning within a GraalVM native image.
In light of that, I am closing this issue.
However, I recommend that the Vaadin team investigate options to avoid component scanning within a native image.
Comment From: oliveryasuna
@snicoll How can we lookup the results from component scan performed at build-time during runtime?
Comment From: snicoll
If you do the component scanning in a custom code, you need to retain that in some form yourself. We do this for JPA, externalizing the scanning of JPA entities and making sure that the entities are processed at build-time. Please look at PersistenceManagedTypes
and friends. There is also a bit of doc on how to use it.