MicroProfile Health vs Spring Boot Actuator: What's the Difference?
The Problem
I was migrating a Java microservices application to Kubernetes and needed to implement health checks. My team had a mix of Jakarta EE services running on WildFly and Spring Boot services. I kept getting confused about which health check framework to use.
When I deployed to Kubernetes, my pods kept restarting because the health probes were failing:
kubectl describe pod my-service-6c8b9d4f-2x7kl...Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning Unhealthy 12s (x3 over 32s) kubelet Liveness probe failed: HTTP probe failed with statuscode: 404 Warning Unhealthy 1s (x2 over 11s) kubelet Readiness probe failed: HTTP probe failed with statuscode: 404I had configured the probes to hit /health but my Spring Boot application wasn’t exposing that endpoint by default. Meanwhile, my MicroProfile service was returning 200 OK but Kubernetes was still restarting it. What was going on?
Environment
- Java 21
- Spring Boot 3.2.1
- MicroProfile Health 4.0
- WildFly 27.0.1
- Kubernetes 1.28
- Docker 24.0
What I Discovered
I started by checking both frameworks to understand the differences. Here’s what I found.
MicroProfile Health Approach
MicroProfile Health uses annotations to define health checks. You implement the HealthCheck interface and annotate your class:
@Liveness@ApplicationScopedpublic class AppHealthCheck implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.up("application"); }}The framework automatically discovers these beans and exposes them at:
/health/livefor liveness probes/health/readyfor readiness probes/health/startedfor startup probes
Spring Boot Actuator Approach
Spring Boot Actuator takes a different approach. You implement HealthIndicator as a Spring component:
@Componentpublic class AppHealthIndicator implements HealthIndicator { @Override public Health health() { return Health.up().build(); }}But here’s the catch: Actuator endpoints are not fully exposed by default. I had to configure them:
management: endpoints: web: exposure: include: health endpoint: health: probes: enabled: trueThe Key Differences
I spent time comparing both approaches and documented the differences:
1. Configuration Style
MicroProfile uses annotations to specify the probe type:
@Readiness@ApplicationScopedpublic class DatabaseHealthCheck implements HealthCheck { @Inject private DataSource dataSource;
@Override public HealthCheckResponse call() { try (Connection conn = dataSource.getConnection()) { return HealthCheckResponse.up("database"); } catch (SQLException e) { return HealthCheckResponse.down("database"); } }}Spring Boot uses separate interfaces for each probe type:
@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()) { return Health.up().build(); } catch (SQLException e) { return Health.down().withException(e).build(); } }}For reactive probes in Spring Boot, I needed to use ReactiveHealthIndicator:
@Componentpublic class ReactiveDatabaseHealthIndicator implements ReactiveHealthIndicator { private final ReactiveDataSource reactiveDataSource;
@Override public Mono<Health> health() { return reactiveDataSource.getConnection() .map(conn -> Health.up().build()) .onErrorResume(e -> Mono.just(Health.down().withException(e).build())); }}2. Response Format
Both frameworks return JSON, but the structure differs:
{ "status": "UP", "checks": [ { "name": "database", "status": "UP", "data": { "connection": "postgresql" } }, { "name": "application", "status": "UP" } ]}{ "status": "UP", "components": { "db": { "status": "UP", "details": { "database": "PostgreSQL", "validationQuery": "isValid()" } }, "diskSpace": { "status": "UP", "details": { "total": 536608768000, "free": 384640409600, "threshold": 10485760 } } }}3. Scope
This is where the biggest difference lies. MicroProfile Health is focused solely on health checks. Spring Boot Actuator is a full observability framework:
/actuator/metrics - Application metrics/actuator/env - Environment properties/actuator/loggers - Logging configuration/actuator/threaddump - Thread dump/actuator/heapdump - Heap dump/actuator/prometheus - Prometheus metrics export4. Auto-Configuration
Spring Boot Actuator auto-configures health indicators for common dependencies. When I added PostgreSQL to my project, it automatically created a database health check. MicroProfile requires manual implementation.
I found this out when my Spring Boot application exposed health details I didn’t want:
curl http://localhost:8080/actuator/healthIt showed database connection details, disk space info, and even the Git commit hash. I had to secure it:
management: endpoint: health: show-details: neverHow I Solved the Kubernetes Probe Issue
Going back to my original problem, I realized I had two issues:
- Spring Boot wasn’t exposing health endpoints by default
- MicroProfile was returning UP even when dependencies were down
For Spring Boot, I added the configuration:
management: endpoints: web: exposure: include: health endpoint: health: probes: enabled: true show-details: never group: liveness: include: ping readiness: include: db, redisFor MicroProfile, I made sure my readiness check actually validated dependencies:
@Readiness@ApplicationScopedpublic class DatabaseReadinessCheck implements HealthCheck { @Inject @Named("primaryDataSource") private DataSource dataSource;
@Override public HealthCheckResponse call() { try (Connection conn = dataSource.getConnection()) { boolean valid = conn.isValid(5); return HealthCheckResponse.builder() .name("database") .status(valid) .withData("connectionPool", "HikariCP") .build(); } catch (SQLException e) { return HealthCheckResponse.builder() .name("database") .down() .withData("error", e.getMessage()) .build(); } }}Then I updated my Kubernetes deployment:
livenessProbe: httpGet: path: /health/live # MicroProfile # path: /actuator/health/liveness # Spring Boot port: 8080 initialDelaySeconds: 30 periodSeconds: 10
readinessProbe: httpGet: path: /health/ready # MicroProfile # path: /actuator/health/readiness # Spring Boot port: 8080 initialDelaySeconds: 5 periodSeconds: 5When to Choose Which Framework
After this experience, I have clear guidelines:
Choose MicroProfile Health when:
- Building Jakarta EE applications
- Running on WildFly, Open Liberty, or Payara
- Need a focused health check solution only
- Want annotation-based configuration
- Prefer CDI integration
Choose Spring Boot Actuator when:
- Building Spring Boot applications
- Need comprehensive observability (metrics, env, loggers)
- Want auto-configured health indicators
- Need Prometheus integration
- Prefer Spring’s component model
Common Mistakes I Made
-
Assuming Actuator health endpoints are exposed by default - They’re secured. You need explicit configuration.
-
Not reviewing auto-configured health indicators - Actuator exposes details you might not want public. Use
show-details: neverin production. -
Using reactive Spring without ReactiveHealthIndicator - If your app uses WebFlux, you need the reactive variant.
-
Implementing only liveness checks - Kubernetes needs both liveness and readiness probes for proper traffic management.
-
Health checks that never fail - A health check that always returns UP is worse than no health check.
Summary
MicroProfile Health and Spring Boot Actuator solve the same problem but with different philosophies. MicroProfile Health is focused and annotation-driven, perfect for Jakarta EE applications. Spring Boot Actuator is comprehensive and auto-configuring, ideal for Spring ecosystems.
Both frameworks support Kubernetes probes with identical semantics (liveness, readiness, startup). The choice comes down to your technology stack and whether you need just health checks or full observability features.
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:
- 👨💻 MicroProfile Health Specification
- 👨💻 Spring Boot Actuator Documentation
- 👨💻 Kubernetes Liveness and Readiness Probes
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments