🏠Spring FrameworkSpring Boot Custom Health Indicators

Spring Boot Custom Health Indicators

Spring Framework

In this post, We will learn about writing Custom Health Check indicators for Spring Boot Applications.

Spring Boot out of the box health-checks are good. But in the real world it is often the case that one application relies on other application’s availability for performing operations. In this case it would be helpful to add a health indicator for the downstream systems just like a Database or a file system health-check. Spring-Boot has a clean and easier way to do this and we will find out how in this post.

Here is a real world scenario.

  • You are developing an Order Processing system for your website.
  • Your Order service uses a payment service from a third party.
  • Your Order service will fail to process requests if the payment service is down
  • The customer has no direct way to test if the payment service is up

In this case, It is a good practice to mark the Order service as down if the payment system is down. Here is how.

Health Indicator Beans

Before going further, I would like you to read about Spring-boot health-check Indicators as you would get a better understanding of HealthIndicators.

Spring Boot health check indicators are controlled by Auto-Configured beans of HealthIndicator interface. If we want to, We can write our own HealthIndicator beans.

for example, Check this bean definition.

package com.springhow.examples.customhealthchecks; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class CustomHealthIndicator implements HealthIndicator { @Override public Health health() { return Health.up().build(); } }
Code language: CSS (css)

The above definition creates a bean named customHealthIndicator and will be picked by AutoConfiguredHealthContributorRegistry. This can be confirmed by seeing custom health component at http://localhost:8080/actuator/health

$ curl -s -i -X GET http://localhost:8080/actuator/health HTTP/1.1 200 Content-Type: application/vnd.spring-boot.actuator.v3+json Transfer-Encoding: chunked Date: Sat, 01 Aug 2020 19:03:38 GMT Keep-Alive: timeout=60 Connection: keep-alive { "status": "UP", "components": { "custom": { "status": "UP" }, "diskSpace": { "status": "UP", "details": { "total": 254971625472, "free": 60132696064, "threshold": 10485760, "exists": true } }, "ping": { "status": "UP" } } }
Code language: JavaScript (javascript)

The public Health health() method in our example simply builds a status UP response. but you could also send any one of the following status response.

  • Status.UP
  • Status.DOWN
  • Status.OUT_OF_SERVICE
  • Status.UNKNOWN

Custom health check names

Note that the health-component’s name is based on the Class name of the Bean. This is because the health endpoint needs an unique name to show the new component in the response.

So HealthContributorNameFactory takes the indicator bean name and strips the suffixes healthindicator and healthcontributor from the bean name if needed. In our case custom for the bean customHealthIndicator.

If we want to rename our health-check, We either need to name our class differently or just give the component definition a name like below.

package com.springhow.examples.customhealthchecks; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component("downstream") public class CustomHealthIndicator implements HealthIndicator { @Override public Health health() { return Health.up().build(); } }
Code language: CSS (css)

This would create same response but the health component will have name as downstream.

Custom Health check for a third-party service

To simulate a third-party API, I’m going to use mocky.io. This can be any URL that generates a HTTP 200 OK response.

lets add below property to application.properties

my.downstream.service.url = https://run.mocky.io/v3/ed8d9ae2-7d0d-4411-8b8c-66106d8a2721
Code language: JavaScript (javascript)

This sample URL gives the following response.

$ curl -s -i -X GET https://run.mocky.io/v3/ed8d9ae2-7d0d-4411-8b8c-66106d8a2721 HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Date: Sat, 01 Aug 2020 19:51:34 GMT Content-Length: 21 { "status": "OK" }
Code language: JavaScript (javascript)

So Lets add some logic to our CustomHealthIndicator to validate the HTTP status code and the status text of the response.

@Component("downstream") public class CustomHealthIndicator implements HealthIndicator { @Value("${my.downstream.service.url}") private String downstreamUrl; RestTemplate restTemplate = new RestTemplate(); @Override public Health health() { try { ResponseEntity<JsonNode> responseEntity = restTemplate.getForEntity(downstreamUrl, JsonNode.class); if (responseEntity.getStatusCode().is2xxSuccessful()) { String status = responseEntity.getBody().get("status").textValue(); if (status.equals("OK")) { return Health.up().withDetail("status", status).build(); } else { return Health.down().build(); } } else { return Health.down().build(); } } catch (Exception e) { return Health.down().withException(e).build(); } } }
Code language: JavaScript (javascript)

Lets give an URL that would give 503 Service unavailable status.

my.downstream.service.url = https://run.mocky.io/v3/1caa3162-845f-4b98-9e26-1fb90d23d970
Code language: JavaScript (javascript)

As this URL gives out a 503 service unavailable error, restTemplate throws an HttpServerErrorException which is then caught and used to return a health information using withException() builder method.

A sample response in this case looks as below.

GET http://localhost:8080/actuator/health HTTP/1.1 200 Content-Type: application/vnd.spring-boot.actuator.v3+json Transfer-Encoding: chunked Date: Sat, 01 Aug 2020 20:08:28 GMT Keep-Alive: timeout=60 Connection: keep-alive { "status": "UP", "components": { "diskSpace": { "status": "UP", "details": { "total": 254971625472, "free": 61228318720, "threshold": 10485760, "exists": true } }, "downstream": { "status": "UNKNOWN", "details": { "error": "org.springframework.web.client.HttpServerErrorException$ServiceUnavailable: 503 Service Unavailable: [{\n \"status\": \"Not OK\"\n}]" } }, "ping": { "status": "UP" } } }
Code language: JavaScript (javascript)

The examples are available in this github repository.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *