# spring-boot-demo — Actuator in production Companion repository for **[Spring Boot Actuator in Production](https://ankurm.com/spring-boot-actuator-production-endpoints-security-health-indicators/)** on ankurm.com. Every status code, JSON body, byte count and timing figure in the article was produced by running this project. The transcripts live in [`docs/output/`](docs/output) and are regenerated by a single command. --- ## Versions Verified with `mvn dependency:list` on the machine that produced `docs/output/` — see [`docs/output/00-versions.txt`](docs/output/00-versions.txt). | Component | Version | Notes | |---|---|---| | Spring Boot | 4.1.1 | GA 20 August 2026 | | Spring Framework | 7.0.9 | via `spring-boot-starter-parent` | | Spring Security | 7.1.1 | via `spring-boot-starter-parent` | | `spring-boot-actuator` | 4.1.1 | | | `spring-boot-health` | 4.1.1 | **new module in Boot 4** — `HealthIndicator` lives here now | | `spring-boot-restclient` | 4.1.1 | **not pulled in by the web starter** | | Micrometer | 1.17.1 | there is no Micrometer 2.x GA; see [`docs/02`](docs/02-boot-4-changes.md) | | `kafka-clients` | 4.2.1 | what Boot 4.1.1 manages (4.3.1 is the latest on Central) | | H2 | 2.4.240 | | | JDK | Temurin 25.0.4.1+1 LTS | | | Maven | 3.9.11 | | --- ## Quickstart ```bash export JAVA_HOME=/path/to/jdk-25 mvn -DskipTests package # defaults: only /actuator/health is exposed java -jar target/actuator-production-1.0.0.jar # every endpoint, no authentication - the configuration you should never ship java -jar target/actuator-production-1.0.0.jar --spring.profiles.active=exposeall,open # the configuration you should ship java -jar target/actuator-production-1.0.0.jar --spring.profiles.active=secured ``` Credentials for every profile that requires them: **`ops` / `ops-password`**. Regenerate every transcript in `docs/output/`: ```bash JAVA_HOME=/path/to/jdk-25 ./scripts/run-all.sh ``` That takes roughly three minutes, most of which is the naive Kafka scenario blocking for its full 60 seconds. That is the point of it. --- ## Profiles | Profile | What it demonstrates | |---|---| | *(none)* | Boot defaults. Only `health` on the web, `show-details: never` | | `exposeall` | `management.endpoints.web.exposure.include: "*"` | | `open` | A `permitAll` security chain — the misconfiguration, kept on purpose | | `secured` | `EndpointRequest.toAnyEndpoint()` + `ROLE_ACTUATOR` + `when-authorized` details | | `mgmtport` | Actuator on port 9001, base path `/manage`, bound to loopback | | `details` | `show-details: always` — the full component breakdown | | `groups` | `liveness` / `readiness` / `startup` groups wired correctly | | `kafkanaive` | The textbook `AdminClient` health check, so its 60-second block can be timed | Profiles compose: `--spring.profiles.active=exposeall,open`. --- ## Endpoints Read from the running application, not from the documentation — see [`docs/output/02-endpoint-catalogue.txt`](docs/output/02-endpoint-catalogue.txt). | Endpoint | Web-exposed by default | `access` default | Notes | |---|---|---|---| | `health` | **yes** | `unrestricted` | the only one exposed out of the box | | `info` | no | `unrestricted` | Boot 4.1 added `process.*` fields | | `beans` | no | `unrestricted` | 426 beans in this app, with types and wiring | | `conditions` | no | `unrestricted` | the auto-configuration report | | `configprops` | no | `unrestricted` | values masked like `env` | | `env` | no | `unrestricted` | **masks every value** unless `show-values` says otherwise | | `loggers` | no | `unrestricted` | has a `POST` — a write endpoint | | `mappings` | no | `unrestricted` | every URL your app serves | | `metrics` | no | `unrestricted` | from `spring-boot-micrometer-metrics` | | `prometheus` | no | `unrestricted` | needs `micrometer-registry-prometheus` | | `sbom` | no | `unrestricted` | | | `scheduledtasks` | no | `unrestricted` | | | `threaddump` | no | `unrestricted` | two operations: JSON and `text/plain` | | `heapdump` | no | **`none`** | `include: "*"` is **not** enough | | `shutdown` | no | **`none`** | `include: "*"` is **not** enough | | `startup` | no | `unrestricted` | needs a `BufferingApplicationStartup` | | `httpexchanges` | no | `unrestricted` | needs an `HttpExchangeRepository` bean | | `auditevents` | no | `unrestricted` | needs an `AuditEventRepository` bean | | `logfile` | no | `unrestricted` | needs `logging.file.name` | | `caches`, `flyway`, `liquibase`, `quartz`, `sessions`, `integrationgraph` | no | `unrestricted` | conditional on the relevant module | | `diag` | no | `unrestricted` | **this repository's own** — delete before shipping | `heapdump` and `shutdown` are the only two endpoints whose `access` defaults to `none`. That list came from Spring Boot's own `spring-configuration-metadata.json`, not from a blog. --- ## Documentation | # | Chapter | |---|---| | 01 | [What Actuator actually exposes](docs/01-what-actuator-exposes.md) | | 02 | [What changed in Spring Boot 4](docs/02-boot-4-changes.md) | | 03 | [The endpoint catalogue](docs/03-endpoint-catalogue.md) | | 04 | [Securing Actuator](docs/04-securing-actuator.md) | | 05 | [Custom health indicators](docs/05-custom-health-indicators.md) | | 06 | [Health indicator failure modes](docs/06-health-indicator-failure-modes.md) | | 07 | [Groups, probes and Kubernetes](docs/07-groups-and-probes.md) | | 08 | [The diagnostics endpoint](docs/08-diagnostics.md) | | 09 | [Testing Actuator](docs/09-testing-actuator.md) | --- ## Captured output | File | Scenario | |---|---| | [`00-versions.txt`](docs/output/00-versions.txt) | resolved dependency versions | | [`01-default-exposure.txt`](docs/output/01-default-exposure.txt) | Actuator with zero configuration | | [`02-endpoint-catalogue.txt`](docs/output/02-endpoint-catalogue.txt) | every exposed endpoint, from the running app | | [`03-open-actuator-leak.txt`](docs/output/03-open-actuator-leak.txt) | what an anonymous caller really gets | | [`04-heapdump-leak.txt`](docs/output/04-heapdump-leak.txt) | 59 MB, plaintext credentials inside | | [`05-secured-matrix.txt`](docs/output/05-secured-matrix.txt) | the full authorisation matrix | | [`06-management-port.txt`](docs/output/06-management-port.txt) | port 9001, and what it isolates | | [`07-custom-health-indicators.txt`](docs/output/07-custom-health-indicators.txt) | DB, Kafka and external API | | [`08-groups-and-probes.txt`](docs/output/08-groups-and-probes.txt) | liveness 200 while readiness 503 | | [`09-kafka-timeout.txt.tuned`](docs/output/09-kafka-timeout.txt.tuned) | 1.6 s | | [`09-kafka-timeout.txt.naive`](docs/output/09-kafka-timeout.txt.naive) | **60.2 s** | | [`10-slow-upstream.txt`](docs/output/10-slow-upstream.txt) | a read timeout doing its job | Status codes and bodies are reproducible. Timing figures are indicative and drift between machines — except the 60-second one, which is a Kafka default and lands on 60.0 s every time. --- ## Layout ``` pom.xml scripts/ run-all.sh regenerate everything below docs/output/ run.sh / stop.sh start and stop with given profiles demo-*.sh one script per captured scenario src/main/java/com/ankurm/actuator/ health/ OrdersDatabase, Kafka, ExternalApi indicators + UpstreamState config/ Secured, Open and baseline security chains web/ DiagnosticsEndpoint, stub upstream, business controller src/test/java/ contract tests for the surprising behaviour docs/ numbered chapters docs/output/ captured real output ``` ## Licence MIT — see [LICENSE](LICENSE).