DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Kafka JDBC Source Connector for Large Data
  • Kotlin Spring Boot Web-Service Integration With Database
  • Building an Enterprise CDC Solution
  • Data Fabric: What Is It and Why Do You Need It?

Trending

  • Intro to RAG: Foundations of Retrieval Augmented Generation, Part 1
  • Building Enterprise-Ready Landing Zones: Beyond the Initial Setup
  • Revolutionizing Financial Monitoring: Building a Team Dashboard With OpenObserve
  • Unmasking Entity-Based Data Masking: Best Practices 2025
  1. DZone
  2. Coding
  3. Frameworks
  4. Custom Health Checks in Spring Boot

Custom Health Checks in Spring Boot

Spring Boot provides health indicators to monitor application health and database health with the help of Spring Boot Actuator.

By 
Sai Krishna Reddy Chityala user avatar
Sai Krishna Reddy Chityala
·
Jun. 16, 24 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
10.5K Views

Join the DZone community and get the full member experience.

Join For Free

Spring Boot provides health indicators to monitor application health and database health with the help of Spring Boot Actuator. Spring Boot Actuator comes with various health indicators for most relational databases and non-relational databases like MongoDB, Redis, ElasticSearch, etc. Spring Boot Actuator also provides health indicators for RabbitMQ, and IBM MQ out of the box.

Why Choose Custom Health Checks

If we want to monitor the health status of the external services our application connects to, Spring Boot doesn’t provide any health indicators for this scenario out of the box, we need to write custom health indicators. Also, Spring Boot doesn’t provide any Kafka health indicator out of the box as of today, so in this scenario, we need to go for a custom health indicator. 

Implementation 

Project Structure

Project Structure

Main Class

Java
 
package com.lights5.services.orders.api;

import com.lights5.services.orders.api.config.EndpointsRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class OrdersApiApplication {

 @Autowired
 private EndpointsRegistry endpointsRegistry;

 public static void main(String[] args) {
  SpringApplication.run(OrdersApiApplication.class, args);
 }
}


application.yml file 

YAML
 
spring:
  application:
    name: orders-api
  kafka:
    producer:
      bootstrap-servers: localhost:9092
  data:
    mongodb:
      host: localhost
      port: 27017
      database: orders_db
      username: ${username}
      password: ${pwd}
      authentication-database: orders_db

app:
  client:
    payments-service:
      host: https://example.com
      paths:
        health-check: /mock-service/health
        intiate-payment: /payments


ApplicationConfig 

Java
 
package com.lights5.services.orders.api.config;

import org.apache.kafka.clients.admin.AdminClient;
import org.springframework.boot.actuate.data.mongo.MongoHealthIndicator;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;

import java.util.Properties;

@Configuration
public class AppConfig {

    @Bean
    MongoHealthIndicator mongoHealthIndicator(MongoTemplate mongoTemplate) {

        return new CustomMongoHealthIndicator(mongoTemplate);
    }

    @Bean
    AdminClient kafkaAdminClient(KafkaProperties kafkaProperties) {

        Properties properties = new Properties();
        properties.put("bootstrap.servers", kafkaProperties.getBootstrapServers());
        properties.put("request.timeout.ms", 3000);
        properties.put("connections.max.idle.ms", 5000);
        return AdminClient.create(properties);
    }
}


EndpointsRegistry

This class registers all external clients that the application connects to. 

Java
 
package com.lights5.services.orders.api.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "app")
public class EndpointsRegistry {

    private Map<String, ServiceEndpoint> client;

    @Getter
    @Setter
    static class ServiceEndpoint {

        private String host;
        private Map<String, String> paths;
    }

    public String getHealthCheckURL(String serviceName) {
        ServiceEndpoint endpoint = this.client.get(serviceName);
        if (endpoint != null) {
            String healthCheckPath = endpoint.getPaths().get("health-check");
            String host = endpoint.getHost();

            return host + healthCheckPath;
        }
        return null;
    }
}


PaymentServiceMonitor (External Service Monitor)

This class overrides the default behavior of the AbstractHealthIndicator class. It executes a GET request on the health check URL of an external client and updates the status accordingly.

Java
 
package com.lights5.services.orders.api.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@Component
@RequiredArgsConstructor
public class PaymentServiceHealthIndicator extends AbstractHealthIndicator {

    private final EndpointsRegistry endpointsRegistry;

    @Override
    protected void doHealthCheck(Health.Builder builder) {
        try {
            String healthCheckURL = endpointsRegistry.getHealthCheckURL("payment-service");
            CloseableHttpClient httpClient = HttpClientBuilder.create().build();

            CloseableHttpResponse response = httpClient.execute(new HttpGet(healthCheckURL));
            if (response.getCode() >= 500) {
                log.error("Payment Service is down");
            }
            else {
                builder.up();
            }
        } catch (IOException ex) {
            builder.down();
        }
    }
}


HostAvailability Class 

This class is used to verify whether the application is able to connect to a given host and port number.

Java
 
package com.lights5.services.orders.api.service;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.*;

@Slf4j
public class HostAvailability {

    private HostAvailability() {
        // to prevent instantiation from other classes.
    }

    public static boolean isAvailable(String hostName, int port) {
        SocketAddress socketAddress = new InetSocketAddress(hostName, port);

        try (Socket socket = new Socket()) {

            socket.connect(socketAddress, 5000);
        } catch (IOException e) {

            log.error("Application Health Check Failed due to service unavailability {}", e.getMessage());
            return false;
        }
        return true;
    }
}


CustomMongoHealthIndicator 

This class extends the MongoHealthIndicator (provided by Spring Boot) and overrides the behavior of the health check method. First, we verify whether the app is able to connect to the host and port and then we execute a small command on the database to check database connectivity. If we execute the command directly without verifying server connectivity, it takes more time if the server is not reachable from the application. That’s why we are verifying server connectivity first. 

Java
 
package com.lights5.services.orders.api.config;

import com.lights5.services.orders.api.service.HostAvailability;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.data.mongo.MongoHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.data.mongodb.core.MongoTemplate;

@Slf4j
public class CustomMongoHealthIndicator extends MongoHealthIndicator {

    private final MongoTemplate mongoTemplate;

    @Value("${spring.data.mongodb.host}")
    private String mongodbHost;

    @Value("${spring.data.mongodb.port}")
    private int port;

    public CustomMongoHealthIndicator(MongoTemplate mongoTemplate) {
        super(mongoTemplate);
        this.mongoTemplate = mongoTemplate;
    }

    public void doHealthCheck(Health.Builder builder) throws Exception {
        boolean isServerAvailable = HostAvailability.isAvailable(mongodbHost, port);
        if (isServerAvailable) {
            Document result = mongoTemplate.executeCommand("{ isMaster: 1 }");
            builder.up().withDetail("maxWireVersion", result.getInteger("maxWireVersion"));
        }
        else {
            log.error("MongoDB Server is down.");
            builder.down();
        }
    }
}


KafkaHealthIndicator

This class provides implementation to verify Kafka cluster health. It uses KafkaAdminClient to describe the cluster, if it receives the response then the cluster is up else the cluster is down.

Note: Doesn’t work if the producer is configured to support transactions.

Java
 
package com.lights5.services.orders.api.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.common.Node;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@RequiredArgsConstructor
public class KafkaHealthIndicator extends AbstractHealthIndicator {

    private final AdminClient kafkaAdminClient;

    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {

        Collection<Node> nodes = kafkaAdminClient.describeCluster()
                .nodes().get(3, TimeUnit.SECONDS);

        if (!nodes.isEmpty()) {
            log.error("Kafka Server is up with nodes {}", nodes.size());
            builder.up();
        }
        else {
            log.error("Kafka Server is down");
            builder.down();
        }
    }
}


Working 

If any of these HealthIndicators (MongoDB, Kafka, Payments Service) is down, then the health check URL of the application returns the status as the following:

JSON
 
{
  "status": "DOWN"
}


If all these dependent services are up, then the status will be returned as:

JSON
 
{
  "status": "UP"
}


Endpoint to verify app health.

Conclusion

Custom health checks are important as they provide greater visibility of external services availability. We can take action immediately thereby decreasing application errors.

Database Relational database kafka Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • Kafka JDBC Source Connector for Large Data
  • Kotlin Spring Boot Web-Service Integration With Database
  • Building an Enterprise CDC Solution
  • Data Fabric: What Is It and Why Do You Need It?

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!