🏠 ❯ Spring Framework ❯ Spring Boot Custom Health Indicators

Spring Boot Custom Health Indicators

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 another 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.

Let’s take 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 responses.

  • 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 a 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 defines 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 the same response but the health component will have a 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.

Let us add the below property to application.properties

my.downstream.service.url = https://run.mocky.io/v3/ed8d9ae2-7d0d-4411-8b8c-66106d8a2721Code 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 let’s 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)

Let us give an URL that would give 503 Service unavailable status.

my.downstream.service.url = https://run.mocky.io/v3/1caa3162-845f-4b98-9e26-1fb90d23d970Code 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 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. Also, If you liked this article, you may also be interested in the followings articles.

Similar Posts

Leave a Reply

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