'Native Image - Spring Boot - AWS Serverless Java Container - startup error - missing ServletWebServerFactory bean

I am trying to create a native image for my Spring Boot application that also uses the AWS serverless Java container.

The Spring Boot app is a simple petstore application.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            
            <!-- Comment for local -->
            <exclusion>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
            <!-- Not using yaml -->
            <exclusion>
              <groupId>org.yaml</groupId>
              <artifactId>snakeyaml</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>com.amazonaws.serverless</groupId>
        <artifactId>aws-serverless-java-container-springboot2</artifactId>
        <version>1.8</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin> -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <configuration>
                <createDependencyReducedPom>false</createDependencyReducedPom>
                <!-- <filters>
                    <filter>
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>**/Log4j2Plugins.dat</exclude>
                        </excludes>
                    </filter>
                </filters> -->
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <artifactSet>
                            <excludes>
                                <exclude>org.apache.tomcat.embed:*.jar</exclude>
                            </excludes>
                        </artifactSet>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
    
</build>

Entry point

public class StreamLambdaHandler implements RequestStreamHandler {
private static SpringBootLambdaContainerHandler<HttpApiV2ProxyRequest, AwsProxyResponse> handler;
private static Logger logger = LoggerFactory.getLogger(StreamLambdaHandler.class);

public static void main(String[] args) {
    
}

static {
    try {
        logger.info("StreamLambdaHandler static init start");
        //handler = SpringBootLambdaContainerHandler.getHttpApiV2ProxyHandler(SamplebootApplication.class);
        
          handler = new SpringBootProxyHandlerBuilder<HttpApiV2ProxyRequest>()
          .defaultHttpApiV2Proxy() .asyncInit()
          .springBootApplication(SamplebootApplication.class) .buildAndInitialize();
          
          logger.info("StreamLambdaHandler init -async init call ");
         
        logger.info("StreamLambdaHandler init call invoked");

    } catch (ContainerInitializationException e) {
        // if we fail here. We re-throw the exception to force another cold start
        e.printStackTrace();
        throw new RuntimeException("Could not initialize Spring Boot application", e);
    }
}

@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
    Instant start = Instant.now();
    logger.info("StreamLambdaHandler handler method begin");
    handler.proxyStream(inputStream, outputStream, context);
    logger.info(
            "StreamLambdaHandler handler method end in millisecs:" + start.until(Instant.now(), ChronoUnit.MILLIS));

}

The application works when deployed on AWS lambda. I am now trying to optimise the cold start time by converting to Native Image.

Added spring native

<dependency>
        <!-- This is a mandatory dependency for your application -->
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-native</artifactId>
        <version>0.11.4</version>
    </dependency>

Removed shade plugin, added AOT plugin

<plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            
        </plugin>
        <plugin>
            <groupId>org.springframework.experimental</groupId>
            <artifactId>spring-aot-maven-plugin</artifactId>
            <version>0.11.4</version>
            <executions>
                <execution>
                    <id>generate</id>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                </execution>
                <!-- <execution> <id>test-generate</id> <goals> <goal>test-generate</goal> 
                    </goals> </execution> -->
            </executions>
        </plugin>

The executable jar - works fine in local.

Edit: The jar fails with the same error as below when run with springAot=true.

The jar is now converted to Native Image using below steps.

rm -rf native
mkdir -p native
cd native
jar -xvf ../samplebootv2-native-0.0.1-SNAPSHOT.jar >/dev/null 2>&1
cp -R META-INF BOOT-INF/classes
native-image -H:Name=samplebootv2-native-app -cp BOOT-INF/classes:`find BOOT-INF/lib | tr '\n' ':'`

Image is build without errors. However, when the native image is excecuted it fails with following error.

    org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:163) ~[samplebootv2-native-app:2.6.6]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:577) ~[samplebootv2-native-app:5.3.18]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[samplebootv2-native-app:2.6.6]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740) ~[samplebootv2-native-app:2.6.6]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415) ~[samplebootv2-native-app:2.6.6]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) ~[samplebootv2-native-app:2.6.6]
        at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:164) ~[na:na]
        at com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler.initialize(SpringBootLambdaContainerHandler.java:195) ~[samplebootv2-native-app:na]
        at com.amazonaws.serverless.proxy.AsyncInitializationWrapper$AsyncInitializer.run(AsyncInitializationWrapper.java:120) ~[na:na]
        at java.lang.Thread.run(Thread.java:829) ~[samplebootv2-native-app:na]
        at com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:704) ~[samplebootv2-native-app:na]
        at com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:202) ~[na:na]
    Caused by: org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getWebServerFactory(ServletWebServerApplicationContext.java:210) ~[samplebootv2-native-app:2.6.6]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:180) ~[samplebootv2-native-app:2.6.6]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:160) ~[samplebootv2-native-app:2.6.6]
        ... 11 common frames omitted    

I checked the generated image using graal vm visualiser and found that the classes from the aws-serverless-java-container-springboot2-1.8.jar were missing in the image

So I added the class files (only few of them) in

META-INF\native-image\org.springframework.aot\spring-aot\reflect-config.json

Also tried(with both init modes)

@InitializationHint(types =com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory.class, initTime = InitializationTime.RUN)

However, the same error repeats. The classes are missing in the generated image.

Could anyone provide some insights into what could be going wrong here?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source