Companion repository for the ankurm.com article. Every transcript in docs/output/ was produced by running this project; scripts/run-all.sh regenerates all of them. Verified against Spring Boot 4.1.1 / Framework 7.0.9 / Security 7.1.1 / Micrometer 1.17.1 / kafka-clients 4.2.1 on Temurin JDK 25.0.4.1+1.
spring-boot-demo — Actuator in production
Companion repository for Spring Boot Actuator in Production 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/ 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.
| 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 |
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
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/:
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.
| 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
Captured output
| File | Scenario |
|---|---|
00-versions.txt |
resolved dependency versions |
01-default-exposure.txt |
Actuator with zero configuration |
02-endpoint-catalogue.txt |
every exposed endpoint, from the running app |
03-open-actuator-leak.txt |
what an anonymous caller really gets |
04-heapdump-leak.txt |
59 MB, plaintext credentials inside |
05-secured-matrix.txt |
the full authorisation matrix |
06-management-port.txt |
port 9001, and what it isolates |
07-custom-health-indicators.txt |
DB, Kafka and external API |
08-groups-and-probes.txt |
liveness 200 while readiness 503 |
09-kafka-timeout.txt.tuned |
1.6 s |
09-kafka-timeout.txt.naive |
60.2 s |
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.