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.
3.7 KiB
← 07 Groups and probes · 08 · The diagnostics endpoint · 09 Testing Actuator →
08 — The diagnostics endpoint
DiagnosticsEndpoint is a
custom @Endpoint(id = "diag") that prints the Actuator state no configuration file will tell
you: which endpoints were actually discovered and mapped onto HTTP, what paths and methods each
publishes, and which health contributors are registered.
Delete it before you ship. The list of exposed endpoints is itself reconnaissance.
Why it exists
Reading application.yaml and predicting exposure is how people ship /actuator/heapdump to
the internet. management.endpoints.web.exposure.include: "*" looks like it exposes everything;
it does not expose heapdump or shutdown (chapter 03). Conditional
endpoints like httpexchanges and startup are absent unless a specific bean exists. Ask the
running application instead of predicting.
Writing a custom endpoint
@Component
@Endpoint(id = "diag")
public class DiagnosticsEndpoint {
@ReadOperation
public Map<String, Object> diagnostics() { ... }
}
@ReadOperation maps to GET, @WriteOperation to POST, @DeleteOperation to DELETE. The id
must be lowercase alphanumeric; it becomes the path segment and the JMX name. You still have to
expose it — include: health,info,diag or "*".
The interesting part is the three beans it injects:
| Bean | Gives you |
|---|---|
WebEndpointsSupplier |
every ExposableWebEndpoint, with its WebOperation predicates |
HealthContributorRegistry |
the live contributor tree, composites included |
Environment |
the effective values of the management.* keys |
HealthContributorRegistry is in org.springframework.boot.health.registry — new in Boot 4
(chapter 02). It iterates as HealthContributors.Entry, and nested
HealthContributors are composites, which is why the collection is recursive.
The output
From output/02-endpoint-catalogue.txt:
{
"activeProfiles": ["exposeall", "open"],
"serverPort": "8080",
"managementPort": "(same as server.port)",
"managementBasePath": "/actuator",
"exposureInclude": "*",
"healthShowDetails": "always",
"exposedWebEndpointCount": 14,
"exposedWebEndpoints": {
"loggers": ["GET loggers", "GET loggers/{name}", "POST loggers/{name}"],
"threaddump": ["GET threaddump", "GET threaddump"],
...
},
"healthContributors": [
"db (DataSourceHealthIndicator)",
"diskSpace (DiskSpaceHealthIndicator)",
"externalApi (ExternalApiHealthIndicator)",
"kafka (KafkaHealthIndicator)",
"livenessState (LivenessStateHealthIndicator)",
"ordersDatabase (OrdersDatabaseHealthIndicator)",
"ping (PingHealthIndicator)",
"readinessState (ReadinessStateHealthIndicator)",
"ssl (SslHealthIndicator)"
]
}
Two things worth pausing on.
"loggers" shows the POST operation explicitly. That is the write endpoint from
chapter 03, visible in a list rather than remembered.
"threaddump" appears twice with the same path. It publishes two operations that differ
only in what they produce — application/json and text/plain. Content negotiation, not a bug.
Run the same endpoint under the mgmtport profile and managementPort reads 9001,
managementBasePath reads /manage, and the endpoint count is unchanged — the endpoints moved,
they did not disappear. That is the fact that breaks path-string security rules
(chapter 04).
← 07 · 08 · 09 Testing Actuator →