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

  1. Run demo.zip
  2. 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