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
3.1. SmallRye Health extension
-
in Wizard:
-
or add to existing project:
quarkus ext add io.quarkus:quarkus-smallrye-health
./mvnw quarkus:add-extension -Dextensions="io.quarkus:quarkus-smallrye-health"
<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 dev --clean
./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
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.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.2. Readiness-Check
These checks appear in the endpoint /q/health/ready
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.3. Startup-Check
These checks appear in the endpoint /q/health/started
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. |
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
# 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 ext add io.quarkus:quarkus-jdbc-postgresql
quarkus ext add io.quarkus:quarkus-hibernate-orm-rest-data-panache
./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:
<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.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 dev --clean
./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 |
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.
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. |