Micrometer Metrics Bridge (juneau-rest-server-metrics-micrometer)
The juneau-rest-server-metrics-micrometer module bridges every @RestOp call to a Micrometer
MeterRegistry, enabling Prometheus / Grafana
dashboards, StatsD, Datadog, New Relic, and any other Micrometer-backed backend with a single bean
registration.
This page covers the module-specific classes and wiring patterns. The shared opt-in contract,
the MetricsRecorder SPI, the per-resource / per-operation annotation overrides, and the custom
(non-request) observation API are described in
Observability — Micrometer + OpenTelemetry.
Maven dependency
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-server-metrics-micrometer</artifactId>
<version>${juneau.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.13.6</version> <!-- consumer-supplied; provided scope on the bridge module -->
</dependency>
<!-- pick the registry you want; example uses Prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.13.6</version>
</dependency>
micrometer-core is declared in provided scope on the bridge module. Consumers pick their own
Micrometer version (and the registry that matches their backend); core juneau-rest-server stays
Micrometer-free.
Classes in this module
| Class | Role |
|---|---|
MicrometerMetricsRecorder | MetricsRecorder impl that records to a MeterRegistry. |
MetricsMixin | @Rest(mixins=...) flavor of a /metrics Prometheus scrape endpoint. |
MetricsResource | @Rest(children=...) flavor of the same endpoint, routed at /metrics. |
MetricsManager | Internal worker used by both endpoint flavors; consumers do not use it directly. |
MicrometerMetricsRecorder
MicrometerMetricsRecorder is the MetricsRecorder bridge. Register one as a @Bean on your
resource (or in a Spring @Configuration) to activate per-request metrics:
@Configuration
public class ObservabilityConfig {
@Bean
public MeterRegistry meterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Bean
public MetricsRecorder metricsRecorder(MeterRegistry r) {
return new MicrometerMetricsRecorder(r);
}
}
Constructors
// Default timer name "http.server.requests" (matches Spring Boot's WebMvcMetricsFilter convention).
new MicrometerMetricsRecorder(MeterRegistry registry)
// Custom timer name — use when an existing dashboard uses a different name.
new MicrometerMetricsRecorder(MeterRegistry registry, String timerName)
The getRegistry() and getTimerName() accessors are available for testing / introspection.
Metric shape
Each @RestOp call records one Timer sample. Default timer name: http.server.requests.
| Tag | Value |
|---|---|
method | Uppercased HTTP method (GET, POST, …). |
uri | The @RestOp path template (e.g. /users/{id}), not the raw URI — keeps cardinality bounded. Empty string when the operation has no path pattern. |
status | HTTP response status code as a string (200, 404, 500, …). |
exception | Thrown exception simple-name, or None on a normal completion. |
The tag set matches Spring Boot's WebMvcMetricsFilter convention so existing Prometheus / Grafana
dashboards work without modification when a service migrates from Spring Boot to Juneau:
http_server_requests_seconds_count{method="GET",uri="/users/{id}",status="200",exception="None"} 1
http_server_requests_seconds_sum {method="GET",uri="/users/{id}",status="200",exception="None"} 0.007
Custom timer name
@Bean public MetricsRecorder recorder(MeterRegistry r) {
return new MicrometerMetricsRecorder(r, "myservice.http.timer");
}
Per-operation metric name and tags
Override the timer name or add extra tags for individual operations using the @RestOp attributes
(see Observability — per-resource and per-operation controls):
@RestGet("/orders/{id}")
@RestOp(metricName="orders.get", metricTags="team=payments,region=us-east")
public Order getOrder(@Path String id) { ... }
When metricName is non-empty it overrides the recorder-level timer name for that operation.
metricTags are merged into the standard tag set as additional key=value pairs.
Custom (non-request) observations
MicrometerMetricsRecorder also overrides the non-request record(String metricName, String metricTags, Duration elapsed, Throwable error) default method, so it works with Observations.observe(...) for timing
arbitrary code blocks outside the request boundary:
try (Observation o = Observations.observe("order.load", "team=payments")) {
return loadOrder(id);
} catch (RuntimeException e) {
o.setError(e);
throw e;
}
The non-request record emits a Timer named metricName carrying the exception tag plus any
caller-supplied metricTags. There are no method/uri/status tags because there is no
request.
/metrics endpoint — Prometheus scrape
The module ships two pre-built endpoint components that expose the MeterRegistry as a Prometheus
scrape surface. Both delegate to the same internal MetricsManager worker so they cannot drift.
When no PrometheusMeterRegistry bean is registered, both degrade to HTTP 501 (Not Implemented).
MetricsMixin — add /metrics to an existing resource
Use MetricsMixin when you want the scrape endpoint to live on the same path prefix as your API
resource:
@Rest(
path="/api",
mixins=MetricsMixin.class // adds GET /api/metrics
)
public class ApiResource extends BasicRestServlet {
// your @RestOp methods
}
GET /api/metrics returns the Prometheus exposition text.
MetricsResource — routed child under /metrics
Use MetricsResource when you prefer a dedicated child resource mounted at /metrics:
@Rest(
path="/api",
children=MetricsResource.class // mounts GET /api/metrics/*
)
public class ApiResource extends BasicRestServlet {
// your @RestOp methods
}
The child resource inherits its parent's bean store, so the consumer's MeterRegistry bean is
reachable without any extra configuration.
Full wiring example
@Configuration
public class ObservabilityConfig {
@Bean
public MeterRegistry meterRegistry() {
// PrometheusMeterRegistry — scraped by the MetricsMixin/MetricsResource endpoint.
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Bean
public MetricsRecorder metricsRecorder(MeterRegistry r) {
return new MicrometerMetricsRecorder(r);
}
}
@Rest(
path="/api",
observability="true", // fail-fast if no backend is wired
mixins=MetricsMixin.class // exposes GET /api/metrics
)
public class ApiResource extends BasicRestServlet {
@RestGet("/users/{id}")
public User getUser(@Path String id) { ... }
@RestGet("/health")
@RestOp(observability="false") // exclude health-check from metrics
public String health() { return "ok"; }
}
With a PrometheusMeterRegistry in scope, GET /api/metrics returns:
http_server_requests_seconds_count{method="GET",uri="/users/{id}",status="200",exception="None"} 12
http_server_requests_seconds_sum {method="GET",uri="/users/{id}",status="200",exception="None"} 0.054
See also
- Observability — Micrometer + OpenTelemetry — overview,
the
MetricsRecorderSPI, opt-in contract, per-resource/op annotation controls, and custom observations. - OpenTelemetry Tracing Bridge — the companion module for distributed tracing.
- REST Server — Management Surface — the broader management /
health endpoint surface, of which
MetricsMixin/MetricsResourceare a part.