health logo

1. General

  • SmallRye Health allows applications to provide information about their state to external viewers which is typically useful in cloud environments (e.g. k8s) where automated processes must be able to determine whether the application should be discarded or restarted.

  • Runtimes that currently use SmallRye Health as their health monitoring implementation

    • Quarkus with quarkus-smallrye-health extension

    • WildFly

    • Thorntail

  • There are 3 types of checks to check the healthiness of your application

    • Liveness Checks: Determines if the application is still running and not in a failed or unrecoverable state.

    • Readiness Checks: Determines if the application is ready to handle incoming requests.

    • Startup Checks: Determines if the application has successfully started.

  • Also read:

In this instruction, I will show you how to use it in Quarkus.

2. Prerequisites

To complete this guide, you need:

  • Roughly 15 minutes

  • An IDE

  • JDK 17+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.9.8

  • Optionally the Quarkus CLI if you want to use it

  • Optionally Mandrel or GraalVM installed and configured appropriately if you want to build a native executable (or Docker if you use a native container build)

3. Creating the Maven Project

create project

3.1. SmallRye Health extension

  • in Wizard:

required extensions
  • or add to existing project:

Quarkus-CLI
quarkus ext add io.quarkus:quarkus-smallrye-health
Maven-Wrapper
./mvnw quarkus:add-extension -Dextensions="io.quarkus:quarkus-smallrye-health"
pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

4. First attempts with existing health checks endpoints

4.1. Start the project

Quarkus-CLI
quarkus dev --clean
Maven-Wrapper
./mvnw clean quarkus:dev

Importing the smallrye-health extension directly exposes three REST endpoints:

  • /q/health/live - Liveness Checks: The application is up and running.

  • /q/health/ready - Readiness Checks: The application is ready to serve requests.

  • /q/health/started - Startup Checks: The application is started.

  • /q/health - Accumulating all health check procedures in the application.

All health REST endpoints return a simple JSON object with two fields:

  • status - the overall result of all the health check procedures

    • UP - all checks are up

    • DOWN - one or more checks are down

  • checks - an array of individual checks

4.2. Use existing health checks endpoints

Example: Accumulating all health check procedures in the application
curl http://localhost:8080/q/health
Response of /q/health/live
{
    "status": "UP",
    "checks": [
    ]
}

Now try it yourself with the other existing endpoints :-)

5. Individual Health Checks

You can also create your own health checks to announce the availability of your own program parts using own health check classes.

5.1. Liveness-Check

These checks appear in the endpoint /q/health/live

5.1.1. Create class LivenessCheck

create class livenesscheck

5.1.2. Implement class LivenessCheck

package at.htlleonding.health;

import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;

import java.util.Random;

@Liveness (1)
@ApplicationScoped (2)
public class LivenessCheck implements HealthCheck { (3)
    @Override
    public HealthCheckResponse call() {
        boolean isUp = new Random().nextBoolean(); (4)

        if(isUp) { (5)
            return HealthCheckResponse.up("liveness-check");
        } else {
            return HealthCheckResponse.down("liveness-check");
        }
    }
}
1 @Liveness annotation means that the check is a Liveness-Check and exposes the result on /q/health/live.
2 It’s recommended to annotate the health check class with @ApplicationScoped so that a single bean instance is used for all health check requests.
3 Your health check class needs to implement the HealthCheck interface. This means you have to override the call method.
4 This is the condition whether the check is up or down. Here in the demo example we use a random boolean
5 Here you return the name of your health check with HealthCheckResponse.up or HealthCheckResponse.down

5.1.3. Get response of LivenessCheck

Accumulating liveness health check procedures in the application
curl http://localhost:8080/q/health/live
Response of /q/health/live
{
    "status": "DOWN",
    "checks": [
        {
            "name": "liveness-check",
            "status": "DOWN"
        }
    ]
}

5.2. Readiness-Check

These checks appear in the endpoint /q/health/ready

5.2.1. Create class ReadinessCheck

create class readinesscheck

5.2.2. Implement class ReadinessCheck

package at.htlleonding.health;

import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;

import java.util.Random;

@Readiness (1)
@ApplicationScoped (2)
public class ReadinessCheck implements HealthCheck { (3)
    @Override
    public HealthCheckResponse call() {
        boolean isUp = new Random().nextBoolean(); (4)

        if(isUp) { (5)
            return HealthCheckResponse.up("readiness-check");
        } else {
            return HealthCheckResponse.down("readiness-check");
        }
    }
}
1 @Readiness annotation means that the check is a Readiness-Check and exposes the result on /q/health/ready.
2 It’s recommended to annotate the health check class with @ApplicationScoped so that a single bean instance is used for all health check requests.
3 Your health check class needs to implement the HealthCheck interface. This means you have to override the call method.
4 This is the condition whether the check is up or down. Here in the demo example we use a random boolean.
5 Here you return the name of your health check with HealthCheckResponse.up or HealthCheckResponse.down.

5.2.3. Get response of ReadinessCheck

Accumulating readiness health check procedures in the application
curl http://localhost:8080/q/health/ready
Response of /q/health/ready
{
    "status": "DOWN",
    "checks": [
        {
            "name": "readiness-check",
            "status": "DOWN"
        }
    ]
}

5.3. Startup-Check

These checks appear in the endpoint /q/health/started

5.3.1. Create class StartupCheck

create class startupcheck

5.3.2. Implement class StartupCheck

package at.htlleonding.health;

import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Startup;

import java.util.Random;

@Startup (1)
@ApplicationScoped (2)
public class StartupCheck implements HealthCheck { (3)
    @Override
    public HealthCheckResponse call() {
        boolean isUp = new Random().nextBoolean(); (4)

        if(isUp) { (5)
            return HealthCheckResponse.up("startup-check");
        } else {
            return HealthCheckResponse.down("startup-check");
        }
    }
}
1 @Startup annotation means that the check is a Startup-Check and exposes the result on /q/health/started.
2 It’s recommended to annotate the health check class with @ApplicationScoped so that a single bean instance is used for all health check requests.
3 Your health check class needs to implement the HealthCheck interface. This means you have to override the call method.
4 This is the condition whether the check is up or down. Here in the demo example we use a random boolean.
5 Here you return the name of your health check with HealthCheckResponse.up or HealthCheckResponse.down.

5.3.3. Get response of ReadinessCheck

Accumulating startup health check procedures in the application
curl http://localhost:8080/q/health/started
Response of /q/health/started
{
    "status": "UP",
    "checks": [
        {
            "name": "startup-check",
            "status": "UP"
        }
    ]
}

6. Individual Health Check: Example with Database connection

Stop the Quarkus application to avoid any problems.

6.1. Database Setup

  • run the following script to start a PostgreSQL database in Docker

docker run --rm \
           --name postgres-db \
           -e POSTGRES_USER=app \
           -e POSTGRES_PASSWORD=app \
           -e POSTGRES_DB=db \
           -v ${PWD}/db-postgres/db:/var/lib/postgresql/data \
           -p 5432:5432 \
           postgres:16.3-alpine
  • or use this download link and run the script with

chmod u+x postgres-run-in-docker.sh
./postgres-run-in-docker.sh

6.2. Prepare Quarkus application for database usage

  • paste following properties for the PostgreSQL database connection in your application.properties in the Quarkus project

application.properties
# datasource configuration
quarkus.datasource.db-kind = postgresql
quarkus.datasource.username = app
quarkus.datasource.password = app
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/db

# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation=drop-and-create
  • if you use a GitHub-Repository, add following clause to your .gitignore, to exclude all database files

**/db-postgres/
  • add dependencies for the database connection (JDBC, Hibernate, Panache)

Quarkus-CLI
quarkus ext add io.quarkus:quarkus-jdbc-postgresql
quarkus ext add io.quarkus:quarkus-hibernate-orm-rest-data-panache
Maven-Wrapper
./mvnw quarkus:add-extension -Dextensions="io.quarkus:quarkus-jdbc-postgresql"
./mvnw quarkus:add-extension -Dextensions="io.quarkus:quarkus-hibernate-orm-rest-data-panache"
  • or paste following dependency snippets to your pom.xml:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-orm-rest-data-panache</artifactId>
</dependency>

6.3. Create the health check for the database connection

6.3.1. Create class DatabaseHealthCheck

create class databasehealthcheck

6.3.2. Implement class DatabaseHealthCheck

package at.htlleonding.health;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;

import javax.sql.DataSource;
import java.sql.Connection;

@Readiness (1)
@ApplicationScoped
public class DatabaseHealthCheck implements HealthCheck {
    @Inject
    DataSource dataSource; (2)

    @Override
    public HealthCheckResponse call() {
        try(Connection connection = dataSource.getConnection()) { (3)
            if(!connection.isValid(2)){ (4)
                throw new Exception("invalid connection after calling connection.isValid with a timeout of 2s");
            }

            return HealthCheckResponse.up("database-connection-active"); (5)
        } catch (Exception e) {
            return HealthCheckResponse.down("database-connection-active"); (6)
        }
    }
}
1 for database checks, we use Readiness-Checks
2 the existing configured database connection will be injected in this variable (java.sql.Datasource dataSource)
3 we try to get the connection
4 then it will be checked if the connection is valid
5 if everything works, it returns a HealthCheckResponse.up with database-connection-active as name
6 if something fails, it returns a HealthCheckResponse.down with database-connection-active as name

6.4. Launch the application and watch the results

Quarkus-CLI
quarkus dev --clean
Maven-Wrapper
./mvnw clean quarkus:dev

6.4.1. Explore the results

curl http://localhost:8080/q/health/ready
{
    "status": "DOWN",
    "checks": [
        {
            "name": "Database connections health check",
            "status": "DOWN",
            "data": {
                "<default>": "Unable to execute the validation check for the default DataSource: Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections."
            }
        },
        {
            "name": "database-connection-active",
            "status": "DOWN"
        },
        {
            "name": "readiness-check",
            "status": "DOWN"
        }
    ]
}
Surprisingly, in addition to our own implemented database check, there is already a pre-implemented database check by SmallRye

7. Integrated Health Monitor

Route /q/health-ui allows you to see your Health Checks in a Web GUI.

health ui

8. Health Checks (Probes) for Kubernetes

In Kubernetes, a probe is a mechanism used to determine the health and readiness of a container or application running within a pod. Probes are defined in the pod specification and are performed periodically to ensure the proper functioning of the application.

You can use these Health Check endpoints for Kubernetes Deployments.

appsrv.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: appsrv

spec:
  replicas: 1
  selector:
    matchLabels:
      app: appsrv
  template:
    metadata:
      labels:
        app: appsrv
    spec:
      containers:
        - name: appsrv
          image: ghcr.io/example-user/example-repo/example-image:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          startupProbe: (1)
            httpGet:
              path: /q/health
              port: 8080
            timeoutSeconds: 5
            initialDelaySeconds: 15
          readinessProbe:
            tcpSocket:
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe: (2)
            httpGet:
              path: /q/health
              port: 8080
            timeoutSeconds: 5
            initialDelaySeconds: 60
            periodSeconds: 120
---
apiVersion: v1
kind: Service
metadata:
  name: appsrv

spec:
  ports:
    - port: 8080
      targetPort: 8080
      protocol: TCP
  selector:
    app: appsrv
1 In Kubernetes, the startupProbe ensures the containerized application starts correctly. It is ideal for applications with long initialization times. If the probe fails, Kubernetes restarts the container. Once it succeeds, other probes, like livenessProbe, take over.
2 The livenessProbe in Kubernetes checks the container’s health during its lifecycle. If it detects a failure, such as a crash or unresponsiveness, Kubernetes restarts the container to maintain application availability.