How to Migrate from MicroProfile Health to Spring Boot Actuator
Problem
I was migrating a Java microservices application from Quarkus to Spring Boot. Everything was going smoothly until I hit the health check endpoints. My Kubernetes probes started failing with 404 errors, and my custom health checks were nowhere to be found.
Warning Unhealthy 2m (x10 over 3m) kubelet Liveness probe failed: HTTP status 404Warning Unhealthy 2m (x10 over 3m) kubelet Readiness probe failed: HTTP status 404The old application used MicroProfile Health with @Liveness, @Readiness, and @Startup annotations. Spring Boot uses a completely different approach with HealthIndicator beans and the Actuator framework.
Environment
- Spring Boot 3.x
- Kubernetes 1.28
- Java 17
- Previously: Quarkus 3.x with MicroProfile Health
The Migration Challenge
MicroProfile Health and Spring Boot Actuator serve the same purpose but use different implementations. Here’s the mapping I discovered:
MicroProfile Spring Boot Actuator------------- --------------------HealthCheck interface -> HealthIndicator interface@Liveness annotation -> HealthIndicator bean (liveness group)@Readiness annotation -> HealthIndicator bean (readiness group)@Startup annotation -> HealthIndicator bean (startup group)/health/live -> /actuator/health/liveness/health/ready -> /actuator/health/readinessHealthCheckResponse -> Health classBefore: MicroProfile Health Check
This was my original database health check in Quarkus:
@Readiness@ApplicationScopedpublic class DatabaseCheck implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.named("database") .withData("connection", "active") .withData("poolSize", "10") .up() .build(); }}The @Readiness annotation automatically registered this as a readiness probe. I also had a liveness check:
@Liveness@ApplicationScopedpublic class AppLivenessCheck implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.named("app-liveness") .up() .build(); }}After: Spring Boot Health Indicator
After migration, I converted these to Spring Boot HealthIndicator beans:
@Componentpublic class DatabaseHealthIndicator implements HealthIndicator { @Override public Health health() { return Health.up() .withDetail("connection", "active") .withDetail("poolSize", "10") .build(); }}@Componentpublic class AppLivenessHealthIndicator implements HealthIndicator { @Override public Health health() { return Health.up().build(); }}But wait, how does Spring Boot know which one is liveness and which one is readiness?
Solution
Step 1: Configure Health Groups
Spring Boot 2.2+ supports health groups for liveness and readiness probes. I added this configuration:
management: endpoint: health: show-details: when-authorized probes: enabled: true health: livenessstate: enabled: true readinessstate: enabled: true startupstate: enabled: trueWait, but how do I assign my custom HealthIndicator beans to specific groups?
Step 2: Auto-Registration vs Manual Group Assignment
I discovered two approaches:
Approach A: Use Built-in Health Contributors
Spring Boot automatically includes LivenessStateHealthIndicator and ReadinessStateHealthIndicator when you enable the probes. These check the application’s lifecycle state:
@RestControllerpublic class HealthController { private final ApplicationEventPublisher eventPublisher;
public HealthController(ApplicationEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; }
@PostMapping("/ready") public void markReady() { eventPublisher.publishEvent(new ReadinessStateChangedEvent(this, ReadinessState.ACCEPTING_TRAFFIC)); }
@PostMapping("/not-ready") public void markNotReady() { eventPublisher.publishEvent(new ReadinessStateChangedEvent(this, ReadinessState.REFUSING_TRAFFIC)); }}Approach B: Custom Health Indicators in Groups
For custom health checks that should affect readiness, I configured them explicitly:
management: endpoint: health: show-details: when-authorized group: readiness: include: "databaseHealthIndicator,redisHealthIndicator" liveness: include: "pingHealthIndicator"Step 3: Update Kubernetes Probe Paths
This was the critical step I initially missed. The endpoint paths changed:
# BEFORE (MicroProfile)livenessProbe: httpGet: path: /health/live port: 8080readinessProbe: httpGet: path: /health/ready port: 8080
# AFTER (Spring Boot Actuator)livenessProbe: httpGet: path: /actuator/health/liveness port: 8080readinessProbe: httpGet: path: /actuator/health/readiness port: 8080startupProbe: httpGet: path: /actuator/health/startup port: 8080Step 4: Configure Security
Here’s where things got tricky. Spring Boot Actuator endpoints are secured by default. My probes were now returning 401 Unauthorized instead of 404 Not Found:
Warning Unhealthy 30s kubelet Liveness probe failed: HTTP status 401I had to configure security to allow anonymous access to health endpoints:
@Configurationpublic class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .requestMatchers("/actuator/health/**").permitAll() .anyRequest().authenticated() ); return http.build(); }}Alternatively, you can configure actuator to expose health without authentication:
management: endpoints: web: exposure: include: health endpoint: health: show-details: neverCommon Mistakes
I made several mistakes during this migration:
-
Forgot to update probe paths: The Kubernetes deployment still pointed to
/health/liveinstead of/actuator/health/liveness. -
Missing actuator dependency: I had to add the actuator starter:
dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator'}-
Security blocked probes: Actuator endpoints return 401 by default. I needed to permit unauthenticated access to health endpoints.
-
Wrong health indicator naming: Spring Boot uses
HealthIndicatorsuffix convention. MyDatabaseCheckneeded to becomeDatabaseHealthIndicator. -
Missing health group configuration: Without explicit group configuration, all health indicators contribute to the main health endpoint, not liveness/readiness probes.
Complete Working Example
Here’s my final configuration for a complete migration:
@Componentpublic class DatabaseHealthIndicator implements HealthIndicator { private final DataSource dataSource;
public DatabaseHealthIndicator(DataSource dataSource) { this.dataSource = dataSource; }
@Override public Health health() { try (Connection conn = dataSource.getConnection()) { if (conn.isValid(2)) { return Health.up() .withDetail("database", "PostgreSQL") .withDetail("validationQuery", "SELECT 1") .build(); } } catch (SQLException e) { return Health.down() .withException(e) .withDetail("error", e.getMessage()) .build(); } return Health.down().withDetail("reason", "Unknown").build(); }}management: endpoints: web: exposure: include: health endpoint: health: show-details: when-authorized probes: enabled: true health: livenessstate: enabled: true readinessstate: enabled: true group: readiness: include: "databaseHealthIndicator"apiVersion: apps/v1kind: Deploymentspec: template: spec: containers: - name: app livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 5 periodSeconds: 5Summary
Migrating from MicroProfile Health to Spring Boot Actuator requires understanding the conceptual mapping between the two frameworks. The key steps are:
- Replace
HealthCheckinterface withHealthIndicatorinterface - Convert
@Liveness,@Readiness,@Startupannotations to@Componentbeans with health groups - Update
HealthCheckResponsebuilder to Spring’sHealthbuilder - Change Kubernetes probe paths from
/health/*to/actuator/health/* - Configure security to permit anonymous access to health endpoints
The concepts transfer well - liveness, readiness, and startup probes exist in both frameworks. The implementation differs, but the mental model remains the same. Once you understand the mapping, the migration is straightforward.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Spring Boot Actuator Documentation
- 👨💻 MicroProfile Health Specification
- 👨💻 Kubernetes Probes Configuration
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments