Issue
I have a very specific Tomcat configuration that I don't want to change and I am looking for a solution for this specific configuration.
I have declared multiple contexts (<Context>
) for deployment inside the conf/server.xml file in Tomcat, something I know is not recommended. My problem is that if one of those deployments fail, the whole Tomcat process stops which terminates the rest of the applications that have successfully started. Is this behavior expected? Is there an attribute or a way to prevent this and emulate the behavior of distinct context declarations akin to the usage of context.xml
file?
The error I'm getting is the following:
ConfigServletWebServerApplicationContext : Exception encountered during context initialization
with this stacktrace (for Tomcat version 8.5.24):
SEVERE [Catalina-startStop-1] org.apache.catalina.core.ContainerBase.startInternal A child container failed during start
java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/auth-service]]
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:939)
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:872)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1419)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/auth-service]]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:167)
... 6 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'authenticationController': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'app.client-html.context' in value "${app.client-html.context}"
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:405)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1415)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:608)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:531)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:925)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:588)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:326)
at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.run(SpringBootServletInitializer.java:173)
at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:153)
at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:95)
at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:174)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5196)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
... 6 more
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'app.client-html.context' in value "${app.client-html.context}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:178)
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124)
at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239)
at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210)
at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175)
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:931)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1308)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1287)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
... 27 more
Sample configuration:
<Host name="localhost" unpackWARs="true" autoDeploy="true">
<Context docBase="../../app-that-works" path="/itworks"/>
<Context docBase="../../app-that-fails" path="/itfails"/>
</Host>
IMPORTANT: The failing application is a spring-boot application that is packaged as a .war file and running inside a vanilla tomcat, not the embedded tomcat way. This is important because it seems that these applications have this behavior...
DISCLAIMER: I am referring to vanilla Tomcat, not spring boot or embedded tomcat etc. I am not looking for such solutions, simply a solution to this specific problem.
Solution
Basically:
- an exception during the
start
phase (cf. LifeCycle) of any component configured inserver.xml
causes the server to exit. However some exceptions (like a failedServletContextListener
) don't propagate. - an error of a component added during runtime, does not cause the server to exit.
If you don't want the whole server to stop, when a context fails to start up, use auto-deployment: for each context defined in server.xml
create a file $CATALINA_BASE/conf/<engine_name>/<host_name>/<context_path>.xml
with content:
<Context docBase="/path/to/application" />
In your case you need to create $CATALINA_BASE/conf/Catalina/localhost/itworks.xml
and $CATALINA_BASE/conf/Catalina/localhost/itfails.xml
. See Naming for Tomcat's naming convention for XML descriptors.
Edit: Your stack trace suggests that we might be dealing with a Tomcat or Spring bug:
On one side the
StandardContext
expects aServletContainerInitializer
to throw onlyServletException
s (cf. source code) and let's all unchecked exceptions through. Remark that at the same time all exceptions fromServletContextListener
s are caught and just logged (cf. source code).On the other hand Spring's
SpringServletContainerInitializer
should probably wrap all unchecked exceptions into aServletException
.
What happens in your case is that an unchecked exception makes it all the way through to the StandardContext
and is rethrown as a LifecycleException
stopping the server. Anyway, setting:
<Context throwOnFailure="false" ... />
should stop Tomcat from propagating the exception.
Answered By - Piotr P. Karwasz