Architecturally, how does Actuator wire up these built-in indicators, and how does HealthContributor relate to HealthIndicator and readiness/liveness probes?
answer
- HealthContributor = HealthIndicator (leaf) | CompositeHealthContributor (tree)
- AbstractHealthIndicator wraps exceptions → DOWN
- @ConditionalOnEnabledHealthIndicator reads the toggles
- Registry → StatusAggregator → HttpCodeStatusMapper
- groups → /health/readiness & /health/liveness via ApplicationAvailability
basics
~20 sEach built-in indicator has its own auto-configuration (e.g. DataSourceHealthContributorAutoConfiguration) that conditionally registers a HealthContributor. HealthIndicator is a single HealthContributor; a CompositeHealthContributor groups several. Kubernetes probes are served via health groups (liveness/readiness) built on the same registry.
solid answer
~40 sBuilt-in indicators are registered by dedicated auto-configurations guarded by @ConditionalOnClass/@ConditionalOnBean plus @ConditionalOnEnabledHealthIndicator (which reads the management.health.<name>.enabled and defaults.enabled properties). Each contributes a HealthContributor: a HealthIndicator is the leaf (returns one Health), while a CompositeHealthContributor is a named tree of contributors (used when e.g. there are multiple DataSources). All contributors live in a HealthContributorRegistry that the health endpoint walks, applying a StatusAggregator and HttpCodeStatusMapper. Health groups (management.endpoint.health.group.<name>.include/exclude) produce sub-endpoints like /health/readiness and /health/liveness; with probes enabled Spring adds LivenessStateHealthIndicator and ReadinessStateHealthIndicator backed by ApplicationAvailability. The reactive stack mirrors this with ReactiveHealthContributor/ReactiveHealthIndicator. This layering is what lets you disable, group, and remap indicators declaratively.
code
java · 18 lines// A custom leaf contributor plugs into the same registry/aggregator machinery.
@Component
class PaymentGatewayHealthIndicator extends AbstractHealthIndicator {
private final PaymentClient client;
PaymentGatewayHealthIndicator(PaymentClient client) { this.client = client; }
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
// Any thrown exception is turned into DOWN by AbstractHealthIndicator.
var status = client.ping(); // your call
builder.up().withDetail("gateway", status.name());
}
}
// Put it in readiness so traffic stops if the gateway is unreachable,
// but keep it OUT of liveness so the pod isn't restarted for a gateway blip:
// management.endpoint.health.group.readiness.include=db,paymentGateway,readinessState
// management.endpoint.health.group.liveness.include=livenessStatego deeper
Aware that indicators are auto-wired and there are readiness/liveness endpoints.
Distinguish HealthIndicator vs Composite and know groups produce /health/readiness and /health/liveness.
Explain conditional auto-config (@ConditionalOnEnabledHealthIndicator), the registry→aggregator→mapper pipeline, and ApplicationAvailability-backed probes.
Design the readiness-vs-liveness contribution policy across services and leverage the contributor/aggregator/mapper separation to make health declaratively composable.
## The type hierarchy Actuator's health model has two abstractions: - **`HealthContributor`** — the umbrella type registered with the endpoint. It has two shapes: - **`HealthIndicator`** — a *leaf* contributor; its `health()` returns a single `Health`. Most built-ins (`PingHealthIndicator`, `DiskSpaceHealthIndicator`, `DataSourceHealthIndicator`, `RedisHealthIndicator`) extend `AbstractHealthIndicator`, which wraps `doHealthCheck(Health.Builder)` with exception handling (so a thrown exception becomes `DOWN` instead of a 500). - **`CompositeHealthContributor`** — a *named tree* of child contributors, iterable by name. Used when one concept has several instances (e.g. multiple `DataSource` beans → each reported under its bean name). - The **reactive** stack mirrors this: **`ReactiveHealthContributor`** / **`ReactiveHealthIndicator`** returning `Mono<Health>` (WebFlux apps). ## How built-ins get registered Each built-in has its own **auto-configuration** class, e.g. `DiskSpaceHealthContributorAutoConfiguration`, `DataSourceHealthContributorAutoConfiguration`, `RedisHealthContributorAutoConfiguration`, `PingHealthContributorAutoConfiguration`. They are guarded by conditions: - **`@ConditionalOnClass`** — the supporting library is present (e.g. `RedisConnectionFactory`). - **`@ConditionalOnBean`** — the prerequisite bean exists (e.g. a `DataSource`). - **`@ConditionalOnEnabledHealthIndicator("<name>")`** — a meta-condition that reads `management.health.<name>.enabled`, falling back to `management.health.defaults.enabled`. This is exactly the toggle mechanism from earlier questions, implemented as a bean condition. Registered contributors are collected into a **`HealthContributorRegistry`** (and `ReactiveHealthContributorRegistry`). The `HealthEndpoint` walks that registry on each request, recursively descending into composites. ## Aggregation and HTTP mapping (the endpoint's job) - **`StatusAggregator`** (default `SimpleStatusAggregator`) collapses the tree to one `Status` by worst-severity ordering; customizable via `management.endpoint.health.status.order`. - **`HttpCodeStatusMapper`** maps that `Status` → HTTP code (`UP`→200, `DOWN`/`OUT_OF_SERVICE`→503 by default); customizable via `management.endpoint.health.status.http-mapping.*`. ## Health groups and Kubernetes probes **Health groups** let you carve subsets of contributors into their own endpoints: ``` management.endpoint.health.group.readiness.include=db,readinessState management.endpoint.health.group.liveness.include=livenessState ``` These surface as `/actuator/health/readiness` and `/actuator/health/liveness`. Each group can have its own `StatusAggregator`, `HttpCodeStatusMapper`, and `show-details`. When **probes** are enabled (auto-enabled on Kubernetes, or `management.endpoint.health.probes.enabled=true`), Spring adds **`LivenessStateHealthIndicator`** and **`ReadinessStateHealthIndicator`**, backed by **`ApplicationAvailability`**. Application code can publish `AvailabilityChangeEvent`s to flip `LivenessState`/`ReadinessState` (e.g. mark itself `REFUSING_TRAFFIC` during graceful shutdown), and the probe endpoints reflect that. This decouples *process liveness* (should Kubernetes restart me?) from *readiness* (should I receive traffic?). ## Why the architecture matters (principal lens) - The contributor/registry/aggregator/mapper split is what makes health **declaratively composable**: you disable via a condition, regroup via group includes, and remap severity/HTTP without touching indicator code. - It cleanly separates *what a check measures* (indicator) from *what the outcome means operationally* (which group it's in, how it maps to HTTP). That separation is the key design lesson: put only traffic-gating dependencies in readiness, keep restart-worthy conditions in liveness, and expose everything else on the main endpoint for observability. - Custom indicators plug in by implementing `HealthIndicator`/`AbstractHealthIndicator` (or the reactive variants) and are auto-discovered and slotted into the same machinery.
- Why put a dependency check in readiness but not liveness?Liveness answers 'should the orchestrator restart this process?' — restarting won't fix an external dependency outage and just causes crash loops. Readiness answers 'should this instance receive traffic?' — so a downstream outage should remove it from rotation without killing it. Built-in dependency indicators therefore belong in readiness, while liveness stays tied to intrinsic process health.
- How does AbstractHealthIndicator prevent a thrown exception from becoming an HTTP 500?It calls doHealthCheck in a try/catch; any exception is caught and the builder is set to DOWN with the error as a detail, so the endpoint still returns a well-formed health response (aggregated, mapped to 503) rather than an error page.
saying these in an interview costs you the question
- Confusing HealthIndicator (leaf) with HealthContributor (umbrella including composites)
- Thinking liveness and readiness should include the same dependency checks
- Believing you must write code to disable/regroup indicators (it's all declarative properties)