Skip to content

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 output
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: 404

I 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:

AppHealthCheck.java
@Liveness
@ApplicationScoped
public class AppHealthCheck implements HealthCheck {
@Override
public HealthCheckResponse call() {
return HealthCheckResponse.up("application");
}
}

The framework automatically discovers these beans and exposes them at:

  • /health/live for liveness probes
  • /health/ready for readiness probes
  • /health/started for startup probes

Spring Boot Actuator Approach

Spring Boot Actuator takes a different approach. You implement HealthIndicator as a Spring component:

AppHealthIndicator.java
@Component
public 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:

application.yml
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
probes:
enabled: true

The Key Differences

I spent time comparing both approaches and documented the differences:

1. Configuration Style

MicroProfile uses annotations to specify the probe type:

DatabaseHealthCheck.java
@Readiness
@ApplicationScoped
public 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:

DatabaseHealthIndicator.java
@Component
public 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:

ReactiveDatabaseHealthIndicator.java
@Component
public 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:

MicroProfile Health Response
{
"status": "UP",
"checks": [
{
"name": "database",
"status": "UP",
"data": {
"connection": "postgresql"
}
},
{
"name": "application",
"status": "UP"
}
]
}
Spring Boot Actuator Health Response
{
"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 Additional Endpoints
/actuator/metrics - Application metrics
/actuator/env - Environment properties
/actuator/loggers - Logging configuration
/actuator/threaddump - Thread dump
/actuator/heapdump - Heap dump
/actuator/prometheus - Prometheus metrics export

4. 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 health endpoint
curl http://localhost:8080/actuator/health

It showed database connection details, disk space info, and even the Git commit hash. I had to secure it:

application.yml
management:
endpoint:
health:
show-details: never

How I Solved the Kubernetes Probe Issue

Going back to my original problem, I realized I had two issues:

  1. Spring Boot wasn’t exposing health endpoints by default
  2. MicroProfile was returning UP even when dependencies were down

For Spring Boot, I added the configuration:

application.yml
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
probes:
enabled: true
show-details: never
group:
liveness:
include: ping
readiness:
include: db, redis

For MicroProfile, I made sure my readiness check actually validated dependencies:

DatabaseReadinessCheck.java
@Readiness
@ApplicationScoped
public 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:

deployment.yml
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: 5

When 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

  1. Assuming Actuator health endpoints are exposed by default - They’re secured. You need explicit configuration.

  2. Not reviewing auto-configured health indicators - Actuator exposes details you might not want public. Use show-details: never in production.

  3. Using reactive Spring without ReactiveHealthIndicator - If your app uses WebFlux, you need the reactive variant.

  4. Implementing only liveness checks - Kubernetes needs both liveness and readiness probes for proper traffic management.

  5. 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:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments