skip to content

What does AbstractHealthIndicator give you over implementing HealthIndicator directly, and how does doHealthCheck work?

level: middleimportance: should knowfreq 55%

answer

  1. template method: health() final, doHealthCheck abstract
  2. mutate Health.Builder, throws Exception
  3. auto DOWN + error detail on exception
  4. healthCheckFailedMessage hook
  5. DataSource/Redis/Mongo indicators extend it

basics

~20 s

AbstractHealthIndicator is a base class that implements health() for you and wraps your check in a try/catch. You override doHealthCheck(Health.Builder), and if it throws, the base class automatically sets status DOWN and records the exception.

solid answer

~40 s

AbstractHealthIndicator implements HealthIndicator and provides the boilerplate: its final health() calls the abstract doHealthCheck(Health.Builder builder), builds the result, and — crucially — catches any Exception you throw, setting the status to DOWN and adding the exception as an "error" detail. So you write only the check logic and mutate the passed-in builder (builder.up() or builder.down().withDetail(...)), rather than constructing and returning a Health yourself. It also supports a configurable healthCheckFailedMessage/function for the failure message. This is the recommended base for most custom indicators because it standardises exception handling — you can just let a failing ping throw and trust the base class to translate it into DOWN. All of Spring's own JDBC, Redis, and Mongo indicators extend it. Use plain HealthIndicator only when you want full control over the returned Health.

code

java · 27 lines
java
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.stereotype.Component;

@Component
public class SearchClusterHealthIndicator extends AbstractHealthIndicator {

    private final SearchClient client;

    public SearchClusterHealthIndicator(SearchClient client) {
        super("Search cluster health check failed"); // logged on failure
        this.client = client;
    }

    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        // Just let a failure throw; the base class turns it into DOWN + error detail.
        ClusterStats stats = client.clusterStats();
        if (stats.activeShards() == stats.totalShards()) {
            builder.up().withDetail("shards", stats.totalShards());
        } else {
            builder.status("DEGRADED") // custom status
                   .withDetail("activeShards", stats.activeShards())
                   .withDetail("totalShards", stats.totalShards());
        }
    }
}

go deeper

for a junior

Know it's a base class where you override doHealthCheck and don't build the Health yourself.

for a middle

Explain the template-method design, auto-DOWN-on-exception, the builder mutation, and that Spring's own indicators extend it.

for a senior

Discuss when it's the wrong tool (non-DOWN failure mapping), the failure-message hook, and lack of built-in timeouts.

for a principal

Weigh consistency benefits of a shared base vs. explicit control; standardise custom-indicator patterns and failure semantics across teams.

## The problem it solves When you implement `HealthIndicator` directly, you're responsible for wrapping your probe in a `try/catch` and translating exceptions into a `Health.down()` result. Forget the try/catch and a thrown exception still ends up as DOWN (Actuator catches it upstream), but you lose control over the details. `AbstractHealthIndicator` (in `org.springframework.boot.actuate.health`) standardises this. ## How it works `AbstractHealthIndicator` implements `HealthIndicator`. Its `health()` is effectively: ```java public final Health health() { Health.Builder builder = new Health.Builder(); try { doHealthCheck(builder); } catch (Exception ex) { // logs, then: builder.down(ex); // sets DOWN + records the exception message/details } return builder.build(); } ``` You override the single **abstract** method: ```java protected abstract void doHealthCheck(Health.Builder builder) throws Exception; ``` Key differences from raw `HealthIndicator`: - You **mutate a passed-in `Health.Builder`** instead of building and returning your own `Health`. - The method **declares `throws Exception`** — you are encouraged to let failures propagate; the base class turns them into DOWN. - Exception handling is centralised, so every indicator behaves consistently. ## The failure message hook `AbstractHealthIndicator` has a constructor that accepts a `healthCheckFailedMessage` String, and a `setHealthCheckFailedMessage(Function<Exception,String>)` to compute a message per exception. This message is logged when the check fails, aiding diagnosis without exposing internals to the HTTP response. ## Details on failure When `builder.down(ex)` runs, it sets status DOWN and adds an `"error"` detail of the form `"<ExceptionClass>: <message>"`. Whether that reaches the HTTP body still depends on `management.endpoint.health.show-details`. ## Who uses it Spring's own indicators extend it: `DataSourceHealthIndicator`, `RedisHealthIndicator`, `MongoHealthIndicator`, `DiskSpaceHealthIndicator`, etc. That's a strong signal it's the intended base for custom checks. ## When NOT to use it - If you want to return a **non-DOWN** status on failure (e.g., OUT_OF_SERVICE for a soft dependency), the auto-DOWN-on-exception behaviour fights you — implement `HealthIndicator` directly and control the mapping. - For **reactive** applications, use `AbstractReactiveHealthIndicator` instead (its `doHealthCheck` returns `Mono<Health>`). ## Gotchas - `doHealthCheck` must still be **cheap and fast** — the base class does nothing about timeouts. - Don't swallow the exception yourself and return silently UP; the whole point is to let it propagate. - The builder is fresh per call; don't cache it.

  • You want a failing soft dependency to report OUT_OF_SERVICE, not DOWN. Is AbstractHealthIndicator a good fit?
    No. Its health() forces DOWN on any thrown exception, so you can't easily map failure to OUT_OF_SERVICE. Implement HealthIndicator directly and catch/translate yourself.
  • Does AbstractHealthIndicator add a timeout around your check?
    No. It only centralises exception-to-DOWN handling. You must add your own timeout/circuit breaker inside doHealthCheck to keep probes fast.

saying these in an interview costs you the question

  • Claiming doHealthCheck returns a Health (it returns void and mutates the builder)
  • Saying AbstractHealthIndicator is for reactive apps (that's AbstractReactiveHealthIndicator)
  • Thinking it adds timeouts or retries automatically
  • Wrapping doHealthCheck body in your own try/catch that swallows the exception, defeating the base class

context