When using Spring boot with actuator, jersey configured with filter type and component extends from ResourceConfig, start failed

It's ok with servlet type or without actuator deps

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jerseyWebEndpointsResourcesRegistrar' defined in class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration$JerseyWebEndpointsResourcesRegistrar]: Factory method 'jerseyWebEndpointsResourcesRegistrar' threw exception; nested exception is java.lang.IllegalStateException: The resource configuration is not modifiable in this context.
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1336) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1179) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:571) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:531) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$161/1765795529.getObject(Unknown Source) ~[na:na]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:923) ~[spring-context-5.3.3.jar:5.3.3]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:588) ~[spring-context-5.3.3.jar:5.3.3]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144) ~[spring-boot-2.4.2.jar:2.4.2]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767) [spring-boot-2.4.2.jar:2.4.2]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) [spring-boot-2.4.2.jar:2.4.2]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426) [spring-boot-2.4.2.jar:2.4.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:326) [spring-boot-2.4.2.jar:2.4.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1311) [spring-boot-2.4.2.jar:2.4.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300) [spring-boot-2.4.2.jar:2.4.2]
        at com.example.demo.DemoApplication.main(DemoApplication.java:10) [main/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration$JerseyWebEndpointsResourcesRegistrar]: Factory method 'jerseyWebEndpointsResourcesRegistrar' threw exception; nested exception is java.lang.IllegalStateException: The resource configuration is not modifiable in this context.
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.3.jar:5.3.3]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.3.jar:5.3.3]
        ... 21 common frames omitted
Caused by: java.lang.IllegalStateException: The resource configuration is not modifiable in this context.
        at org.glassfish.jersey.server.ResourceConfig$ImmutableState.registerResources(ResourceConfig.java:208) ~[jersey-server-2.32.jar:na]
        at org.glassfish.jersey.server.ResourceConfig.registerResources(ResourceConfig.java:576) ~[jersey-server-2.32.jar:na]
        at org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration$JerseyWebEndpointsResourcesRegistrar.register(JerseyWebEndpointManagementContextConfiguration.java:141) ~[spring-boot-actuator-autoconfigure-2.4.2.jar:2.4.2]
        at org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration$JerseyWebEndpointsResourcesRegistrar.register(JerseyWebEndpointManagementContextConfiguration.java:128) ~[spring-boot-actuator-autoconfigure-2.4.2.jar:2.4.2]
        at org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration$JerseyWebEndpointsResourcesRegistrar.<init>(JerseyWebEndpointManagementContextConfiguration.java:113) ~[spring-boot-actuator-autoconfigure-2.4.2.jar:2.4.2]
        at org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration.jerseyWebEndpointsResourcesRegistrar(JerseyWebEndpointManagementContextConfiguration.java:76) ~[spring-boot-actuator-autoconfigure-2.4.2.jar:2.4.2]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_25]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_25]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_25]
        at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_25]
        at org.springframework.beans.fac

To reproduce :

  • https://start.spring.io/#!type=gradle-project&language=java&platformVersion=2.4.2.RELEASE&packaging=jar&jvmVersion=15&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=lombok,configuration-processor,devtools,jersey,actuator
  • application.properties

spring.jersey.type=filter

  • define JerseyConfig
package com.example.demo;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

@Component
public class JerseyConfig extends ResourceConfig {

}

** your app must not have spring-web-mvc deps include on runtime" like spring-cloud-starter-netflix-hystrix-dashboard or org.springframework.boot:spring-boot-starter-web or bean "org.springframework.web.servlet.DispatcherServlet"

seems to be ko from long time ago, try with 2.3.x and 2.2.x with same bug

https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-jersey

Comment From: wilkinsona

The problem's occurring because filters are initialised more eagerly than servlets. When the initialization happens, Jersey's state is made immutable. If this happens before a call to registerResources is made, the call will fail. The problem does not occur with a servlet even when it's configured to be loaded on startup as this is still late enough.

@Fyro-Ing You can work around the problem by configuring Jersey's filter to be initialised lazily using Boot's DelegatingFilterProxyRegistrationBean. You can do so by adding a couple of beans to your application. The following beans mimic Boot's auto-configuration but also cause the filter to be initialised lazily:

@Bean
public DelegatingFilterProxyRegistrationBean jerseyFilterRegistration(JerseyApplicationPath applicationPath,
        ResourceConfig resourceConfig, JerseyProperties jersey) {
    DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean("jerseyFilter") {

        @Override
        public DelegatingFilterProxy getFilter() {
            DelegatingFilterProxy filter = super.getFilter();
            filter.setTargetFilterLifecycle(true);
            return filter;
        }

    };
    registration.setUrlPatterns(Collections.singletonList(applicationPath.getUrlMapping()));
    registration.setOrder(jersey.getFilter().getOrder());
    registration.addInitParameter(ServletProperties.FILTER_CONTEXT_PATH, stripPattern(applicationPath.getPath()));
    addInitParameters(jersey.getInit(), registration);
    registration.setName("jerseyFilter");
    registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
    return registration;
}

@Bean
public ServletContainer jerseyFilter(ResourceConfig resourceConfig) {
    return new ServletContainer(resourceConfig);
}

private String stripPattern(String path) {
    if (path.endsWith("/*")) {
        path = path.substring(0, path.lastIndexOf("/*"));
    }
    return path;
}

private void addInitParameters(Map<String, String> initParameters, DynamicRegistrationBean<?> registration) {
    initParameters.forEach(registration::addInitParameter);
}

Comment From: wilkinsona

The last version where this worked appears to be 2.1.8.RELEASE as it breaks in 2.1.9.RELEASE. I think that's due to the changes made for https://github.com/spring-projects/spring-boot/issues/17801 which stopped using a ResourceConfigCustomizer to register the endpoints. Using a ResourceConfigCustomizer ensured that the ResourceConfig was configured before it was locked, irrespective of the differing lifecycles of servlets and filters. An alternative to the delegating filter proxy approach shown above may be to rework the fix for #17801 somehow so that we can go back to using a ResourceConfigCustomizer again.

Comment From: philwebb

We'll update our auto-config to use the DelegatingFilterProxyRegistrationBean.

Comment From: wilkinsona

A downside of the delegating filter proxy approach that we didn't consider is that many Jersey-related errors won't be discovered until the first request is received. That doesn't feel great from a usability perspective. I think it would be good if we can rework the fix for #17801 to use a ResourceConfigCustomizer.

Comment From: wilkinsona

Here's a fix that avoids the use of a delegating filter proxy. Instead, it reworks the fix for #17801 by introducing a ManagementContextResourceConfigCustomizer that can be used to customize the ResourceConfig that the Actuator is using without also applying the main application's customisers to it. Flagging for team attention to see if we're comfortable with the new public API that this requires.