We can't call @Validated bean's method on application shutdown because defaultValidator bean is destroyed first even though we are referring it.

Sample Application

  • MyBean with @Validated depends on defaultValidator implicitly through MethodValidationInterceptor
  • MyService depends on MyBean
  • MyService takes a while to destroy and it calls MyBean at last
  • But the call fails due to BeanCreationNotAllowedException
package com.example.demo;

import javax.annotation.PreDestroy;

import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Service
    static class MyService {
        private final MyBean myBean;

        MyService(MyBean myBean) {
            this.myBean = myBean;
        }

        @PreDestroy
        void destroy() {
            System.out.println("MyService.destroy");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            try {
                myBean.method();
            } catch (BeanCreationNotAllowedException ex) {
                // This is thrown because defaultValidator has already been closed.
                ex.printStackTrace();
            }
        }

    }

    @Component
    @Validated
    // This is a workaround for this issue:
    // @DependsOn("defaultValidator")
    static class MyBean {

        void method() {
            System.out.println("MyBean.method");
        }

        @PreDestroy
        void destroy() {
            System.out.println("MyBean.destroy");
        }
    }

}

See the following link for the complete demo app code: https://github.com/uphy/20231010_BeanCreationNotAllowedExceptionDemo

Workaround

  • Add @DependsOn("defaultValidator") to MyBean
  • Add @ConfigurationProperties annotation to MyBean (ref https://github.com/spring-projects/spring-boot/commit/f60f3cb38eeb40f6863d86b8ef91fa395c513a82)
  • Use MethodValidationExcludeFilter to exclude method validation from MyBean

Environment

  • Spring Boot 2.7.6
  • Java 11

Comment From: vishalsingh2972

@uphy @sbrannen can you assign this to me if it's still open ?

Comment From: uphy

@vishalsingh2972 Thank you! I am not authorized to assign, so I will wait for @sbrannen's response.

Comment From: snicoll

@vishalsingh2972 thanks for the offer but we haven't triaged the issue yet so nobody can really work on it really. Except if you've found what the issue was?

Comment From: snicoll

@uphy I can't reproduce the issue that you have described. Both with framework 5.3 and 6.x, the shutdown is initiated with the sleep there and it ends up with:

2023-10-05 16:22:08.799  INFO 22703 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.558 seconds (JVM running for 0.744)
MyService.destroy
MyBean.method
MyBean.destroy

Going forward, please share a sample that we can run rather than code in text that we'll have to copy/paste anyway in a project to be able to investigate. You can do that by attaching a zip to the issue or sharing a link to a GitHub repository with the code.

I am going to close this but we can reopen if you manage to reproduce. Please provide more instructions in that case.

Comment From: uphy

@snicoll Thank you for checking. I could reproduce the issue again with latest environment.

See the following link for the complete demo project: https://github.com/uphy/20231010_BeanCreationNotAllowedExceptionDemo

Could you reopen this issue?

Comment From: snicoll

Thanks, I've reproduced it now. This is similar to #22526

Comment From: jhoeller

As of 6.2, we leniently tolerate late retrieval of existing instances during destroySingletons(), making late bean interactions possible during destroy method invocations even for retrievals from an on-demand supplier or async worker etc. After all, such late interactions were already possible for bean instances that got stored directly, and late retrieval was possible for beans without destroy methods (even if they were part of a depends-on arrangement in the other direction), so we may also tolerate interactions when retrieved on demand now: typically for validators and transaction managers, late-bound and/or potentially late-qualified.