Spring Boot
Kubernetes
Readiness Probe
Custom Probes
Microservices

Spring Boot custom Kubernetes readiness probe

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Kubernetes readiness probes determine whether a pod is ready to accept traffic. When a readiness probe fails, the pod is removed from the Service endpoints, so no new requests are routed to it. Spring Boot provides built-in Actuator health infrastructure that integrates directly with Kubernetes probes, and you can extend it with custom health checks to cover your application's specific dependencies.

Default Readiness Support in Spring Boot

Starting with Spring Boot 2.3, the framework natively supports Kubernetes probe endpoints. When you add the Actuator dependency and enable the probe endpoints, Spring Boot exposes /actuator/health/readiness and /actuator/health/liveness automatically.

Add the dependency to your pom.xml:

xml
1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-starter-actuator</artifactId>
4</dependency>

Then enable the probe endpoints in application.yml:

yaml
1management:
2  endpoint:
3    health:
4      probes:
5        enabled: true
6      show-details: always
7  health:
8    readinessstate:
9      enabled: true
10    livenessstate:
11      enabled: true

With this configuration, Spring Boot reports the readiness state based on the application lifecycle. The readiness endpoint returns UP once the application context is fully loaded and all startup tasks are complete.

Creating a Custom HealthIndicator

The default readiness probe only checks the application lifecycle state. In production, you often need to verify that downstream dependencies such as databases, message brokers, or external APIs are reachable. You do this by implementing a custom HealthIndicator.

java
1import org.springframework.boot.actuate.health.Health;
2import org.springframework.boot.actuate.health.HealthIndicator;
3import org.springframework.stereotype.Component;
4
5@Component
6public class DatabaseHealthIndicator implements HealthIndicator {
7
8    private final DataSource dataSource;
9
10    public DatabaseHealthIndicator(DataSource dataSource) {
11        this.dataSource = dataSource;
12    }
13
14    @Override
15    public Health health() {
16        try (Connection conn = dataSource.getConnection()) {
17            if (conn.isValid(2)) {
18                return Health.up()
19                    .withDetail("database", "reachable")
20                    .build();
21            }
22        } catch (SQLException e) {
23            return Health.down()
24                .withDetail("database", "unreachable")
25                .withException(e)
26                .build();
27        }
28        return Health.down()
29            .withDetail("database", "connection invalid")
30            .build();
31    }
32}

Spring Boot automatically includes all HealthIndicator beans in the readiness group. When any indicator reports DOWN, the readiness endpoint returns a 503 status, causing Kubernetes to stop routing traffic to that pod.

Grouping Health Indicators

You can control exactly which indicators belong to the readiness probe by defining health groups:

yaml
1management:
2  endpoint:
3    health:
4      group:
5        readiness:
6          include: readinessState, db, redis, customExternal
7        liveness:
8          include: livenessState

This configuration ensures that the readiness endpoint checks the database, Redis, and a custom external service, while the liveness probe only checks whether the application process is alive. Keeping liveness checks lightweight prevents Kubernetes from killing pods due to slow dependency responses.

Programmatic Readiness State Changes

Spring Boot 2.3+ also lets you change the readiness state programmatically. This is useful when your application needs to temporarily stop accepting traffic, for example during a graceful shutdown or a cache warming phase.

java
1import org.springframework.boot.availability.AvailabilityChangeEvent;
2import org.springframework.boot.availability.ReadinessState;
3import org.springframework.context.ApplicationEventPublisher;
4import org.springframework.stereotype.Service;
5
6@Service
7public class TrafficController {
8
9    private final ApplicationEventPublisher eventPublisher;
10
11    public TrafficController(ApplicationEventPublisher eventPublisher) {
12        this.eventPublisher = eventPublisher;
13    }
14
15    public void acceptTraffic() {
16        AvailabilityChangeEvent.publish(
17            eventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC);
18    }
19
20    public void refuseTraffic() {
21        AvailabilityChangeEvent.publish(
22            eventPublisher, this, ReadinessState.REFUSING_TRAFFIC);
23    }
24}

Kubernetes Deployment Configuration

Once your Spring Boot application exposes the readiness endpoint, configure the Kubernetes deployment manifest to use it:

yaml
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: my-service
5spec:
6  replicas: 3
7  template:
8    spec:
9      containers:
10        - name: my-service
11          image: my-service:latest
12          ports:
13            - containerPort: 8080
14          readinessProbe:
15            httpGet:
16              path: /actuator/health/readiness
17              port: 8080
18            initialDelaySeconds: 10
19            periodSeconds: 5
20            failureThreshold: 3
21            successThreshold: 1
22          livenessProbe:
23            httpGet:
24              path: /actuator/health/liveness
25              port: 8080
26            initialDelaySeconds: 30
27            periodSeconds: 10
28            failureThreshold: 5

Key parameters:

  • initialDelaySeconds gives the application time to start before the first probe runs.
  • periodSeconds controls how often Kubernetes sends the probe request.
  • failureThreshold sets how many consecutive failures trigger the pod to be marked not ready.

Common Pitfalls

  • Putting slow checks in the liveness probe: If a database timeout causes the liveness probe to fail, Kubernetes restarts the pod instead of just removing it from traffic. Keep liveness checks fast and dependency-free.
  • Missing the Actuator dependency: Without spring-boot-starter-actuator, the /actuator/health/readiness endpoint does not exist and Kubernetes probes return 404.
  • Setting initialDelaySeconds too low: If the probe fires before Spring Boot finishes loading, the pod gets marked not ready repeatedly and may enter a restart loop.
  • Exposing health details to untrusted networks: Setting show-details: always reveals internal dependency information. In production, use show-details: when-authorized and secure the management endpoints.
  • Not separating readiness and liveness groups: Using the same health checks for both probes causes Kubernetes to restart pods for transient dependency failures instead of just pausing traffic.

Summary

  • Spring Boot 2.3+ natively supports Kubernetes readiness and liveness probe endpoints through Actuator.
  • Implement HealthIndicator beans to add custom dependency checks to the readiness probe.
  • Use health groups to control which indicators belong to readiness versus liveness.
  • Configure initialDelaySeconds, periodSeconds, and failureThreshold in your Kubernetes manifest to match your application startup time.
  • Keep liveness probes lightweight and reserve dependency checks for readiness probes to avoid unnecessary pod restarts.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.