Spring Boot (tested with 2.6.3) aggregates multiple instances of the same health indicator

  • Every aggregated health indicator gets recreated every time the health endpoint is pulled.
  • Non aggregated health indicator instances are reused.

This lifecycle inconsistency seems unintended to me.

Demo: https://github.com/schuch/actuator-test

Fire up the application and call http://localhost:8080/actuator/health multiple times, then view the log.

This will be logged for every request:

created TestHealthIndicator for 1
created TestHealthIndicator for 2

This is logged only once at startup:

created SingleHealthIndicator

Sample Log:

2022-02-03 16:14:22.972  INFO 27776 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1857 ms
2022-02-03 16:14:23.174  INFO 27776 --- [           main] com.example.demo.SingleHealthIndicator   : created SingleHealthIndicator
2022-02-03 16:14:23.197  INFO 27776 --- [           main] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 1
2022-02-03 16:14:23.199  INFO 27776 --- [           main] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 2
2022-02-03 16:14:23.801  INFO 27776 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint(s) beneath base path '/actuator'
2022-02-03 16:14:23.849  INFO 27776 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-02-03 16:14:23.868  INFO 27776 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 3.43 seconds (JVM running for 5.703)
2022-02-03 16:14:25.243  INFO 27776 --- [1)-10.180.42.16] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 1
2022-02-03 16:14:25.244  INFO 27776 --- [1)-10.180.42.16] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 2
2022-02-03 16:14:25.248  INFO 27776 --- [2)-10.180.42.16] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-02-03 16:14:25.249  INFO 27776 --- [2)-10.180.42.16] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-02-03 16:14:25.251  INFO 27776 --- [2)-10.180.42.16] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms
2022-02-03 16:14:46.794  INFO 27776 --- [nio-8080-exec-1] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 1
2022-02-03 16:14:46.794  INFO 27776 --- [nio-8080-exec-1] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 2
2022-02-03 16:14:48.749  INFO 27776 --- [nio-8080-exec-2] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 1
2022-02-03 16:14:48.749  INFO 27776 --- [nio-8080-exec-2] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 2
2022-02-03 16:14:49.693  INFO 27776 --- [nio-8080-exec-3] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 1
2022-02-03 16:14:49.694  INFO 27776 --- [nio-8080-exec-3] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 2
2022-02-03 16:14:51.387  INFO 27776 --- [nio-8080-exec-4] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 1
2022-02-03 16:14:51.387  INFO 27776 --- [nio-8080-exec-4] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 2
2022-02-03 16:14:52.051  INFO 27776 --- [nio-8080-exec-5] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 1
2022-02-03 16:14:52.051  INFO 27776 --- [nio-8080-exec-5] com.example.demo.TestHealthIndicator     : created TestHealthIndicator for 2

This behavior makes it impossible to keep state in health indicators after initialization.

E.g. SolrHealthIndicator has a heuristic approach to detect the suitable status check method. The recreation of the health indicator makes it pointless to store the check method. Also unnecessary 404 errors are raised in the solr log for every health check.

Comment From: philwebb

This does indeed look like an oversight. With a single contributor, the result is returned directly and is treated as a singleton. With the composite we're calling CompositeHealthContributor with a valueAdapter callback. The callback is invoked on each access.

I think we should change the composite to call the value adapter early. We might need to be a little careful about back compatibility.

Comment From: izeye

This has been superseded by #31676, but it seems to have missed to close it.