How do you expose your Spring Boot application's metrics to Prometheus, and what is the /actuator/prometheus endpoint?
answer
- micrometer-registry-prometheus on classpath
- exposure.include=prometheus
- GET /actuator/prometheus = text format
- Prometheus SCRAPES (pull)
- tags become labels
basics
~10 sAdd the micrometer-registry-prometheus dependency and expose the endpoint with management.endpoints.web.exposure.include=prometheus. Spring Boot then serves metrics as plain text at /actuator/prometheus, which the Prometheus server reads (scrapes).
solid answer
~40 sMetrics in Spring Boot are recorded through Micrometer, a vendor-neutral metrics facade. To publish them for Prometheus you add the micrometer-registry-prometheus dependency; Spring Boot auto-configures a PrometheusMeterRegistry and registers an Actuator endpoint named 'prometheus'. Because Actuator hides most endpoints by default, you must opt in with management.endpoints.web.exposure.include=prometheus (or *). The endpoint then serves all registered meters at /actuator/prometheus in the Prometheus text exposition format — one metric line per series, with tags rendered as labels. Prometheus is pull-based: the Prometheus server is configured to scrape that URL on an interval, so the app just exposes current values on demand rather than pushing anything. No credentials or push targets are configured on the app side for a basic setup.
code
java · 23 lines// build.gradle.kts
// implementation("org.springframework.boot:spring-boot-starter-actuator")
// runtimeOnly("io.micrometer:micrometer-registry-prometheus")
// application.yml equivalent in properties:
// management.endpoints.web.exposure.include=health,prometheus
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final io.micrometer.core.instrument.Counter placed;
public OrderService(MeterRegistry registry) {
// Recorded via the facade; PrometheusMeterRegistry formats it for scraping.
this.placed = registry.counter("orders.placed", "channel", "web");
}
public void place() {
placed.increment(); // shows up as orders_placed_total{channel="web"}
}
}go deeper
Know the dependency, the exposure property, the endpoint path, and that Prometheus scrapes it.
Explain text exposition format, tags→labels, and OpenMetrics via Accept header.
Discuss securing/isolating the scrape endpoint and cardinality risks from URI labels.
Frame the pull contract's operational implications (service discovery, staleness, blast radius) vs. push alternatives.
**Micrometer** is the metrics facade built into Spring Boot Actuator: your code (and Boot's auto-instrumentation) records measurements against a `MeterRegistry` using meters like `Counter`, `Timer`, and `Gauge`, without knowing which monitoring backend will consume them. A **registry implementation** decides the output format. `PrometheusMeterRegistry` (from the `io.micrometer:micrometer-registry-prometheus` dependency) formats meters for Prometheus. **Wiring it up:** 1. Add the dependency `io.micrometer:micrometer-registry-prometheus` (Spring Boot manages the version via the BOM). 2. Spring Boot's auto-configuration detects it on the classpath and creates a `PrometheusMeterRegistry` bean plus an Actuator endpoint whose id is `prometheus`. 3. Actuator exposes only `health` over HTTP by default, so you must include the endpoint: `management.endpoints.web.exposure.include=prometheus` (or a comma list, or `*`). 4. The metrics become available at `GET /actuator/prometheus`. **What the response looks like** — the *Prometheus text exposition format*, e.g.: ``` # HELP http_server_requests_seconds # TYPE http_server_requests_seconds summary http_server_requests_seconds_count{method="GET",status="200",uri="/api"} 5.0 http_server_requests_seconds_sum{method="GET",status="200",uri="/api"} 0.42 ``` Each unique combination of metric name + label values is a **time series**. `# HELP`/`# TYPE` are metadata comments. The endpoint can also emit the OpenMetrics variant when the request sends `Accept: application/openmetrics-text`. **Pull model:** Prometheus (the server) periodically *scrapes* — issues an HTTP GET to — the endpoint. The application does not connect out to Prometheus; it just reflects the meter registry's current values each time it's scraped. That means the app needs no knowledge of where Prometheus lives, and Prometheus needs a scrape config pointing at the app (service discovery or a static target). Configuring/operating the Prometheus server, PromQL, and Grafana is a separate, downstream concern. **Common gotchas:** - Forgetting the `exposure.include` property → 404 at `/actuator/prometheus`. - Confusing `/actuator/metrics` (a JSON, human/navigation endpoint) with `/actuator/prometheus` (machine scrape format) — they are different endpoints backed by the same meters. - Securing the endpoint: in production the scrape path is often on a separate management port or protected, since it can leak URI cardinality and internal detail. - The endpoint only reports meters that exist; a meter with no recorded value yet may be absent until first use.
- Why don't you configure a Prometheus server URL in the application?Because Prometheus is pull-based — the server scrapes the app's endpoint. The app only exposes current values; it never initiates a connection, so no target URL lives in the app config.
- What is the difference between /actuator/metrics and /actuator/prometheus?/actuator/metrics is a JSON, browsable endpoint for querying individual meters by name; /actuator/prometheus dumps every meter in Prometheus's text exposition format for machine scraping. Both read the same MeterRegistry.
saying these in an interview costs you the question
- Thinking the app pushes metrics to Prometheus (it's pull/scrape).
- Believing metrics are exposed by default without setting exposure.include.
- Confusing /actuator/metrics JSON with the /actuator/prometheus scrape format.