How do you programmatically flip the readiness (or liveness) probe to DOWN at runtime in Spring Boot?
answer
- AvailabilityChangeEvent.publish(publisher, this, state)
- REFUSING_TRAFFIC -> drop from LB; BROKEN -> restart
- read-only bean; change via event
- dimension inferred from state type
- latest-write-wins, no ref-counting
basics
~10 sPublish 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 sState 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 linesimport 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
Recognize that AvailabilityChangeEvent is how state changes, at a high level.
Write the publish call correctly and pick the right state for restart vs drain.
Explain the read/change separation, dimension inference, latest-write-wins, and real use cases (drain, self-fence, warm-up).
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