org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'resourceHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'resourceHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No ServletContext set
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.13.jar:5.3.13]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.13.jar:5.3.13]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.6.1.jar:2.6.1]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[spring-boot-2.6.1.jar:2.6.1]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412) ~[spring-boot-2.6.1.jar:2.6.1]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:302) ~[spring-boot-2.6.1.jar:2.6.1]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-2.6.1.jar:2.6.1]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290) ~[spring-boot-2.6.1.jar:2.6.1]
        at com.example.demo.DemoApplication.main(DemoApplication.java:10) ~[main/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'resourceHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No ServletContext set
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.13.jar:5.3.13]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.13.jar:5.3.13]
        ... 19 common frames omitted
Caused by: java.lang.IllegalStateException: No ServletContext set
        at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.13.jar:5.3.13]
        at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.resourceHandlerMapping(WebMvcConfigurationSupport.java:591) ~[spring-webmvc-5.3.13.jar:5.3.13]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.13.jar:5.3.13]
        ... 20 common frames omitted

demo.zip

The attached project demonstrates this problem. It seems to require a very specific combination of dependencies (actuator, data rest and sleuth) and for the presence of a health indicator which takes a RestTemplateBuilder.

Adding the following annotation to the HealthIndicator seems to resolve the problem.

@DependsOn("healthContributorRegistry")

This took ages to track down and was detected during an upgrade from Spring Boot 2.5.7 to 2.6.1.

Comment From: wilkinsona

Thanks very much for the sample, @smcgregor83. I can well imagine that this took a long time to track down.

Without your workaround in place, Spring Boot's EnableWebMvcConfiguration (a WebMvcConfigurationSupport sub-class) is created before the ServletContext is available. This means that when it's post-processed by WebApplicationContextServletContextAwareProcessor, no call to setServletContext is made. This is what causes the subsequent call to resourceHandlerMapping to fail. The chain of dependencies that leads to this early initialization of EnableWebMvcConfiguration is sizeable. We'll need to spend some time digging through it to identify the cause.

Comment From: wilkinsona

Here's the chain of beans that are created:

tomcatServletWebServerFactory
traceTomcatWebServerFactoryCustomizer
braveHttpServerHandler
httpTracing
sleuthSkipPatternProvider
    WebEndpointDiscoverer getEndpoints()
healthEndpoint
healthContributorRegistry
    getBeansOfType(HealthContributor.class)
testHealthIndicator
restTemplateBuilder
restTemplateBuilderConfigurer
messageConverters
jacksonHttpMessageConverter (Spring Data REST)
linkCollector
selfLinkProvider
mvcConversionService
EnableWebMvcConfiguration

The problem begins almost immediately as Sleuth's traceTomcatWebServerFactoryCustomizer triggers the creation of a large number of beans via sleuthSkipPatternProvider which triggers actuator endpoint discovery. This appears to have caused problems in the past. As far as I can tell, this will prevent any endpoint bean or bean that is required by an endpoint from having the ServletContext injected when they are ServletContextAware. Sleuth could perhaps fix this by accessing the HttpServerHandler lazily.

I've opened https://github.com/spring-cloud/spring-cloud-sleuth/issues/2074 so that the Sleuth team can investigate.

Many thanks again for the sample, @smcgregor83, it was a huge help in quickly identify the cause of the problem you're facing.

Comment From: smcgregor83

Thanks @wilkinsona for the quick response. Something to highlight with the workaround I mentioned is that this effectively disables the health indicator as it won't make it into the registry. Currently trying to find some other workaround.

Comment From: wilkinsona

As long as you don't use it in the health indicator's constructor, marking the injection of RestTemplateBuilder as @Lazy allows your sample application to start:

package com.example.demo;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
public class TestHealthIndicator implements HealthIndicator {

    private final RestTemplateBuilder builder;

    public TestHealthIndicator(@Lazy RestTemplateBuilder builder) {
        this.builder = builder;
    }

    @Override
    public Health health() {
        return Health.up().build();
    }
}

This results in builder being proxied. An alternative that just defers the bean creation without a proxy would be to use ObjectFactory:

package com.example.demo;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Component;

@Component
public class TestHealthIndicator implements HealthIndicator {

    private final ObjectFactory<RestTemplateBuilder> builder;

    public TestHealthIndicator(ObjectFactory<RestTemplateBuilder> builder) {
        this.builder = builder;
    }

    @Override
    public Health health() {
        return Health.up().build();
    }
}

Again, this will only work if you don't call the factory's getObject() method in the indicator's constructor. You could use Framework's SingletonSupplier to take care of deferring the use of the RestTemplateBuilder bean but still only building the rest template once:

package com.example.demo;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Component;
import org.springframework.util.function.SingletonSupplier;
import org.springframework.web.client.RestTemplate;

@Component
public class TestHealthIndicator implements HealthIndicator {

    private final SingletonSupplier<RestTemplate> restTemplate;

    public TestHealthIndicator(ObjectFactory<RestTemplateBuilder> builderFactory) {
        this.restTemplate = new SingletonSupplier<>(() -> builderFactory.getObject().build(), null);
    }

    @Override
    public Health health() {
        RestTemplate rest = this.restTemplate.get();
        return Health.up().build();
    }
}

Comment From: smcgregor83

Thanks very much. I've done something along those lines using ObjectProvider.

Our real service has HealthIndicator implementations which have a @Component injected, this component is our client which in turn uses RestTemplateBuilder within its own constructor to build a RestTemplate.

If I use ObjectProvider to inject the client @Component into the HealthIndicator, everything then works as expected.

Thanks again for the help on this one. Took a good two days to get to the bottom of.