skip to content

How do you programmatically flip the readiness (or liveness) probe to DOWN at runtime in Spring Boot?

level: seniorimportance: must knowfreq 50%

answer

  1. AvailabilityChangeEvent.publish(publisher, this, state)
  2. REFUSING_TRAFFIC -> drop from LB; BROKEN -> restart
  3. read-only bean; change via event
  4. dimension inferred from state type
  5. latest-write-wins, no ref-counting

basics

~10 s

Publish an AvailabilityChangeEvent via the ApplicationEventPublisher, e.g. AvailabilityChangeEvent.publish(publisher, this, ReadinessState.REFUSING_TRAFFIC). Spring updates ApplicationAvailability and the probe endpoint immediately reflects the new state.

solid answer

~40 s

State is changed by publishing an `AvailabilityChangeEvent`, never by mutating the bean. The convenient static helper is `AvailabilityChangeEvent.publish(ApplicationEventPublisher publisher, Object source, S state)` where `state` is a `ReadinessState` or `LivenessState` (or any `AvailabilityState`). For example, to take a pod out of rotation for maintenance you publish `ReadinessState.REFUSING_TRAFFIC`; Kubernetes then removes it from Service endpoints. To signal an unrecoverable internal fault and force a restart you publish `LivenessState.BROKEN`. Spring's internal `ApplicationAvailabilityBean` listens for these events, updates the current state, and the `/actuator/health/readiness` or `/actuator/health/liveness` group reflects it on the next probe. You typically inject `ApplicationEventPublisher` (or `ApplicationContext`). This is the sanctioned way to drive graceful drain, feature-flag maintenance mode, or self-fencing on detected corruption.

code

java · 25 lines
java
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
public class ProbeController {

    private final ApplicationEventPublisher publisher;

    public ProbeController(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    /** Pull this pod out of the Kubernetes Service load balancer. */
    public void refuseTraffic() {
        AvailabilityChangeEvent.publish(publisher, this, ReadinessState.REFUSING_TRAFFIC);
    }

    /** Signal unrecoverable internal state -> Kubernetes restarts the pod. */
    public void fenceSelf() {
        AvailabilityChangeEvent.publish(publisher, this, LivenessState.BROKEN);
    }
}

go deeper

for a junior

Recognize that AvailabilityChangeEvent is how state changes, at a high level.

for a middle

Write the publish call correctly and pick the right state for restart vs drain.

for a senior

Explain the read/change separation, dimension inference, latest-write-wins, and real use cases (drain, self-fence, warm-up).

for a principal

Design centralized availability coordination, reason about race conditions and the lack of ref-counting across multiple publishers.

## The event-driven update model Spring Boot deliberately separates **reading** availability (`ApplicationAvailability`) from **changing** it. You change state by publishing an application event: `org.springframework.boot.availability.AvailabilityChangeEvent`. The internal bean `ApplicationAvailabilityBean` implements both `ApplicationAvailability` (read) and an `ApplicationListener<AvailabilityChangeEvent<?>>` (it consumes the events and stores the latest state per dimension). Because the probe health indicators read from that same bean, publishing an event instantly changes what the probe endpoint returns. ## The API The simplest form is the static helper: ```java AvailabilityChangeEvent.publish(applicationEventPublisher, source, ReadinessState.REFUSING_TRAFFIC); ``` - `publisher` — an `ApplicationEventPublisher` (the `ApplicationContext` is one) - `source` — usually `this` - state — a `LivenessState`, `ReadinessState`, or any custom `AvailabilityState` There is also an overload taking an `ApplicationContext`. Or you can construct `new AvailabilityChangeEvent<>(source, state)` and publish it yourself. ## Concrete use cases 1. **Graceful drain before shutdown / during maintenance:** publish `REFUSING_TRAFFIC`, wait for the load balancer to drop the pod and in-flight requests to finish, then proceed. Spring already does this automatically on shutdown, but you might do it earlier for a controlled rollout. 2. **Self-fencing:** a background verification detects corrupted in-memory state that a restart would clear -> publish `LivenessState.BROKEN` so Kubernetes restarts the pod. 3. **Warm-up gating:** keep readiness `REFUSING_TRAFFIC` until a cache is preloaded, then publish `ACCEPTING_TRAFFIC`. ## Example ```java @Component class MaintenanceSwitch { private final ApplicationEventPublisher publisher; MaintenanceSwitch(ApplicationEventPublisher publisher) { this.publisher = publisher; } void enterMaintenance() { AvailabilityChangeEvent.publish(publisher, this, ReadinessState.REFUSING_TRAFFIC); } void exitMaintenance() { AvailabilityChangeEvent.publish(publisher, this, ReadinessState.ACCEPTING_TRAFFIC); } } ``` ## Gotchas - **Events are dimension-specific.** Publishing a `ReadinessState` only changes readiness; liveness is untouched (and vice versa). The dimension is inferred from the state's type. - **Latest-write-wins.** There is no stacking/reference-counting: if two components both drive readiness, the last event wins. If you need coordinated gating, centralize the logic. - **The probe reflects it on the next scrape, not with retroactive history**, but the change to the bean is synchronous when the event is published (standard Spring event publication is synchronous by default). - Don't try to set state by writing a custom `HealthIndicator` that returns DOWN for the liveness/readiness groups — the correct mechanism is the event; custom indicators are for other, non-probe health. - `getLastChangeEvent(ReadinessState.class)` lets you assert the source/state in tests.

  • If two beans both publish readiness events, how is the final state decided?
    Latest-write-wins. AvailabilityChangeEvent has no reference counting; the most recently published ReadinessState is the current one. If you need coordinated gating across components, centralize the decision rather than letting each publish independently.
  • Does publishing a ReadinessState event affect liveness?
    No. The dimension is inferred from the event's state type, so a ReadinessState event updates only readiness and leaves LivenessState unchanged.
  • Why publish an event instead of writing a custom HealthIndicator returning DOWN?
    The liveness/readiness groups are backed by ApplicationAvailability state, not arbitrary indicators. The event is the sanctioned, dimension-correct way; a custom indicator would be for separate, non-probe health and wouldn't cleanly integrate with the availability state model.

saying these in an interview costs you the question

  • Claiming you set the state by calling a setter on ApplicationAvailability
  • Publishing REFUSING_TRAFFIC and expecting the pod to restart (that's what BROKEN does)
  • Thinking events reference-count so you must 'undo' each one
  • Believing a ReadinessState event also changes liveness

context