Unable to build spring boot native image (using spring-boot-maven-plugin). The native image does not support the embedded jetty server.

Versions:

Spring Boot: 3.0.3 (spring-boot-starter-parent) JVM: graalvm-ce-java17-windows-amd64-22.3.1

According to this: https://www.graalvm.org/native-image/libraries-and-frameworks/#footnote-1 I think it should work.

A very simple sample project that demonstrates this issue is here: https://github.com/jasclarke24/spring-native-example

Built Image using:

mvn -Pnative spring-boot:build-image

Maven completes successfully, but execution of the run-time image fails to start due to the following:

PS C:\workspace\spring-native-example>  docker run --rm -p 8080:8080 spring-native-example-web:0.0.1-SNAPSHOT

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.3)

2023-02-26T23:51:09.680Z  INFO 1 --- [           main] o.j.s.SpringExampleWebApplication        : Starting AOT-processed SpringExampleWebApplication using Java 17.0.6 with PID 1 (/workspace/org.jasclarke24.springexampleweb.SpringExampleWebApplication started by cnb in /workspace)
2023-02-26T23:51:09.680Z  INFO 1 --- [           main] o.j.s.SpringExampleWebApplication        : No active profile set, falling back to 1 default profile: "default"
2023-02-26T23:51:09.689Z  WARN 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server
2023-02-26T23:51:09.690Z ERROR 1 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Unable to start web server
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:164) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:578) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:6.0.5]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1304) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1293) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        at org.jasclarke24.springexampleweb.SpringExampleWebApplication.main(SpringExampleWebApplication.java:18) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:na]
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.eclipse.jetty.server.session.SessionHandler   
        at org.eclipse.jetty.servlet.ServletContextHandler.newSessionHandler(ServletContextHandler.java:339) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:11.0.13]
        at org.eclipse.jetty.servlet.ServletContextHandler.getSessionHandler(ServletContextHandler.java:432) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:11.0.13]
        at org.eclipse.jetty.servlet.ServletContextHandler.relinkHandlers(ServletContextHandler.java:257) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:11.0.13]
        at org.eclipse.jetty.servlet.ServletContextHandler.<init>(ServletContextHandler.java:180) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:11.0.13]
        at org.eclipse.jetty.webapp.WebAppContext.<init>(WebAppContext.java:301) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:11.0.13]
        at org.eclipse.jetty.webapp.WebAppContext.<init>(WebAppContext.java:228) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:11.0.13]
        at org.springframework.boot.web.embedded.jetty.JettyEmbeddedWebAppContext.<init>(JettyEmbeddedWebAppContext.java:28) ~[na:na]
        at org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory.getWebServer(JettyServletWebServerFactory.java:158) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:183) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:161) ~[org.jasclarke24.springexampleweb.SpringExampleWebApplication:3.0.3]
        ... 8 common frames omitted

Removing the embedded jetty by removing jetty and tomcat exclusion in the pom (thus using Tomcat) the image works as expected:

Replace this

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- Exclude the Tomcat dependency -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- Use Jetty instead -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
    <version>3.0.2</version>
</dependency>

with this for Tomcat Only

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Test Method of Image:

To run the image:

docker run --rm -p 8080:8080 spring-native-example-web:0.0.1-SNAPSHOT

Then hit: http://localhost:8080

You should see: Hello from Spring-Example-Web (uses spring-boot-parent)

Comment From: mhalbritter

Hey, the linked project doesn't start on the JVM, either. You need to downgrade the jakarta.servlet-api to 5.0.0, see this comment. And you should drop the <version>3.0.2</version> from spring-boot-starter-jetty and let Boot manage the version for you.

Comment From: jasclarke24

Those changes did work!

<jakarta-servlet.version>5.0.0</jakarta-servlet.version>

I feel like that should be in some spring documentation to say this is required.

Also the test that Initalzr wrote does not work either. Meaning the context does not load in the test environment, I would think this should work without extra configuration. Very simple context test.

But I can proceed, Thanks for rapid response!!

Comment From: wilkinsona

I feel like that should be in some spring documentation to say this is required.

We agree. That change to the reference documentation is being tracked by the issue that @mhalbritter already linked to. Note that it's already documented in the migration guide.

Also the test that Initalzr wrote does not work either

In what way did it not work?

Comment From: jasclarke24

Running the Initialzr context test results in the following error. Adding -DskipTests=true allows for a successful image to be built, and it runs and executes properly. I updated project (https://github.com/jasclarke24/spring-native-example) to exhibit the error:

 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.3)

2023-02-28T08:26:19.344-05:00  INFO 482935 --- [           main] o.j.s.SpringExampleWebApplicationTests   : Starting SpringExampleWebApplicationTests using Java 17.0.6 with PID 482935 (started by jason in /home/jason/workspaces/spring-native-example)
2023-02-28T08:26:19.345-05:00  INFO 482935 --- [           main] o.j.s.SpringExampleWebApplicationTests   : No active profile set, falling back to 1 default profile: "default"
2023-02-28T08:26:20.208-05:00  INFO 482935 --- [           main] o.j.s.SpringExampleWebApplicationTests   : Started SpringExampleWebApplicationTests in 1.073 seconds (process running for 1.693)
2023-02-28T08:26:20.213-05:00 ERROR 482935 --- [           main] o.s.test.context.TestContextManager      : Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener] to prepare test instance [org.jasclarke24.springexampleweb.SpringExampleWebApplicationTests@1afd72ef]

java.lang.NoClassDefFoundError: jakarta/servlet/ServletConnection
        at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:210) ~[spring-test-6.0.5.jar:6.0.5]
        at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:130) ~[spring-test-6.0.5.jar:6.0.5]
        at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:241) ~[spring-test-6.0.5.jar:6.0.5]
        at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138) ~[spring-test-6.0.5.jar:6.0.5]
        at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$10(ClassBasedTestDescriptor.java:377) ~[junit-jupiter-engine-5.9.2.jar:5.9.2]
        at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:382) ~[junit-jupiter-engine-5.9.2.jar:5.9.2]
        at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$11(ClassBasedTestDescriptor.java:377) ~[junit-jupiter-engine-5.9.2.jar:5.9.2]

Comment From: wilkinsona

That is to be expected as Spring Framework's test infrastructure depends on the Servlet 6.0 API. You may be able to work around this with custom source sets in Gradle or a separate module in Maven but it will be cumbersome. Until Jetty supports Servlet 6.0, you may want to consider using Tomcat instead.