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.