Skip to content
Go back

Health probes in Java Spring Boot done right

Posted on:April 4, 2023 at 09:55 AM (3 min read)

Table of contents

Open Table of contents

Health probes

Health probes are used by Kubernetes to monitor your services health. There is different kinds of probes that can be set up.

Liveness probe

Used for detecting applications in broken states. Kubernetes will restart applications that reports negatively on liveness.

Readiness probe

Used for detecting applications which is not able to serve traffic temporarily. Kubernetes won’t kill applications that reports negatively on readiness, but it will not send traffic to it until it’s ready again.

Startup probe

Used for detecting if the application is ready to serve traffic. This probe is only used after starting up an application, and will not be used after it’s started and the startup probe has reported positively.

To read more in detail about the probes, I refer to the kubernetes documentation.

Serving the probes

Kubernetes has different ways of configuring how the probes should query the application. A common way is using http by making a http request towards the application which responds with the status.

Spring Boot Actuator

Provides endpoints which serves the health probes out of the box. Those endpoints will use Spring:s built in availability functionality and read from it when responding on the endpoints. I suggest to use this functionality rather than implementing your own.

A part from providing the above functionality you can also serve these endpoints on a separate port. Imagine that your service is overloaded and you have no threads able to serve requests. Kubernetes is trying to query your liveness endpoint but it does not respond in time, so it proceeds to restart your service. You don’t want that. By serving these requests on a separate port, they are not affected by the regular traffic that’s reaching your service. This is because these requests will be served by a different threadpool.

This can be configured using application properties:

management:
  server:
    port: 9090
  endpoint:
    health:
      probes:
        enabled: true
  health:
    livenessState:
      enabled: true
    readinessState:
      enabled: true
  endpoints:
    web:
      exposure:
        include: health

You can of course change the port to whatever suits your needs. The rest of the configuration just exposes the health endpoints, while making sure no other endpoints is exposed by Spring Boot Actuator.

The liveness endpoint will be exposed at /actuator/health/liveness & the readiness at /actuator/health/readiness.

Kubernetes probe definitions

This is an example of a liveness probe definition in Kubernetes:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
    - name: liveness
      image: registry.k8s.io/liveness
      livenessProbe:
        httpGet:
          path: /actuator/health/liveness
          port: 9090
        initialDelaySeconds: 3
        periodSeconds: 3

It calls the liveness endpoint exposed by Spring Boot Actuator on port 9090 that was defined with application properties above. Those http requests will now be served on a separate threadpool than the regular requests reaching the service.

Modifying liveness and readiness state

As touched upon above Spring Boot provides an easy way to modify the liveness & readiness states. Here’s an example on how you could do that:

@ControllerAdvice
public class ApplicationExceptionHandler {
    private final ApplicationEventPublisher eventPublisher;

    public ApplicationExceptionHandler(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @ExceptionHandler(CacheIsCompletelyBrokenException.class)
    public void handleCacheIsCompletelyBrokenException(CacheIsCompletelyBrokenException ex) {
        AvailabilityChangeEvent.publish(this.eventPublisher, ex, LivenessState.BROKEN);
        throw ex;
    }

    @ExceptionHandler(ApplicationIsOverwhelmedException.class)
    public void handleApplicationIsOverwhelmedException(ApplicationIsOverwhelmedException ex) {
        AvailabilityChangeEvent.publish(this.eventPublisher, ex, ReadinessState.REFUSING_TRAFFIC);
        throw ex;
    }
}

If the application recovers, you can change the state back again:

AvailabilityChangeEvent.publish(this.eventPublisher, "Application available for work again", ReadinessState.ACCEPTING_TRAFFIC);

Which will in turn make the /actuator/health/readiness endpoint report positively again.