DelegatingWebFluxConfiguration can trigger BeanCurrentlyInCreationException under a few circumstances.

ReactiveAdapterRegistry

The first problem is that it is difficult to provide a HandlerMethodArgumentResolver that needs the ReactiveAdapterRegistry because this can cause a cycle depending on the order the beans are instantiated. For example, this test produces a cycle:

  1. authenticationPrincipalArgumentResolver is requested
  2. It triggers the creation of ReactiveAdapterRegistry which is a dependant bean
  3. In order to create ReactiveAdapterRegistry, DelegatingWebFluxConfiguration needs to be created which requires all WebFluxConfigurer instances to be wired into it.
  4. This tries to retrieve all WebFluxConfigurer instances. One of which is authenticationPrincipalArgumentResolverConfigurer which provides our authenticationPrincipalArgumentResolver bean as an argument resolver.
  5. This requests authenticationPrincipalArgumentResolver which is a cycle

One option to make this less likely to happen is to define authenticationPrincipalArgumentResolverConfigurer as:

@Bean
public WebFluxConfigurer authenticationPrincipalArgumentResolverConfigurer(
        ObjectProvider<AuthenticationPrincipalArgumentResolver> authenticationPrincipalArgumentResolver) {
    return new WebFluxConfigurer() {
        @Override
        public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
            configurer.addCustomResolver(authenticationPrincipalArgumentResolver.getObject());
        }
    };
}

This delays the lookup of AuthenticationPrincipalArgumentResolver. However, this has a few drawbacks.

  1. The first drawback is that while it reduces the likelihood of an error, it doesn't remove the cycle between ArgumentResolverConfig and WebFluxConfigurationSupport.
  2. Second, it requires every AuthenticationPrincipalArgumentResolver configuration needing ReactiveAdapterRegistry (I'd argue that is pretty much every AuthenticationPrincipalArgumentResolver) to have the burden of fixing the issue rather than decoupling the beans (HandlerMethodArgumentResolver and ReactiveAdapterRegistry) that are likely dependant on one another).

We could move the delay inside DelegatingWebFluxConfiguration by autowiring ObjectProvider<WebFluxConfigurer> instead. However, this only fixes the second problem and leaves the cycle present.

Another option is to make the definition of ReactiveAdapterRegistry a static method. This would ensure that DelegatingWebFluxConfiguration does not need to be instantiated. Yet another option would be to move ReactiveAdapterRegistry to another configuration, but this could break other applications.

ResourceUrlProvider Can Cause Early Initialization of DelegatingWebFluxConfiguration

ResourceUrlProvider implements ApplicationListener which can trigger early initialization of DelegatingWebFluxConfiguration. As soon as an ApplicationEvent is published, the ApplicationListeners need initialized. Since ResourceUrlProvider is defined by DelegatingWebFluxConfiguration it initializes DelegatingWebFluxConfiguration and all of its dependant beans very early on.

This is what is why the originally reported issue would happen in Boot 2.3.0 and not in previous versions. Specifically the changes for https://github.com/spring-projects/spring-boot/issues/21325 moved the publishing of the ReactiveWebServerInitializedEvent from finishRefresh to SmartLifecycle's start method. This changed the order that DelegatingWebFluxConfiguration was created and triggered the bean cycle to happen.

One solution is to move the ResourceUrlProvider bean definition to a static method.

Related Issues

  • https://github.com/spring-projects/spring-boot/issues/21580
  • https://github.com/spring-projects/spring-security/issues/8596

Comment From: THS-on

We currently run into this issue. Is there a workaround that we could apply?

It seems that it's really inconsistent when it happens. gradle bootRun always works but building a jar and starting it works on one machine and fails on on others.

Gradle config versions

id("org.springframework.boot") version "2.3.3.RELEASE"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
kotlin("jvm") version "1.3.72"
kotlin("plugin.spring") version "1.3.72"