Intro: Spring Boot Version: 3.4.4 Java Version: 23 Tomcat Servlet Container has been the default & preferred server in my application from the beginning.

Problem: Some auto-configuration classes are defined as static inner classes inside top-level configs (e.g., EmbeddedWebServerFactoryCustomizerAutoConfiguration$UndertowWebServerFactoryCustomizerConfiguration). These are conditionally loaded based on classpath presence (e.g. Undertow.class).

Here's the excerpt for convenience:


import io.undertow.Undertow;


@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
public static class UndertowWebServerFactoryCustomizerConfiguration {

    @Bean
    public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
            ServerProperties serverProperties) {
        return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
    }

    @Bean
    @ConditionalOnThreading(Threading.VIRTUAL)
    UndertowDeploymentInfoCustomizer virtualThreadsUndertowDeploymentInfoCustomizer() {
        return (deploymentInfo) -> deploymentInfo.setExecutor(new VirtualThreadTaskExecutor("undertow-"));
    }

}

Here is the maven dependency that includes io.undertow.Undertow.

<dependency>
    <groupId>io.undertow</groupId>
    <artifactId>undertow-core</artifactId>
    <version>2.3.18.Final</version>
</dependency>

However, since these inner config classes are not registered in AutoConfiguration.imports, they cannot be excluded via spring.autoconfigure.exclude.

This creates a situation where:

  • I need to use Undertow manually in an isolated use case (non-servlet container)
  • I do not want Spring Boot to auto-configure anything related to Undertow, especially not an additional Servlet Container
  • But Spring Boot sees Undertow.class and tries to auto-configure the factory anyway
  • I cannot exclude just the inner config class, and excluding the top-level class disables unrelated embedded server support like Tomcat.

Expected: Support spring.autoconfigure.exclude for inner config classes or provide a mechanism to opt-out of specific servlet container support (like server.exclude=undertow).


I am aware that there is/was a similar ask with issue number #5427 which was declined as it was deemed "not reasonable" and to quote @philwebb :

We can't easily disable inner-configurations. I'd rather take each need on a case-by-case basis and work out why a specific inner-configuration couldn't be used.

I kindly request this requirement to be reconsidered as it has been 9 years over the other issue and I can't really come up with an elegant solution.

Comment From: snicoll

Some auto-configuration classes are defined as static inner classes inside top-level configs

These are not technically auto-configurations but rather a "continuation" of their parent.

I kindly request this requirement to be reconsidered as it has been 9 years over the other issue and I can't really come up with an elegant solution.

I think the reason stated at that time still holds. We don't want to do that as the class that you've referred to is just one piece of the auto-configuration for a technical reason. And working on a case-by-case basis is what we should be doing.

That being said, Why don't you exclude EmbeddedWebServerFactoryCustomizerAutoConfiguration?

Comment From: ismailfarukkaya

@snicoll Thanks for your prompt response. It took me a while to get back as I was looking for the root cause.

In response to your remarks,

I think the reason stated at that time still holds.

Fair enough.

Why don't you exclude EmbeddedWebServerFactoryCustomizerAutoConfiguration?

Because that would mean to give up the TomcatWebServerFactoryCustomizer too and I'd like to find a way without that. This is an upgrade of a multi-module old project from Java 8 with SB 2.2.2-RELEASE to Java 23 with SB 3.4.4, so I do not want to cause problems in any other part of the app.

I can tell from the source code that it configures the Tomcat instance with server.* prefixed app properties.

Will disabling the autoconfiguration require a manual implementation of this procedure?

Can we still define custom WebServerFactoryCustomizers after this configuration is disabled?

Comment From: snicoll

Because that would mean to give up the TomcatWebServerFactoryCustomizer too and I'd like to find a way without that

Why does it matter? You've already stated that:

I need to use Undertow manually in an isolated use case (non-servlet container)

In such use case, it shouldn't matter if those customizers aren't applied.

Comment From: ismailfarukkaya

In such use case, it shouldn't matter if those customizers aren't applied.

But this is a web service and I am using Tomcat as the servlet container, Undertow is just an internal tool being used on the side. Wouldn't disabling the auto config (which houses auto configuration for all types of servlets) get in the way of configuring Tomcat properly?

Comment From: wilkinsona

Unfortunately, as already discussed, we can't support excluded internal parts of auto-configuration as they haven't been designed with that in mind. It may work in some cases, but in others it would lead to failures as, for example, beans that are expected to be there no longer would be because one part of an auto-configuration's internals has been excluded.

I believe that the two Undertow-related beans defined by UndertowWebServerFactoryCustomizerConfiguration will be benign. If this is correct, I think your best option for now is to just tolerate their presence. If you really want to remove them, a BeanFactoryPostProcessor is one way to do so.

This use case will be better-served in Spring Boot 4.0 as part of our restructuring work. We will have (many) more modules with each containing auto-configuration for a specific technology. Of relevance to this specific case are the separate spring-boot-tomcat and spring-boot-undertow modules. With only the former as a dependency, you will get auto-configuration for Tomcat but not for Undertow.

Comment From: ismailfarukkaya

Thanks for your comment @wilkinsona , really exciting news on the Spring 4.0 work, I am aware that it is planned to be released later this year and hopefully it's not going to be too much work after this 2.x to 3.x undertaking I have been working on. :)

I believe that the two Undertow-related beans defined by UndertowWebServerFactoryCustomizerConfiguration will be benign. If this is correct, I think your best option for now is to just tolerate their presence.

It is totally fine to have an additional customizer bean set up on the side, my worry is if I were to remove the entire auto config, will my web application based on Tomcat be impacted in any way?

Also, I haven't been very transparent in my question, the reason why I am looking to exclude this single customizer is that causes the application to crash during startup:

Since its registration is triggered by this single class "io.undertow.Undertow" per the @Conditionals on the bean; my understanding is that the framework assumes that everything else is also properly configured. However, the artifact/dependency that provides the aforementioned class, "undertow-core", is not provided by the official undertow-starter, so the configuration is incomplete for a proper Undertow servlet initialization. It is also apparent in the auto-configuration class that the class io.undertow.servlet.api.DeploymentInfo is missing in the classpath (as expected). I found out that this class is provided by the undertow-servlet artifact, which would have been brought in just fine, had I used the undertow-starter in my app.

Because of this inadvertent fact, the application fails to start with below error:


java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration.characterEncodingFilter
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:60) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:99) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:184) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:145) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:121) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:430) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:290) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:349) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:118) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) ~[spring-context-6.2.5.jar:6.2.5]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.4.jar:3.4.4]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) [spring-boot-3.4.4.jar:3.4.4]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) [spring-boot-3.4.4.jar:3.4.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) [spring-boot-3.4.4.jar:3.4.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) [spring-boot-3.4.4.jar:3.4.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) [spring-boot-3.4.4.jar:3.4.4]
    at com.us.myapp.service.WebService.main(WebService.java:15) [classes/:?]
Caused by: java.lang.IllegalStateException: Failed to introspect Class [org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$UndertowWebServerFactoryCustomizerConfiguration] from ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader@5a07e868]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:483) ~[spring-core-6.2.5.jar:6.2.5]
    at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:360) ~[spring-core-6.2.5.jar:6.2.5]
    at org.springframework.util.ReflectionUtils.getUniqueDeclaredMethods(ReflectionUtils.java:417) ~[spring-core-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.lambda$getTypeForFactoryMethod$1(AbstractAutowireCapableBeanFactory.java:751) ~[spring-beans-6.2.5.jar:6.2.5]
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1713) ~[?:?]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryMethod(AbstractAutowireCapableBeanFactory.java:750) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineTargetType(AbstractAutowireCapableBeanFactory.java:683) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:654) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1704) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:618) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:590) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.collectBeanDefinitionsForType(OnBeanCondition.java:332) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanDefinitionsForType(OnBeanCondition.java:324) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanDefinitionsForType(OnBeanCondition.java:314) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchingBeans(OnBeanCondition.java:213) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.evaluateConditionalOnMissingBean(OnBeanCondition.java:196) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchOutcome(OnBeanCondition.java:143) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    ... 17 more
Caused by: java.lang.NoClassDefFoundError: io/undertow/servlet/api/DeploymentInfo
    at java.base/java.lang.Class.getDeclaredMethods0(Native Method) ~[?:?]
    at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3650) ~[?:?]
    at java.base/java.lang.Class.getDeclaredMethods(Class.java:2735) ~[?:?]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:465) ~[spring-core-6.2.5.jar:6.2.5]
    at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:360) ~[spring-core-6.2.5.jar:6.2.5]
    at org.springframework.util.ReflectionUtils.getUniqueDeclaredMethods(ReflectionUtils.java:417) ~[spring-core-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.lambda$getTypeForFactoryMethod$1(AbstractAutowireCapableBeanFactory.java:751) ~[spring-beans-6.2.5.jar:6.2.5]
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1713) ~[?:?]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryMethod(AbstractAutowireCapableBeanFactory.java:750) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineTargetType(AbstractAutowireCapableBeanFactory.java:683) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:654) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1704) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:618) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:590) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.collectBeanDefinitionsForType(OnBeanCondition.java:332) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanDefinitionsForType(OnBeanCondition.java:324) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanDefinitionsForType(OnBeanCondition.java:314) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchingBeans(OnBeanCondition.java:213) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.evaluateConditionalOnMissingBean(OnBeanCondition.java:196) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchOutcome(OnBeanCondition.java:143) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    ... 17 more
Caused by: java.lang.ClassNotFoundException: io.undertow.servlet.api.DeploymentInfo
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[?:?]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[?:?]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528) ~[?:?]
    at java.base/java.lang.Class.getDeclaredMethods0(Native Method) ~[?:?]
    at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3650) ~[?:?]
    at java.base/java.lang.Class.getDeclaredMethods(Class.java:2735) ~[?:?]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:465) ~[spring-core-6.2.5.jar:6.2.5]
    at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:360) ~[spring-core-6.2.5.jar:6.2.5]
    at org.springframework.util.ReflectionUtils.getUniqueDeclaredMethods(ReflectionUtils.java:417) ~[spring-core-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.lambda$getTypeForFactoryMethod$1(AbstractAutowireCapableBeanFactory.java:751) ~[spring-beans-6.2.5.jar:6.2.5]
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1713) ~[?:?]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryMethod(AbstractAutowireCapableBeanFactory.java:750) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineTargetType(AbstractAutowireCapableBeanFactory.java:683) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:654) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1704) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:618) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:590) ~[spring-beans-6.2.5.jar:6.2.5]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.collectBeanDefinitionsForType(OnBeanCondition.java:332) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanDefinitionsForType(OnBeanCondition.java:324) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getBeanDefinitionsForType(OnBeanCondition.java:314) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchingBeans(OnBeanCondition.java:213) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.evaluateConditionalOnMissingBean(OnBeanCondition.java:196) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchOutcome(OnBeanCondition.java:143) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47) ~[spring-boot-autoconfigure-3.4.4.jar:3.4.4]

Then the only step made sense to me was to disable this inner-configuration for Undertow in the EmbeddedWebServerFactoryCustomizerAutoConfiguration but keep the Tomcat one to prevent any potential impact to the other parts of the application.

Thanks again for your ongoing help.

Comment From: wilkinsona

That's a bug. The auto-configuration should have backed off in the absence of undertow-servlet. I have opened https://github.com/spring-projects/spring-boot/issues/46178.

In the future, please share the problem that you're facing from the outset.