This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/docs by this push:
     new 600ad0e635 Add graceful shutdown and readiness gating for Jetty/Tomcat 
microservices; externalize remaining starter projects; document graceful 
shutdown.
600ad0e635 is described below

commit 600ad0e635a3e37ed93c5a51d908db463f1f7c20
Author: James Bognar <[email protected]>
AuthorDate: Fri Jun 12 11:42:46 2026 -0400

    Add graceful shutdown and readiness gating for Jetty/Tomcat microservices; 
externalize remaining starter projects; document graceful shutdown.
---
 pages/release-notes/10.0.0.md          |  38 +++++++
 pages/topics/16.11.GracefulShutdown.md | 191 +++++++++++++++++++++++++++++++++
 sidebars.ts                            |   5 +
 3 files changed, 234 insertions(+)

diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index 8dfac36b13..73270b2af6 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -61,6 +61,30 @@ The `JettyMicroservice.run(...)` facade has three overloads:
 
 The legacy `juneau-my-jetty-microservice` template module has been removed 
from the reactor. Consumers should migrate to the new `JettyMicroservice` 
facade in `juneau-microservice-jetty` — it delivers the same zero-config 
developer experience without requiring the consumer to copy a template project.
 
+### juneau-microservice-jetty / juneau-microservice-tomcat
+
+#### Graceful shutdown + readiness gating (zero-downtime k8s rollouts)
+
+Both embedded-server microservice flavors now perform a zero-downtime graceful 
shutdown suitable for rolling Kubernetes deployments, sharing one identical 
shutdown contract.
+
+On shutdown, a shared `ReadinessState` flag is flipped **before** the 
listening connector stops, so `GET /readyz` immediately returns `503` 
(`state=OUT_OF_SERVICE`) while a load balancer / Kubernetes is still routing. 
`GET /livez` and `GET /healthz` stay healthy, so the orchestrator does not kill 
the pod mid-drain. The server then waits up to a bounded `stopTimeout` (default 
`30s`) for in-flight requests to drain — Jetty via 
`server.setStopTimeout(...)`, Tomcat by pausing the connector a [...]
+
+- New shared `org.apache.juneau.rest.server.health.ReadinessState` flag, 
consulted by `HealthAggregator` to gate the `READY` probe (liveness and the 
aggregate health surface are unaffected).
+- New `stopTimeout` / `shutdownSettleDelay` knobs on both `JettySettings` and 
`TomcatSettings`, also configurable via `juneau.cfg` (`[Jetty]` / `[Tomcat]` 
sections, in milliseconds). Precedence: programmatic settings > config-file 
entry > (Jetty only: `jetty.xml` value) > 30s default.
+- Bundled `juneau.cfg` documents the knobs and the recommended Kubernetes 
`preStop` hook + `terminationGracePeriodSeconds` pairing.
+
+```java
+@Bean
+JettySettings jettySettings() {
+    return JettySettings.create()
+        .stopTimeout(Duration.ofSeconds(45))
+        .shutdownSettleDelay(Duration.ofSeconds(2))
+        .build();
+}
+```
+
+See the new [Graceful Shutdown & Readiness 
Gating](/docs/topics/GracefulShutdown) topic page for details, including the 
Jetty/Tomcat parity table and Kubernetes deployment guidance.
+
 ### juneau-petstore (new module family)
 
 Three new modules under a top-level `juneau-petstore/` aggregator together 
form the canonical Juneau petstore showcase application.  The legacy 
`juneau-examples-rest{,-jetty,-springboot,-jetty-ftest}` family of modules has 
been retired in this release in favor of the petstore family — see the 
**Breaking Changes** section below.
@@ -107,6 +131,20 @@ Launcher: `python3 scripts/start-petstore-springboot.py`.
 
 The core-hosted surface (CRUD, view demos, React SPA demo, auth-gated subset) 
is identical across both deployments by construction — it's shared code, not 
parallel copies. The deployment-specific surface (Jetty's microservice console 
+ admin resources vs Spring Boot's `@Autowired` injection demo) is 
intentionally non-parity and documented as such.
 
+### juneau-rest-common / juneau-rest-server
+
+#### Content Security Policy (CSP) support
+
+Juneau can now emit `Content-Security-Policy` response headers for the HTML it 
serves, including dynamic per-request nonce support. New in this release:
+
+- A first-class immutable `ContentSecurityPolicy` HTTP header bean + fluent 
builder in `juneau-rest-common` (`org.apache.juneau.http.header`), with typed 
directive setters (`defaultSrc`, `scriptSrc`, `styleSrc`, `imgSrc`, 
`connectSrc`, `fontSrc`, `objectSrc`, `baseUri`, `frameAncestors`, 
`reportUri`/`reportTo`), a generic `directive(name, sources...)` escape hatch, 
source-expression helpers, report-only mode, and a ready-made nonce-based 
"strict starter" preset.
+- Per-response `SecureRandom` nonce generation: the same nonce is injected 
into the emitted `Content-Security-Policy` header and stamped onto every inline 
`<script>`/`<style>` Juneau emits — the HtmlDoc template, the Swagger UI, the 
OpenAPI UI, and menu-item widgets.
+- Opt-in by design: turning CSP on is a deliberate, one-line choice; existing 
apps are unaffected by default.
+
+See the new [Content Security Policy](/docs/topics/ContentSecurityPolicy) 
topic page for details.
+
+This feature was inspired by an earlier proposal from **Gary Gregory** 
([@garydgregory](https://github.com/garydgregory), PR #57); the implementation 
shipped here is a fresh one built against the current 10.0 APIs.
+
 ### Bug Fixes
 
 _TBD — to be filled in as development continues._
diff --git a/pages/topics/16.11.GracefulShutdown.md 
b/pages/topics/16.11.GracefulShutdown.md
new file mode 100644
index 0000000000..7e76448fe9
--- /dev/null
+++ b/pages/topics/16.11.GracefulShutdown.md
@@ -0,0 +1,191 @@
+---
+title: "Graceful Shutdown & Readiness Gating"
+slug: GracefulShutdown
+---
+
+> **See also:** [Health / Readiness / Liveness 
Probes](/docs/topics/HealthProbes) — the probe surface (`/healthz`, `/readyz`, 
`/livez`) this page gates during shutdown.
+
+Starting with **10.0.0**, both embedded-server microservice flavors — 
`juneau-microservice-jetty` and `juneau-microservice-tomcat` — perform a 
**zero-downtime graceful shutdown** suitable for rolling Kubernetes 
deployments. The two servers share one identical shutdown contract.
+
+When a microservice begins shutting down it:
+
+1. **Flips readiness out of service first.** A shared `ReadinessState` flag is 
flipped before the listening connector stops, so `GET /readyz` immediately 
returns `503 Service Unavailable` (`state=OUT_OF_SERVICE`) while a load 
balancer / Kubernetes is still routing. `GET /livez` and `GET /healthz` stay 
healthy so the orchestrator does **not** kill the pod mid-drain.
+2. **Optionally settles.** An optional `shutdownSettleDelay` pause between the 
readiness flip and the connector stop gives the load balancer a window to 
observe the `503` before draining begins.
+3. **Drains in-flight requests.** The connector stops accepting new 
connections and the server waits up to a bounded `stopTimeout` (default `30s`) 
for in-flight requests to complete before it fully stops.
+
+This sequencing is what lets Kubernetes shift traffic away from a terminating 
pod before its in-flight work is interrupted.
+
+## Shutdown sequence
+
+```
+shutdown begins
+   │
+   ├─▶ ReadinessState.markOutOfService()      /readyz → 503 (OUT_OF_SERVICE)
+   │                                          /livez  → 200  (still LIVE)
+   │                                          /healthz→ 200  (still UP)
+   │
+   ├─▶ sleep(shutdownSettleDelay)             LB observes the 503 (default 0ms)
+   │
+   ├─▶ drain in-flight requests               up to stopTimeout (default 30s)
+   │     Jetty:  server.setStopTimeout(...) then server.stop()
+   │     Tomcat: connector.pause() then wait for active count → 0, then stop()
+   │
+   └─▶ server fully stopped
+```
+
+## `/readyz` vs `/livez` vs `/healthz` during shutdown
+
+| Probe | Normal | During shutdown | Why |
+|---|---|---|---|
+| `GET /readyz` | `200` (when no component is `DOWN`) | **`503` 
`OUT_OF_SERVICE`** | Tells the load balancer / k8s readiness gate to stop 
routing new traffic to this pod. |
+| `GET /livez` | `200` | `200` (unaffected) | A `503` here would make k8s 
**kill** the pod mid-drain — deliberately not gated. |
+| `GET /healthz` | `200` / `503` per indicators | unaffected | The aggregate 
(null-probe) health surface is not gated by readiness. |
+
+The readiness gate is implemented in `HealthAggregator`: when the shared 
`ReadinessState` is out of service it forces the `READY` probe `DOWN` and adds 
a `readiness` component with `{"state": "OUT_OF_SERVICE"}` to the payload. 
Liveness and the aggregate health surface are untouched.
+
+```json
+{
+  "status": "DOWN",
+  "components": {
+    "readiness": {
+      "status": "DOWN",
+      "details": {
+        "state": "OUT_OF_SERVICE"
+      }
+    }
+  }
+}
+```
+
+## Tuning knobs
+
+Two knobs control the drain behavior, both expressible programmatically 
(preferred), via the config file, or left at their defaults.
+
+| Knob | Default | Meaning |
+|---|---|---|
+| `stopTimeout` | `30s` | Bounded time the server waits for in-flight requests 
to drain before stopping the connector. |
+| `shutdownSettleDelay` | `0` (none) | In-process pause between the `/readyz` 
503 flip and the connector stop. The recommended Kubernetes pattern uses a 
`preStop` hook sleep instead (see below). |
+
+### Precedence
+
+For both servers the resolved value follows this precedence:
+
+**programmatic settings > config-file entry > (Jetty only: existing 
`jetty.xml` value) > 30s default**
+
+### Builder examples
+
+Contribute a `@Bean JettySettings` (or `@Bean TomcatSettings`) from your own 
`@Configuration` class to override the defaults:
+
+```java
+import java.time.Duration;
+import org.apache.juneau.commons.inject.Configuration;
+import org.apache.juneau.commons.inject.Bean;
+import org.apache.juneau.microservice.jetty.JettySettings;
+
+@Configuration
+public class MyAppConfig {
+
+    @Bean
+    JettySettings jettySettings() {
+        return JettySettings.create()
+            .stopTimeout(Duration.ofSeconds(45))
+            .shutdownSettleDelay(Duration.ofSeconds(2))
+            .build();
+    }
+}
+```
+
+The Tomcat flavor is identical — swap `JettySettings` for `TomcatSettings`:
+
+```java
+import java.time.Duration;
+import org.apache.juneau.commons.inject.Configuration;
+import org.apache.juneau.commons.inject.Bean;
+import org.apache.juneau.microservice.tomcat.TomcatSettings;
+
+@Configuration
+public class MyAppConfig {
+
+    @Bean
+    TomcatSettings tomcatSettings() {
+        return TomcatSettings.create()
+            .stopTimeout(Duration.ofSeconds(45))
+            .shutdownSettleDelay(Duration.ofSeconds(2))
+            .build();
+    }
+}
+```
+
+Any value left `null` on the settings bean falls back to the config-file 
entry, then to the default.
+
+### `juneau.cfg` examples
+
+Both knobs are documented in the bundled `juneau.cfg` and read from the 
server's section (`[Jetty]` or `[Tomcat]`) in **milliseconds**:
+
+```ini
+[Jetty]
+# Graceful-shutdown drain timeout in milliseconds. Defaults to 30000 (30s) 
when unset.
+stopTimeout = 30000
+
+# Settle delay in milliseconds applied between the /readyz 503 flip and the 
connector stop.
+# Defaults to 0 when unset.
+shutdownSettleDelay = 0
+```
+
+For the Tomcat flavor the keys live under the `[Tomcat]` section instead 
(`Tomcat/stopTimeout`, `Tomcat/shutdownSettleDelay`).
+
+## Jetty vs Tomcat parity
+
+Both servers expose the same knobs, the same defaults, and the same observable 
readiness contract. They differ only in the underlying mechanism used to drain:
+
+| | Jetty | Tomcat |
+|---|---|---|
+| Apply drain timeout | `server.setStopTimeout(...)` applied at startup; 
`server.stop()` drains | connector `pause()`, then wait up to `stopTimeout` for 
the protocol-handler executor's active count to reach zero, then `stop()` / 
`destroy()` |
+| Config section | `[Jetty]` | `[Tomcat]` |
+| `jetty.xml` override | honored if set (precedence above the default) | n/a |
+| Default `stopTimeout` | `30s` | `30s` |
+| Readiness flip / settle | identical | identical |
+
+The bundled `jetty.xml` deliberately does **not** set `stopTimeout` — it is 
applied in code so the 30s default and the precedence rules apply uniformly. A 
comment in `jetty.xml` documents this.
+
+## Kubernetes deployment guidance
+
+The recommended production pattern pairs the in-process readiness flip with a 
`preStop` hook and a generous `terminationGracePeriodSeconds`, rather than 
relying on the in-process `shutdownSettleDelay`:
+
+- Point the **readiness probe** at `/readyz` so k8s stops routing to the pod 
as soon as it flips to `503`.
+- Point the **liveness probe** at `/livez` so k8s does not kill the pod while 
it is draining.
+- Add a **`preStop` hook sleep** to give the cluster's endpoint/Service 
propagation time to remove the pod from rotation before the JVM begins draining.
+- Set **`terminationGracePeriodSeconds`** to comfortably exceed `(preStop 
sleep + shutdownSettleDelay + stopTimeout)` so Kubernetes does not `SIGKILL` 
the pod mid-drain.
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+spec:
+  template:
+    spec:
+      # Must exceed preStop sleep + shutdownSettleDelay + stopTimeout.
+      terminationGracePeriodSeconds: 60
+      containers:
+        - name: my-juneau-microservice
+          readinessProbe:
+            httpGet:
+              path: /readyz
+              port: 10000
+            periodSeconds: 5
+          livenessProbe:
+            httpGet:
+              path: /livez
+              port: 10000
+            periodSeconds: 10
+          lifecycle:
+            preStop:
+              exec:
+                # Give Service/endpoint propagation time to drop this pod 
before draining.
+                command: ["sh", "-c", "sleep 10"]
+```
+
+With this setup: the `preStop` sleep + readiness `503` keep new traffic away, 
`stopTimeout` drains the in-flight requests, and 
`terminationGracePeriodSeconds` guarantees the JVM gets enough time to finish 
before a hard kill.
+
+:::info See Also
+[Health / Readiness / Liveness Probes](/docs/topics/HealthProbes)
+:::
diff --git a/sidebars.ts b/sidebars.ts
index 2f9703dfcd..d1ffc445ea 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -2042,6 +2042,11 @@ const sidebars: SidebarsConfig = {
                                                        id: 
'topics/16.10.HealthProbes',
                                                        label: '16.10. Health / 
Readiness / Liveness Probes',
                                                },
+                                               {
+                                                       type: 'doc',
+                                                       id: 
'topics/16.11.GracefulShutdown',
+                                                       label: '16.11. Graceful 
Shutdown & Readiness Gating',
+                                               },
                                        ],
                                },
                                {

Reply via email to