Skip to content

Separate stop and destroy in UndertowWebServer to avoid premature ServletContext destruction #47141

@hdfg159

Description

@hdfg159

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 the ServletContext.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    for: team-meetingAn issue we'd like to discuss as a team to make progressstatus: waiting-for-triageAn issue we've not yet triaged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions