Background
In the embedded Undertow container, the method
org.springframework.boot.web.embedded.undertow.UndertowWebServer#stop
invokes
io.undertow.servlet.core.DeploymentManagerImpl#undeploy
,
which destroys theServletContext.
Problem
After ServletContext is destroyed, io.undertow.servlet.api.DeploymentInfo
is no longer available.
When using Log4j2, @PreDestroy
methods in Spring Beans try to log messages, but Log4j2 fails because it cannot access Spring-related properties.
This leads to exceptions during the shutdown phase.
Steps to Reproduce
- Run demo.zip
-
Or Steps to Reproduce
-
Run a Spring Boot application with embedded Undertow and Log4j2.
-
Add a Spring Bean with an
@PreDestroy
method that logs something. -
Stop Spring Boot
-
Observe that Log4j2 throws exceptions because Spring properties are no longer accessible.
-
Expected Behavior
stop()
should not destroy the ServletContext immediately, so that logging in @PreDestroy
methods can safely access Spring properties.
Logging during shutdown should work without exceptions.
Actual Behavior
stop()
triggers DeploymentManagerImpl#undeploy, destroying the ServletContext.
Log4j2 cannot retrieve Spring properties and throws exceptions during @PreDestroy logging.
2025-09-10T09:03:55.040170800Z Log4j2-TF-1-AsyncLogger[AsyncContext@36baf30c]-1 ERROR Resolver failed to lookup spring:spring.profiles.active java.lang.IllegalStateException: UT015023: This Context has been already destroyed
at io.undertow.servlet.spec.ServletContextImpl.getDeploymentInfo(ServletContextImpl.java:210)
at io.undertow.servlet.spec.ServletContextImpl.getInitParameter(ServletContextImpl.java:429)
at org.springframework.web.context.support.ServletContextPropertySource.getProperty(ServletContextPropertySource.java:47)
at org.springframework.web.context.support.ServletContextPropertySource.getProperty(ServletContextPropertySource.java:33)
at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:85)
at org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver$DefaultResolver.getProperty(ConfigurationPropertySourcesPropertyResolver.java:133)
at org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue(ConfigurationPropertySourcesPropertyResolver.java:107)
at org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.getProperty(ConfigurationPropertySourcesPropertyResolver.java:76)
at org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.getProperty(ConfigurationPropertySourcesPropertyResolver.java:62)
at org.springframework.core.env.AbstractEnvironment.getProperty(AbstractEnvironment.java:555)
at org.apache.logging.log4j.spring.boot.SpringLookup.lookup(SpringLookup.java:106)
at org.apache.logging.log4j.spring.boot.SpringLookup.lookup(SpringLookup.java:113)
at org.apache.logging.log4j.core.lookup.StrLookup.evaluate(StrLookup.java:117)
at org.apache.logging.log4j.core.lookup.Interpolator.evaluate(Interpolator.java:197)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.resolveVariable(StrSubstitutor.java:1227)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:1138)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:999)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.replace(StrSubstitutor.java:513)
at org.apache.logging.log4j.core.pattern.LiteralPatternConverter.format(LiteralPatternConverter.java:63)
at org.apache.logging.log4j.core.pattern.PatternFormatter.format(PatternFormatter.java:44)
at org.apache.logging.log4j.core.pattern.StyleConverter.format(StyleConverter.java:117)
at org.apache.logging.log4j.core.layout.PatternLayout$NoFormatPatternSerializer.toSerializable(PatternLayout.java:355)
at org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:252)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:238)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:58)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:227)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:220)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:211)
at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:160)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:133)
at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:124)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:88)
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:714)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:672)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:648)
at org.apache.logging.log4j.core.config.LoggerConfig.logParent(LoggerConfig.java:705)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:674)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:648)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:636)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:108)
at org.apache.logging.log4j.core.async.AsyncLogger.actualAsyncLog(AsyncLogger.java:577)
at org.apache.logging.log4j.core.async.RingBufferLogEvent.execute(RingBufferLogEvent.java:172)
at org.apache.logging.log4j.core.async.RingBufferLogEventHandler4.onEvent(RingBufferLogEventHandler4.java:54)
at org.apache.logging.log4j.core.async.RingBufferLogEventHandler4.onEvent(RingBufferLogEventHandler4.java:31)
at com.lmax.disruptor.BatchEventProcessor.processEvents(BatchEventProcessor.java:167)
at com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:122)
at java.base/java.lang.Thread.run(Thread.java:1583)
Proposal
Consider separating stop and destroy in UndertowWebServer.
stop()
should only stop accepting new requests, while destroy()
should handle resource cleanup and ServletContext disposal.
#47140