- Helidon SE Health Check Guide
This guide describes how to create a sample Helidon SE project that can be used to run some basic examples using both built-in and custom health checks.
What You Need
For this 15 minute tutorial, you will need the following:
| Java SE 21 (Open JDK 21) | Helidon requires Java 21+ (25+ recommended). |
| Maven 3.8+ | Helidon requires Maven 3.8+. |
| Docker 18.09+ | If you want to build and run 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). |
java -version
mvn --version
docker --version
kubectl version# On Mac
export JAVA_HOME=`/usr/libexec/java_home -v 21`
# On Linux
# Use the appropriate path to your JDK
export JAVA_HOME=/usr/lib/jvm/jdk-21Create a Sample SE Project
Generate the project sources using the Helidon SE Maven archetype. The result is a simple project that can be used for the examples in this guide.
mvn -U archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-se \
-DarchetypeVersion=4.4.1 \
-DgroupId=io.helidon.examples \
-DartifactId=helidon-quickstart-se \
-Dpackage=io.helidon.examples.quickstart.seUsing the Built-In Health Checks
Helidon has a set of built-in health checks:
deadlock detection
available disk space
available heap memory
The following example shows how to use the built-in health checks. These examples are all executed from the root directory of your project (helidon-quickstart-se).
Notice that the pom.xml file in the generated project already contains dependencies for Helidon’s health component and for the built-in health checks.
<dependencies>
<dependency>
<groupId>io.helidon.webserver.observe</groupId>
<artifactId>helidon-webserver-observe-health</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.health</groupId>
<artifactId>helidon-health-checks</artifactId>
</dependency>
</dependencies>Handling health checks is part of Helidon’s observability support. By default, when you add the dependency for the built-in health checks, Helidon automatically registers the built-in checks.
mvn clean package
java -jar target/helidon-quickstart-se.jarIn another window, access the application’s health endpoint.
curl -v http://localhost:8080/observe/healthThe verbose curl output reports the HTTP status:
< HTTP/1.1 204 No ContentThe successful status means all health checks reported UP.
To see the details about each health check, add the following features configuration fragment in the server section of the application.yaml. Make sure the features key is at the same level as port and host that are already in the file.
server)server:
port: 8080
host: 0.0.0.0
features:
observe:
observers:
health:
details: true- Added
featuresconfig section.
Press ^C to stop the running server, rebuild it, and rerun it.
^C
mvn clean package
java -jar target/helidon-quickstart-se.jarIn the other window access the health endpoint again.
curl -v http://localhost:8080/observe/healthThis time the curl output shows not only the HTTP status—as 200 instead of 204 because the response now contains data—but also the detailed output for all health checks.
{
"status": "UP",
"checks": [
{
"name": "diskSpace",
"status": "UP",
"data": {
"total": "465.63 GB",
"percentFree": "14.10%",
"totalBytes": 499963174912,
"free": "65.67 GB",
"freeBytes": 70513274880
}
},
{
"name": "heapMemory",
"status": "UP",
"data": {
"total": "516.00 MB",
"percentFree": "99.82%",
"max": "8.00 GB",
"totalBytes": 541065216,
"maxBytes": 8589934592,
"free": "500.87 MB",
"freeBytes": 525201320
}
},
{
"name": "deadlock",
"status": "UP"
}
]
}- Overall application health status
- List of individual health checks.
Adding Custom Health Checks
You can add your own custom health checks. These typically assess the conditions in and around your application and report whether the service should be considered started, live, and/or ready.
The following trivial but illustrative example adds a custom start-up check that reports DOWN until the server has been running for eight seconds and reports UP thereafter. Note the two main steps in the example code:
- Create an explicit instance of
ObserveFeaturewhich contains a customHealthObserverwith the custom check. - Add that
ObserveFeatureinstance to theWebServerConfig.Builderas a feature.
Main#main, augmenting the creation of WebServer instance with a custom health checkvoid snippet1(Config config) {
AtomicLong serverStartTime = new AtomicLong();
HealthObserver healthObserver = HealthObserver.builder()
.details(true)
.addCheck(() -> HealthCheckResponse.builder()
.status(System.currentTimeMillis() - serverStartTime.get() >= 8000)
.detail("time", System.currentTimeMillis())
.build(),
HealthCheckType.STARTUP,
"warmedUp")
.build();
ObserveFeature observe = ObserveFeature.builder()
.config(config.get("server.features.observe"))
.addObserver(healthObserver)
.build();
WebServer server = WebServer.builder()
.config(config.get("server"))
.addFeature(observe)
.routing(Main::routing)
.build()
.start();
serverStartTime.set(System.currentTimeMillis()); - Declare a variable for holding the server start-up time. (This is set later in the code.)
- Begin preparing the custom
HealthObserveraccording to this app’s specific needs. - Turn on detailed output in HTTP responses to the health endpoint.
- Add a custom start-up health check:
- Find and apply configuration for observability observers other than health (because we are about to create our own custom
HealthObserver). - Add the
HealthObserverto theObserveFeature. - Add the
ObserveFeatureinstance as a feature to the webserver. - Record when the server has actually started.
Note that the health check type and name are fixed, whereas the health check recomputes the value of the response every time Helidon queries it.
For the next step, be ready to access the health endpoint very quickly after you restart the server!
^C
mvn package
java -jar target/helidon-quickstart-se.jarcurl -v http://localhost:8080/observe/healthIf you access the health endpoint before the server has been up for eight seconds, curl reports the response status as 503 Service Unavailable and displays output similar to the following:
{
"status": "DOWN",
"checks": [
{
"name": "warmedUp",
"status": "DOWN",
"data": {
"time": 1702068978353
}
}
]
}The built-in health checks (not shown in the example output) all report UP but the new custom start-up health check reports DOWN because the server has been up only a short time.
Access the health endpoint again, after the server has been up at least eight seconds.
curl -v http://localhost:8080/observe/healthThis time, curl reports 200 OK for the response status and displays different output for the custom health check.
{
"status": "UP",
"checks": [
{
"name": "warmedUp",
"status": "UP",
"data": {
"time": 1702069379717
}
}
]
}The example code includes the built-in health checks in Helidon’s overall health assessment of the application. To exclude them invoke the HealthObserver.Builder useSystemServices method (for example, just after invoking details on the builder).
HealthObserver.builder()
.useSystemServices(false)
.build();Alternatively, you could instead remove the dependency on the helidon-health-checks component from the pom.xml file.
Accessing Specific Health Check Types
You can choose which category of health check to retrieve when you access the health endpoint by adding the health check type as an additional part of the resource path:
liveness only - http://localhost:8080/observe/health/live
readiness only - http://localhost:8080/observe/health/ready
startup only - http://localhost:8080/observe/health/started
curl http://localhost:8080/observe/started{
"status": "UP",
"checks": [
{
"name": "warmedUp",
"status": "UP",
"data": {
"time": 1702069835172
}
}
]
}Applying Configuration to a Custom Health Observer: Customizing the URL path
Earlier examples showed how to add custom health checks by building a custom HealthObserver in which the code set up the behavior of the health subsystem explicitly. Recall that the example code invoked the HealthObserver.Builder details method to turn on detailed output.
Once it creates a custom health observer, your code has full responsibility for determining the observer’s behavior; Helidon does not automatically apply configuration to a custom observer. But your code can easily do so.
The next example customizes the URL path for the health endpoint, first explicitly in the code and then via configuration.
Customizing the endpoint path in the code
Customize the URL path for health checks by invoking the endpoint method on the HealthObserver.Builder.
HealthObserver healthObserver = HealthObserver.builder()
.endpoint("/myhealth")
.build();- Changes the health endpoint path to
/myhealth.
/myhealth:curl http://localhost:8080/myhealthEarlier you added health config to the application.yaml config file to turn on detailed output. If you want to run an experiment, change that details setting in the config file to false and stop, rebuild, and rerun the application. Now access the health endpoint (at /myhealth, remember). The output remains detailed because your code—which has full responsibility for determining the custom health observer’s behavior—does not apply configuration to the custom observer’s builder.
Adding configuration to a custom observer
In addition to preparing the health observer builder with hard-coded settings, your code can also apply configuration for health. This allows someone who deploys your application to control the behavior of the health subsystem using configuration without requiring source code changes to your application.
The generated Main class in the application already creates a Config object for the top-level config node. Using the following code to create the observe feature also applies any health-related configuration settings to the custom health observer. Notice the added line just before the HealthObserver.Build build() invocation near the end of the example code.
HealthObserver healthObserver = HealthObserver.builder()
.config(config.get("server.features.observe.observers.health"))
.build();- Find and apply any health-related settings from configuration at the
server.features.observe.observers.healthconfig key.
Your code decides what config key to use for retrieving the configuration. Recall earlier, before adding custom health checks, you added a config section for health—to set details to true--at server.features.observe.observers.health. Helidon used that configuration to set up the health observer it created automatically. To be consistent for anyone preparing the configuration file, it’s a good idea for your application code—as it prepares a custom HealthObserver--to look in the same place Helidon does for health config.
Order is important. Here, the code first sets details to true explicitly and later applies configuration. If your end user sets details in the server.features.observe.observers.health config to false, that setting overrides the hard-coded true setting in the code because of where in the code you apply the configuration. Try changing the details value to false in the config file and then stop, rebuild, and rerun the application. Access the health endpoint and notice that the output is no longer detailed.
In general, most applications should apply settings from config after assigning any settings in the code so users have the final say, but there might be exceptions in your particular case.
Using Liveness, Readiness, and Startup Health Checks with Kubernetes
The following example shows how to integrate the Helidon health API in an application that implements health endpoints for the Kubernetes liveness, readiness, and startup probes.
readyTime variable to the Main class:private static AtomicLong readyTime = new AtomicLong(0);HealthObserver builder in the Main#main method to use new built-in liveness checks and custom liveness, readiness, and startup checks:ObserveFeature observe = ObserveFeature.builder()
.config(config.get("server.features.observe"))
.addObserver(HealthObserver.builder()
.useSystemServices(true)
.addCheck(() -> HealthCheckResponse.builder()
.status(readyTime.get() != 0)
.detail("time", readyTime.get())
.build(), HealthCheckType.READINESS)
.addCheck(() -> HealthCheckResponse.builder()
.status(readyTime.get() != 0
&& Duration.ofMillis(System.currentTimeMillis()
- readyTime.get())
.getSeconds() >= 3)
.detail("time", readyTime.get())
.build(), HealthCheckType.STARTUP)
.addCheck(() -> HealthCheckResponse.builder()
.status(HealthCheckResponse.Status.UP)
.detail("time", System.currentTimeMillis())
.build(), HealthCheckType.LIVENESS)
.build())
.build();- Add built-in health checks.
- Add a custom readiness check.
- Add a custom start-up check.
- Add a custom liveness check.
curl http://localhost:8080/health/live
curl http://localhost:8080/health/ready
curl http://localhost:8080/health/starteddocker build -t helidon-quickstart-se .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-se
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
---- A service of type
NodePortthat serves the default routes on port8080. - 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.
kubectl apply -f ./health.yamlkubectl get service/helidon-healthNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helidon-health NodePort 10.107.226.62 <none> 8080:30116/TCP 4s - A service of type
NodePortthat serves the default routes on port30116.
curl http://localhost:30116/healthkubectl delete -f ./health.yamlSummary
This guide demonstrates how to use health checks in a Helidon SE application as follows:
Access the default health checks
Create and use custom readiness, liveness, and startup checks
Customize the health check root path
Integrate Helidon health check with Kubernetes
Refer to the following reference for additional information: