The Spring documentation states the following:
"An application is considered ready as soon as application and command-line runners have been called, see Spring Boot application lifecycle and related Application Events." (https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.application-availability.readiness)
It also states the following:
"Tasks expected to run during startup should be executed by CommandLineRunner and ApplicationRunner components instead of using Spring component lifecycle callbacks such as @PostConstruct. "
I think what is missing here is the fact that you cannot use CommandLineRunner
s or ApplicationRunner
s if you want to have some code executed and finished BEFORE the ApplicationReadyEvent
is fired and BEFORE the application accepts network requests.
It look me a while to find out that I can achieve what I want using a SmartInitializingSingleton
. But it was a quite long journey to find that one out.
I think the existence of SmartInitializingSingleton
and its purpose should be mentioned next to the description of the Readiness state (https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.application-availability.readiness), so that other people don't fall into that trap.
PS: This is the discussion that caused this issue: https://stackoverflow.com/questions/77968217/spring-boot-accepts-network-requests-before-applicationrunners-are-done/77968634#77968634
Comment From: bclozel
I think that this is the expected behavior here.
I think what is missing here is the fact that you cannot use CommandLineRunners or ApplicationRunners if you want to have some code executed and finished BEFORE the ApplicationReadyEvent is fired and BEFORE the application accepts network requests.
I think there is a misunderstanding here. The difference between liveness and readiness is about knowing whether the application is live and if it can receive traffic. If too much work happens before the application context starts and the web server accepts connections, then the platform is likely to kill the application instance because it does not consider it live. The goal here is to start the application as soon as possible to have the liveness endpoint available, and then perform startup tasks while the readiness endpoint states that the application is not accepting traffic yet.
The application indeed accepts network requests if you try it, but the readiness endpoint states otherwise while the runners are executed. You can see the steps in action in the source code here.
I don't think we should mention SmartInitializingSingleton
in the docs, since this still happens during the application context setup and will delay the application responding on its liveness endpoint.
I did find an issue in our documentation on the lifecycle and probes states section: "Refuses requests" is a bit too strong, as the server technically accepts requests but tells the platform that it should not send requests its way.
With that in mind, do you see possible improvements in our documentation? Does this clear things up?
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: Walnussbaer
I think that this is the expected behavior here.
I think what is missing here is the fact that you cannot use CommandLineRunners or ApplicationRunners if you want to have some code executed and finished BEFORE the ApplicationReadyEvent is fired and BEFORE the application accepts network requests.
I think there is a misunderstanding here. The difference between liveness and readiness is about knowing whether the application is live and if it can receive traffic. If too much work happens before the application context starts and the web server accepts connections, then the platform is likely to kill the application instance because it does not consider it live. The goal here is to start the application as soon as possible to have the liveness endpoint available, and then perform startup tasks while the readiness endpoint states that the application is not accepting traffic yet.
The application indeed accepts network requests if you try it, but the readiness endpoint states otherwise while the runners are executed. You can see the steps in action in the source code here.
I don't think we should mention
SmartInitializingSingleton
in the docs, since this still happens during the application context setup and will delay the application responding on its liveness endpoint.I did find an issue in our documentation on the lifecycle and probes states section: "Refuses requests" is a bit too strong, as the server technically accepts requests but tells the platform that it should not send requests its way.
With that in mind, do you see possible improvements in our documentation? Does this clear things up?
Ok, then let me elaborate what I want to achieve:
I need code to be executed before the application can be considered to be ready. In my case, the completion of this code needs to be awaited.
The thing is, even if the readiness endpoint returns false while the runners are running, I won't be able to control the behaviour of the client of my API. If the client calls the HTTP API of the spring boot app, I will get an - possibly false - answer. This is bad in my opinion. How shall It know that the app is not ready yet if the HTTP API already answers requests? So there might be a business requirement that the application executes some code BEFORE it actually answers any requests.
So what could be done to improve the documentation: next to the explanation of the application's readiness state (here https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.application-availability.readiness or here: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.application-events-and-listeners) there should be a link to parts of the documentation that explain how to hook into that lifecycle to be able to run and await code that is required BEFORE the application can be considered ready. Such as the usage of the SmartInitializingSingleton
Comment From: bclozel
Thanks for the update.
Unfortunately, this is a bit of a chicken and egg problem: the platform cannot know whether an application is ready unless it's capable of communicating with it over HTTP - and we can't start the connectors before the application context and its beans are somewhat ready. Now I would argue that it's the job of the platform (usually load balancers) to not direct traffic to the application until the instance is marked as ready.
If your platform does not support this feature I understand the need for SmartInitializingSingleton
, but I don't think we should document that as this will be a liveness check problem for most platforms out there and goes against best practices in that domain.
I'm closing this issue as a result. Thanks!
Comment From: Walnussbaer
Thanks for the update.
Unfortunately, this is a bit of a chicken and egg problem: the platform cannot know whether an application is ready unless it's capable of communicating with it over HTTP - and we can't start the connectors before the application context and its beans are somewhat ready. Now I would argue that it's the job of the platform (usually load balancers) to not direct traffic to the application until the instance is marked as ready.
If your platform does not support this feature I understand the need for
SmartInitializingSingleton
, but I don't think we should document that as this will be a liveness check problem for most platforms out there and goes against best practices in that domain.I'm closing this issue as a result. Thanks!
I see your point, thanks for taking the time in explaining to me why this is not a problem for most platforms! I will keep that in mind.
Comment From: randeepbydesign
Hi, I just wanted to put a vote for the suggestion from @Walnussbaer to clarify this in the documentation. If a reference to SmartInitializingSingleton is not in order, at the very least an indication like:
Application readiness is initially manged by Spring internally and, once an application is online will be put into a state of ACCEPTING_TRAFFIC
I am chiming in here because we did roll out to production with a readiness check that failed to achieve our desired goal because the application started taking on traffic before we wanted. Neither the Spring Docs nor Baeldung or any other resource was clear on this point.
Regarding your comment about "best practices" I do understand how that would be true in many cases. However, I would posit that if an application cannot connect to a 3rd party service for some reason that it should fail a liveness check. The considerations and engineering around these kinds of decisions could fall into the scope of a broader discussion about Spring lifecycle management, liveness, and readiness.
Thanks for taking the time to read this and all the hard work the Spring team does!
Comment From: bclozel
Hi @randeepbydesign , thanks for reaching out.
Application readiness is initially manged by Spring internally and, once an application is online will be put into a state of ACCEPTING_TRAFFIC
I'm not sure I understand the point being made here. We are already describing the difference between liveness and readiness and how to update the readiness state, the startup process and pointing at phases and events published, and showing the link between startup phases and the liveness/readiness state.
If you want to get something done before the application is marked as ready, our TIP explains that a CommandLineRunner
or an ApplicationRunner
component is a good choice. A SmartInitializingSingleton
is also possible, but that's almost orthogonal - you would do that either way because your component would need to be initialized before being used.
I am chiming in here because we did roll out to production with a readiness check that failed to achieve our desired goal because the application started taking on traffic before we wanted.
I think this is the critical part. This sounds like one of your component was created in an invalid state and that it would not respond correctly even after context refresh. Maybe you can tell us more about it?