Helidon MP Health Check Guide

This guide describes how to create a sample MicroProfile (MP) project that can be used to run some basic examples using both built-in and custom health checks with Helidon MP.

What You Need

For this 15 minute tutorial, you will need the following:

A Helidon MP ApplicationYou can use your own application or use the Helidon MP Quickstart to create a sample application.
Java SE 17 (Open JDK 17)Helidon requires Java 17+.
Maven 3.6.1+Helidon requires Maven 3.6.1+.
Docker 18.09+You need Docker if you want to build and deploy Docker containers.
Kubectl 1.16.5+If you want to deploy to Kubernetes, you need kubectl and a Kubernetes cluster (you can install one on your desktop.
Verify Prerequisites
java -version
mvn --version
docker --version
kubectl version
Copied
Setting JAVA_HOME
# On Mac
export JAVA_HOME=`/usr/libexec/java_home -v 17`

# On Linux
# Use the appropriate path to your JDK
export JAVA_HOME=/usr/lib/jvm/jdk-17
Copied

Create a Sample MP Project

Generate the project sources using the Helidon MP Maven archetype. The result is a simple project that can be used for the examples in this guide.

Run the Maven archetype:
mvn -U archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-mp \
    -DarchetypeVersion=3.2.16 \
    -DgroupId=io.helidon.examples \
    -DartifactId=helidon-quickstart-mp \
    -Dpackage=io.helidon.examples.quickstart.mp
Copied

Using the Built-In Health Checks

Helidon has a set of built-in health checks that are automatically enabled to report various health check statuses that are commonly used:

  • deadlock detection

  • available disk space

  • available heap memory

The following example will demonstrate how to use the built-in health checks. These examples are all executed from the root directory of your project (helidon-quickstart-mp).

Add a dependency to your pom.xml file to include the built-in health checks:
<dependency>
    <groupId>io.helidon.health</groupId>
    <artifactId>helidon-health-checks</artifactId>
    <scope>runtime</scope>
</dependency>
Copied
Build the application, skipping unit tests, then run it:
mvn package -DskipTests=true
java -jar target/helidon-quickstart-mp.jar
Copied
Verify the health endpoint in a new terminal window:
curl http://localhost:8080/health
Copied
JSON response:
{
  "status": "UP",
  "checks": [
    {
      "name": "deadlock",
      "status": "UP"
    },
    {
      "name": "diskSpace",
      "status": "UP",
      "data": {
        "free": "325.54 GB",
        "freeBytes": 349543358464,
        "percentFree": "69.91%",
        "total": "465.63 GB",
        "totalBytes": 499963174912
      }
    },
    {
      "name": "heapMemory",
      "status": "UP",
      "data": {
        "free": "230.87 MB",
        "freeBytes": 242085696,
        "max": "3.56 GB",
        "maxBytes": 3817865216,
        "percentFree": "98.90%",
        "total": "271.00 MB",
        "totalBytes": 284164096
      }
    }
  ]
}
Copied

Custom Liveness Health Checks

You can create application-specific custom health checks and integrate them with Helidon using CDI. The following example shows how to add a custom liveness health check.

Create a new GreetLivenessCheck class with the following content:
package io.helidon.examples.quickstart.mp;

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

@Liveness 
@ApplicationScoped 
public class GreetLivenessCheck implements HealthCheck {
  private GreetingProvider provider;

  @Override
  public HealthCheckResponse call() {
    return HealthCheckResponse.named("LivenessCheck")  
        .up()
        .withData("time", System.currentTimeMillis())
        .build();
  }
}
Copied
  • Annotation indicating this is a liveness health check.
  • Annotation indicating there is a single liveness HealthCheck object during the lifetime of the application.
  • Build the HealthCheckResponse with status UP and the current time.
Build and run the application, then verify the custom liveness health endpoint:
curl http://localhost:8080/health/live
Copied
JSON response:
{
  "status": "UP",
  "checks": [
    {
      "name": "LivenessCheck",
      "status": "UP",
      "data": {
        "time": 1566338255331
      }
    }
  ]
}
Copied

Custom Readiness Health Checks

You can add a readiness check to indicate that the application is ready to be used. In this example, the server will wait five seconds before it becomes ready.

Create a new GreetReadinessCheck class with the following content:
package io.helidon.examples.quickstart.mp;

import java.time.Duration; 
import java.util.concurrent.atomic.AtomicLong;
import jakarta.enterprise.context.ApplicationScoped;

import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;

@Readiness 
@ApplicationScoped
public class GreetReadinessCheck implements HealthCheck {
  private final AtomicLong readyTime = new AtomicLong(0);


  @Override
  public HealthCheckResponse call() {
    return HealthCheckResponse.named("ReadinessCheck")  
        .status(isReady())
        .withData("time", readyTime.get())
        .build();
  }

  public void onStartUp(
      @Observes @Initialized(ApplicationScoped.class) Object init) {
    readyTime.set(System.currentTimeMillis()); 
  }

  /**
   * Become ready after 5 seconds
   *
   * @return true if application ready
   */
  private boolean isReady() {
    return Duration.ofMillis(System.currentTimeMillis() - readyTime.get()).getSeconds() >= 5;
  }
}
Copied
  • Include additional imports.
  • Annotation indicating that this is a readiness health check.
  • Build the HealthCheckResponse with status UP after five seconds, else DOWN.
  • Record the time at startup.
Build and run the application. Issue the curl command with -v within five seconds and you will see that the application is not ready:
curl -v  http://localhost:8080/health/ready
Copied
HTTP response status
< HTTP/1.1 503 Service Unavailable 
Copied
  • The HTTP status is 503 since the application is not ready.
Response body
{
  "status": "DOWN",
  "checks": [
    {
      "name": "ReadinessCheck",
      "status": "DOWN",
      "data": {
        "time": 1566399775700
      }
    }
  ]
}
Copied
After five seconds you will see the application is ready:
curl -v http://localhost:8080/health/ready
Copied
HTTP response status
< HTTP/1.1 200 OK 
Copied
  • The HTTP status is 200 indicating that the application is ready.
Response body
{
  "status": "UP",
  "checks": [
    {
      "name": "ReadinessCheck",
      "status": "UP",
      "data": {
        "time": 1566399775700
      }
    }
  ]
}
Copied

Custom Startup Health Checks

You can add a startup check to indicate whether or not the application has initialized to the point that the other health checks make sense. In this example, the server will wait eight seconds before it declares itself started.

Create a new GreetStartedCheck class with the following content:
package io.helidon.examples.quickstart.mp;

import java.time.Duration; 
import java.util.concurrent.atomic.AtomicLong;
import jakarta.enterprise.context.ApplicationScoped;

import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Started;

@Started 
@ApplicationScoped
public class GreetStartedCheck implements HealthCheck {
  private final AtomicLong readyTime = new AtomicLong(0);


  @Override
  public HealthCheckResponse call() {
    return HealthCheckResponse.named("StartedCheck")  
        .status(isStarted())
        .withData("time", readyTime.get())
        .build();
  }

  public void onStartUp(
      @Observes @Initialized(ApplicationScoped.class) Object init) {
    readyTime.set(System.currentTimeMillis()); 
  }

  /**
   * Become ready after 5 seconds
   *
   * @return true if application ready
   */
  private boolean isStarted() {
    return Duration.ofMillis(System.currentTimeMillis() - readyTime.get()).getSeconds() >= 8;
  }
}
Copied
  • Include additional imports.
  • Annotation indicating that this is a startup health check.
  • Build the HealthCheckResponse with status UP after eight seconds, else DOWN.
  • Record the time at startup of Helidon; the application will declare itself as started eight seconds later.
Build and run the application. Issue the curl command with -v within five seconds and you will see that the application has not yet started:
curl -v  http://localhost:8080/health/started
Copied
HTTP response status
< HTTP/1.1 503 Service Unavailable 
Copied
  • The HTTP status is 503 since the application has not started.
Response body
{
  "status": "DOWN",
  "checks": [
    {
      "name": "StartedCheck",
      "status": "DOWN",
      "data": {
        "time": 1566399775700
      }
    }
  ]
}
Copied
After eight seconds you will see the application has started:
curl -v http://localhost:8080/health/started
Copied
HTTP response status
< HTTP/1.1 200 OK 
Copied
  • The HTTP status is 200 indicating that the application is started.
Response body
{
  "status": "UP",
  "checks": [
    {
      "name": "StartedCheck",
      "status": "UP",
      "data": {
        "time": 1566399775700
      }
    }
  ]
}
Copied

When using the health check URLs, you can get the following health check data:

Get all the health check data, including custom data:
curl http://localhost:8080/health
Copied
JSON response:
{
  "status": "UP",
  "checks": [
    {
      "name": "LivenessCheck",
      "status": "UP",
      "data": {
        "time": 1566403431536
      }
    },
    {
      "name": "ReadinessCheck",
      "status": "UP",
      "data": {
        "time": 1566403280639
      }
    },
    {
      "name": "StartedCheck",
      "status": "UP",
      "data": {
        "time": 1566403280639
      }
    },
    {
      "name": "deadlock",
      "state": "UP",
      "status": "UP"
    },
    {
      "name": "diskSpace",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "325.50 GB",
        "freeBytes": 349500698624,
        "percentFree": "69.91%",
        "total": "465.63 GB",
        "totalBytes": 499963174912
      }
    },
    {
      "name": "heapMemory",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "231.01 MB",
        "freeBytes": 242235928,
        "max": "3.56 GB",
        "maxBytes": 3817865216,
        "percentFree": "98.79%",
        "total": "275.00 MB",
        "totalBytes": 288358400
      }
    }
  ]
}
Copied

Custom Health Root Path and Port

You can specify a custom port and root context for the root health endpoint path. However, you cannot use different ports, such as http://localhost:8080/myhealth and http://localhost:8081/myhealth/live. Likewise, you cannot use different paths, such as http://localhost:8080/health and http://localhost:8080/probe/live.

The example below will change the root path.

Create a file named application.yaml in the resources directory with the following contents:
health:
  web-context: "myhealth"  
Copied
  • The web-context specifies a new root path for the health endpoint.
Build and run the application, then verify that the health endpoint is using the new /myhealth root:
curl http://localhost:8080/myhealth
curl http://localhost:8080/myhealth/live
curl http://localhost:8080/myhealth/ready
curl http://localhost:8080/myhealth/started
Copied

The following example will change the root path and the health port.

Update application.yaml to use a different port and root path for the health endpoint:
server:
  port: 8080  
  host: "localhost"
  sockets:
    health: 
      port: 8081 
      bind-address: "localhost"
health:
  routing: "health" 
  web-context: "myhealth"
Copied
  • The default port for the application.
  • The name of the new socket, it can be any name, this example uses health.
  • The port for the new health socket.
  • The health endpoint routing uses the new socket health.
Build and run the application, then verify the health endpoint using port 8081 and /myhealth:
curl http://localhost:8081/myhealth
curl http://localhost:8081/myhealth/live
curl http://localhost:8081/myhealth/ready
curl http://localhost:8081/myhealth/started
Copied

Using Liveness, Readiness, and Startup Health Checks with Kubernetes

The following example shows how to integrate the Helidon health check API with an application that implements health endpoints for the Kubernetes liveness, readiness, and startup probes.

Delete the contents of application.yaml so that the default health endpoint path and port are used.

Rebuild and start the application, then verify the health endpoint:
curl http://localhost:8080/health
Copied
Stop the application and build the docker image:
docker build -t helidon-quickstart-mp .
Copied
Create the Kubernetes YAML specification, named health.yaml, with the following content:
kind: Service
apiVersion: v1
metadata:
  name: helidon-health 
  labels:
    app: helidon-health
spec:
  type: NodePort
  selector:
    app: helidon-health
  ports:
    - port: 8080
      targetPort: 8080
      name: http
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: helidon-health 
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helidon-health
  template:
    metadata:
      labels:
        app: helidon-health
        version: v1
    spec:
      containers:
        - name: helidon-health
          image: helidon-quickstart-mp
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /health/live 
              port: 8080
            initialDelaySeconds: 5 
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /health/ready 
              port: 8080
            initialDelaySeconds: 5 
            periodSeconds: 2
            timeoutSeconds: 3
          startupProbe:
            httpGet:
              path: /health/started 
              port: 8080
            initialDelaySeconds: 8 
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
---
Copied
  • A service of type NodePort that serves the default routes on port 8080.
  • A deployment with one replica of a pod.
  • The HTTP endpoint for the liveness probe.
  • The liveness probe configuration.
  • The HTTP endpoint for the readiness probe.
  • The readiness probe configuration.
  • The HTTP endpoint for the startup probe.
  • The startup probe configuration.
Create and deploy the application into Kubernetes:
kubectl apply -f ./health.yaml
Copied
Get the service information:
kubectl get service/helidon-health
Copied
NAME             TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
helidon-health   NodePort   10.107.226.62   <none>        8080:30116/TCP   4s 
Copied
  • A service of type NodePort that serves the default routes on port 30116.
Verify the health endpoints using port '30116', your port may be different. The JSON response will be the same as your previous test:
curl http://localhost:30116/health
Copied
Delete the application, cleaning up Kubernetes resources:
kubectl delete -f ./health.yaml
Copied

Summary

This guide demonstrated how to use health checks in a Helidon MP application as follows:

  • Access the default health checks

  • Create and use custom readiness, liveness, and startup checks

  • Customize the health check root path and port

  • Integrate Helidon health check API with Kubernetes

Refer to the following references for additional information: