[← 07 Groups and probes](07-groups-and-probes.md) · **08 · The diagnostics endpoint** · [09 Testing Actuator →](09-testing-actuator.md) # 08 — The diagnostics endpoint [`DiagnosticsEndpoint`](../src/main/java/com/ankurm/actuator/web/DiagnosticsEndpoint.java) 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](03-endpoint-catalogue.md)). Conditional endpoints like `httpexchanges` and `startup` are absent unless a specific bean exists. Ask the running application instead of predicting. ## Writing a custom endpoint ```java @Component @Endpoint(id = "diag") public class DiagnosticsEndpoint { @ReadOperation public Map 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](02-boot-4-changes.md)). 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`](output/02-endpoint-catalogue.txt): ```json { "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](03-endpoint-catalogue.md), 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](04-securing-actuator.md)). --- [← 07](07-groups-and-probes.md) · **08** · [09 Testing Actuator →](09-testing-actuator.md)