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.