4 min read

Custom health-check for Spring-Boot applications

August 01, 2020

Spring-Boot’s 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.

HealthIndicator Beans

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

Spring-Boot’s health-check features 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();
    }

}

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"
    }
  }
}

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

Giving names to health components.

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();
    }

}

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

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

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"
}

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

package com.springhow.examples.customhealthchecks;

import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@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();
        }
    }
}

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

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"
    }
  }
}

The examples are available in github and the link can be found below.

Raja Anbazhagan

About the author

Raja is a Software Engineer with over 7 years of experience in working with Enterprise Java applications. Lately, He is focused in cloud-based Java applications and serverless technologies. He spends his spare time in stackoverflow.

Browse Categories