An unpacked war won't start when there is an embedded tomcat on the JVM classpath.
An MVCE can be found here: https://github.com/larsgrefer/tomcat-exception-demo
Just execute ./gradlew triggerBug
The actual exception can be found here: https://github.com/larsgrefer/tomcat-exception-demo/runs/488549568?#step:5:50
java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Error starting the loader
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122) ~[na:na]
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191) ~[na:na]
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:916) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:841) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374) ~[tomcat-embed-core.jar:9.0.31]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core.jar:9.0.31]
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:421) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.startup.Tomcat.start(Tomcat.java:467) ~[tomcat-embed-core.jar:9.0.31]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:107) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:88) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:438) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:191) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:180) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:153) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:544) ~[spring-context-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) ~[spring-boot-2.2.4.RELEASE.jar:2.2.4.RELEASE]
at de.larsgrefer.tomcatexceptiondemo.TomcatExceptionDemoApplication.main(TomcatExceptionDemoApplication.java:10) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) ~[exploded/:na]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) ~[exploded/:na]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:51) ~[exploded/:na]
at org.springframework.boot.loader.WarLauncher.main(WarLauncher.java:58) ~[exploded/:na]
Caused by: org.apache.catalina.LifecycleException: Error starting the loader
at org.apache.catalina.loader.WebappLoader.startInternal(WebappLoader.java:414) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4997) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374) ~[tomcat-embed-core.jar:9.0.31]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core.jar:9.0.31]
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909) ~[tomcat-embed-core.jar:9.0.31]
... 37 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581) ~[na:na]
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
at java.base/java.lang.Class.forName(Class.java:315) ~[na:na]
at org.apache.catalina.loader.WebappLoader.createClassLoader(WebappLoader.java:504) ~[tomcat-embed-core.jar:9.0.31]
at org.apache.catalina.loader.WebappLoader.startInternal(WebappLoader.java:390) ~[tomcat-embed-core.jar:9.0.31]
... 46 common frames omitted
Context
We package our application in the war format, because it contains JSP pages, which won't work with other packaging options.
In the start script for the application we include an additional folder in the classpath which allows our customer to supply additional jars to the application which aren't part of our distribution. This is mainly used for jdbc drivers and commercial libraries. Today one of our customers tried to update the tomcat version by placing newer tomcat-jars there which resulted in the above error.
Comment From: wilkinsona
Thanks for the sample.
The presence of the Tomcat classes on the classpath of the system class loader will result in them being loaded in favour of those on the classpath of Boot’s LaunchedURLClassloader
. These Tomcat classes will then be unable to load anything only available to the LaunchedURLClassLoader
. This results in the ClassNotFoundException
shown above.
I don’t think there’s anything we can do about this as it is standard JVM behaviour with a parent-first ClassLoader
. Switching to parent-last may solve this specific problem but it would potentially introduce a whole host of other problems.
You may want to consider adding the jars in your extra directory to the classpath of the LaunchedURLClassLoader
rather than the system class loader. I think you could probably achieve this with a custom subclass of WarLauncher
Comment From: larsgrefer
That makes sense. Thanks for the answer.
I'll see if I can get everything in the same classloader.
Comment From: larsgrefer
Just in case someone else with the same problem comes here. This is the solution I found:
Instead of running
java -classpath "/opt/extra_libs/*:." org.springframework.boot.loader.WarLauncher
We now use this:
java -classpath "/opt/extra_libs/*:./WEB-INF/classes:./WEB-INF/lib-provided/*:./WEB-INF/lib/*" com.example.app.Main
Doing so, both the Tomcat (from /opt/extra_libs) and Spring Boot (from ./WEB-INF/lib) are in the same classloader, so everthing works as expected.
This solution was inspired by https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/html/deployment.html#containers-deployment