Files
spring-boot-demo/docs/08-diagnostics.md
Ankur Mhatre 4b6cefa60a Spring Boot 4 Actuator in production: endpoints, security, custom health indicators
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.
2026-09-04 10:40:04 +05:30

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 →