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