skip to content

A custom HealthIndicator returns Health.status("DEGRADED"). Explain how that status affects the endpoint's overall status and HTTP code, and how to surface the details.

level: middleimportance: should knowfreq 45%

answer

  1. SimpleStatusAggregator order: DOWN,OUT_OF_SERVICE,UP,UNKNOWN; unknown codes last
  2. status.order to rank custom DEGRADED
  3. status.http-mapping.DEGRADED=503 (unmapped -> 200)
  4. show-details never/when-authorized/always; show-components
  5. groups can override aggregator + mapping

basics

~20 s

A custom status like DEGRADED is unknown to Spring's default aggregator, so it's treated as least severe and won't drag the endpoint down, and by default it maps to HTTP 200. To make it meaningful you must configure the status order and its HTTP mapping, and set show-details to see the details.

solid answer

~40 s

Health.status("DEGRADED") creates a custom Status. Two things determine its effect. First, aggregation: SimpleStatusAggregator orders known statuses DOWN > OUT_OF_SERVICE > UP > UNKNOWN and treats any unknown code as lowest priority, so DEGRADED by default ranks below UNKNOWN and won't lower the aggregate — you fix this with management.endpoint.health.status.order to place DEGRADED where you want. Second, HTTP mapping: HealthEndpoint maps aggregate status to a code (UP/UNKNOWN→200, DOWN/OUT_OF_SERVICE→503); unmapped statuses default to 200, so add management.endpoint.health.status.http-mapping.DEGRADED=503 (or 429) if it should signal unhealthy. Finally, the withDetail entries only appear when management.endpoint.health.show-details is when-authorized or always (default never), and show-components governs the nested breakdown. So a custom status needs explicit order + http-mapping config to behave as intended.

code

properties · 11 lines
properties
# application.properties

# 1) Make DEGRADED rank between OUT_OF_SERVICE and UP
management.endpoint.health.status.order=DOWN,OUT_OF_SERVICE,DEGRADED,UP,UNKNOWN

# 2) Map the custom status to an HTTP code (unmapped statuses default to 200)
management.endpoint.health.status.http-mapping.DEGRADED=503

# 3) Expose the details/components produced by withDetail(...)
management.endpoint.health.show-details=when-authorized
management.endpoint.health.show-components=when-authorized

go deeper

for a junior

Know that details are hidden by default and that non-UP statuses like DOWN return 503.

for a middle

Explain status.order ranking, status.http-mapping, and show-details/show-components for a custom status end to end.

for a senior

Discuss aggregate-vs-contributor status, per-group aggregator/mapping overrides, and avoiding detail leakage.

for a principal

Define org-wide status vocabulary and HTTP-mapping policy, and align readiness/liveness group semantics with orchestration behaviour.

## Custom statuses `Status` is just a wrapper around a **String code** plus optional description. Beyond the four built-ins (`UP`, `DOWN`, `OUT_OF_SERVICE`, `UNKNOWN`) you can mint your own: `Health.status("DEGRADED")` or `Health.status(new Status("DEGRADED", "partially available"))`. Custom statuses are useful for soft/partial conditions, but Spring can't guess their severity or HTTP meaning — you must tell it. ## 1. Aggregation order When the endpoint combines contributors it uses a **`StatusAggregator`**. The default `SimpleStatusAggregator` has a fixed severity order: ``` DOWN, OUT_OF_SERVICE, UP, UNKNOWN ``` The aggregate is the **most severe (earliest in the list)** status present. Any code **not** in the list is considered **less severe than everything listed** (sorted last). So an un-configured `DEGRADED` sits below `UNKNOWN` and can never lower the aggregate — a DEGRADED-only endpoint still reports UP-ish/least-severe behaviour and won't flip the top-level status. Fix it by defining the order: ```properties management.endpoint.health.status.order=DOWN,OUT_OF_SERVICE,DEGRADED,UP,UNKNOWN ``` Now DEGRADED is more severe than UP, so a DEGRADED child makes the aggregate DEGRADED (unless something worse is present). ## 2. HTTP status mapping The `HealthEndpoint` (via a status-to-HTTP mapping) translates the **aggregate** status into an HTTP code. Defaults: - `UP`, `UNKNOWN` → **200** - `DOWN`, `OUT_OF_SERVICE` → **503** - **any unmapped status** → **200** So even after fixing ordering, a DEGRADED aggregate returns **200** unless you map it: ```properties management.endpoint.health.status.http-mapping.DEGRADED=503 # or 429, etc. ``` Mappings are keyed by status code (case-insensitive). This is how you make a load balancer or Kubernetes probe react to your custom status. ## 3. Showing details and components `withDetail(...)` data is **not** shown by default: ```properties management.endpoint.health.show-details=always # never (default) | when-authorized | always management.endpoint.health.show-components=always # controls the nested "components" tree ``` `when-authorized` also honours `roles` config so only privileged callers see internals. This protects against leaking connection details, versions, or internal hostnames to anonymous probes. ## Putting it together For a custom status to be operationally meaningful you generally need **all three**: place it in `status.order`, map it in `status.http-mapping`, and expose details via `show-details`. Miss any one and it looks like it "doesn't work" (silently 200, or invisible details). ## Gotchas - Registering a custom status **order** globally affects every contributor, not just yours. - HTTP mapping applies to the **aggregate** status the endpoint computes, which may differ from your single contributor's status once aggregated. - Health **groups** can have their **own** `StatusAggregator` and `http-mapping` overrides (`management.endpoint.health.group.<name>.status.*`), useful for readiness vs liveness. - Custom statuses are strings — typos (e.g. `DEGREDED`) silently won't match your config.

  • You defined status.http-mapping.DEGRADED=503 but the endpoint still returns 200 when your indicator is DEGRADED. Why?
    Likely the aggregate status isn't DEGRADED: because DEGRADED isn't in status.order it's treated as least severe, so another UP/UNKNOWN contributor outranks it and the aggregate isn't DEGRADED. Add DEGRADED to status.order so it can win the aggregation.
  • How can a readiness health group treat DOWN differently from the main endpoint?
    Health groups accept their own overrides: management.endpoint.health.group.<name>.status.order and .status.http-mapping, plus include/exclude. So the readiness group at /actuator/health/readiness can map or aggregate statuses independently of the root endpoint.

saying these in an interview costs you the question

  • Assuming a custom status is automatically as severe as DOWN (default aggregator ranks unknown codes last)
  • Expecting an unmapped custom status to return 503 (unmapped defaults to 200)
  • Thinking withDetail data always appears (default show-details is never)
  • Confusing a single contributor's status with the aggregated endpoint status that drives the HTTP code

context