Class ReadinessState

java.lang.Object
org.apache.juneau.rest.server.health.ReadinessState

public final class ReadinessState extends Object
Shared, observable readiness flag consumed by HealthAggregator to gate the readiness probe.

This is the building block for zero-downtime shutdown. When a microservice begins shutting down it flips the readiness state out of service before the listening connector stops. The HealthAggregator consults this state for the READY probe so /readyz returns 503 (out of service) as soon as shutdown begins — this lets a load balancer / Kubernetes stop routing new traffic to the pod while in-flight requests drain. The LIVE probe (/livez) is intentionally not gated, so Kubernetes does not kill the pod mid-drain.

Resolution:

HealthAggregator and the embedded-server shutdown hooks resolve the state via resolve(BeanStore): a ReadinessState bean registered in the relevant BeanStore wins; otherwise the process-wide shared instance is used. In the standalone Jetty/Tomcat microservice runtime, the embedded-server lifecycle component (e.g. JettyServerComponent) constructs one per-service instance on start() — reusing an app/test-supplied @Bean ReadinessState if one is already registered — and explicitly publishes that same instance into both the microservice's bean store and the auto-mounted health-probe servlet's own bean store. The two sides then observe each other's flips without ever touching shared(), so stopping one default-configured microservice does not affect any other microservice's readiness in the same JVM. shared() remains the last-resort fallback for embeddings that skip that dual-store publish (a plain servlet container, Spring Boot without this integration, or unit tests that never register a bean).

This class is thread-safe; the flag is volatile.

See Also:
Since:
10.0.0
  • Constructor Details

  • Method Details

    • shared

      public static ReadinessState shared()
      Returns the process-wide shared readiness state.

      Used as the fallback when no ReadinessState bean is registered in the relevant BeanStore. This is not the standalone Jetty/Tomcat microservice path: that runtime constructs a per-service instance and publishes it into both the microservice's bean store and the health-probe servlet's bean store (see the class-level Resolution notes above), so two default-configured microservices in the same JVM never share readiness. This shared instance exists only for embeddings that skip that publish step.

      Returns:
      The shared instance. Never null.
    • resolve

      public static ReadinessState resolve(BeanStore beanStore)
      Resolves the readiness state from the given bean store, falling back to the shared instance.
      Parameters:
      beanStore - The bean store to consult. Can be null (resolves to the shared instance).
      Returns:
      The resolved readiness state. Never null.
    • isReady

      public boolean isReady()
      Returns whether this service is currently ready to receive traffic.
      Returns:
      true if ready, false if out of service.
    • markReady

      Marks this service as ready to receive traffic.

      Called when an embedded server starts so a freshly-(re)started service is ready.

      Returns:
      This object.
    • markOutOfService

      Marks this service as out of service so the readiness probe (/readyz) returns 503.

      Called at the very beginning of shutdown, before the listening connector stops.

      Returns:
      This object.
    • onOutOfService

      public ReadinessState onOutOfService(Object key, Runnable callback)
      Registers a callback to run when this state is marked out of service.

      Lets components that hold long-lived connections (for example MCP subscriptions/listen SSE streams) end them as soon as shutdown begins, instead of making the server's graceful drain wait out its full stop timeout for streams that would otherwise never finish on their own.

      Registering a second callback under the same key replaces the first, so a component that is re-published on every server (re)start does not accumulate duplicate callbacks. Callbacks run on the thread that calls markOutOfService(); exceptions they throw are swallowed.

      Example:

      state.onOutOfService("my-streams", () -> streamRegistry.closeAll());

      Parameters:
      key - The owner of the callback (typically the registering component). Must not be null.
      callback - The callback to run. Must not be null.
      Returns:
      This object.