skip to content

Explain the http.server.requests meter: what tags does it carry and how is it recorded?

level: middleimportance: must knowfreq 68%

answer

  1. Timer per request
  2. tags: uri(template), method, status, outcome, exception
  3. uri templated => bounded cardinality; 404=NOT_FOUND
  4. ServerHttpObservationFilter + Observation
  5. percentiles-histogram for aggregatable p99

basics

~20 s

http.server.requests is a Timer recorded for every HTTP request, tagged with uri, method, status and outcome so you can see request rate, error rate and latency per endpoint. It is added automatically by an Actuator filter.

solid answer

~40 s

http.server.requests is the flagship server-side Timer. In current Boot it is produced through Micrometer Observation: a `ServerHttpObservationFilter` (Servlet) or WebFlux equivalent starts/stops an observation per request, and a default convention derives the tags. Standard tags: `method` (GET/POST), `status` (200/404/500), `outcome` (SUCCESS/CLIENT_ERROR/SERVER_ERROR), `exception` (thrown exception class or none), and crucially `uri` — the *matched route template* like `/users/{id}`, not the raw path. Using the template is deliberate: it keeps cardinality bounded. You then query rate = count over time, error rate via status/outcome tags, and latency via the timer's percentiles/histogram. You can enable percentile histograms with `management.metrics.distribution.percentiles-histogram.http.server.requests=true` to get Prometheus histogram buckets for server-side quantiles.

code

java · 17 lines
java
// Add a safe custom tag via the Observation convention (Boot 3).
import org.springframework.http.server.observation.*;
import io.micrometer.common.KeyValues;
import io.micrometer.common.KeyValue;

@Component
class TenantTagConvention extends DefaultServerRequestObservationConvention {
    @Override
    public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext ctx) {
        String tenant = ctx.getCarrier() != null
            ? ctx.getCarrier().getHeader("X-Tenant") : null;
        return super.getLowCardinalityKeyValues(ctx)
            .and(KeyValue.of("tenant", tenant == null ? "none" : tenant));
    }
}
// application.properties
// management.metrics.distribution.percentiles-histogram.http.server.requests=true

go deeper

for a junior

Know it's a per-request Timer with uri/status tags for rate/error/latency.

for a middle

List all standard tags, explain templated uri, and enable histograms via config.

for a senior

Explain the Observation filter/convention pipeline and safe custom-tag extension.

for a principal

Reason about cardinality budgets, aggregatable quantiles across instances, and SLO buckets.

## What it is `http.server.requests` is a **Timer** — a meter that tracks *count of events*, *total time*, and *max* — recorded once per handled HTTP request. It is the single most useful auto-configured meter because it yields the RED signals (Rate, Errors, Duration) for your API. ## How it's recorded Modern Spring Boot (2.x late / 3.x) routes this through the **Micrometer Observation API**. A filter — `org.springframework.web.filter.ServerHttpObservationFilter` for Servlet MVC (or a WebFlux `WebFilter`) — wraps each request in an `Observation`. When the observation stops, registered `ObservationHandler`s (including a `TimerObservationHandler`) emit the timer sample into the `MeterRegistry`. The tag values come from an `ObservationConvention` (default `DefaultServerRequestObservationConvention`). In older Boot it was a plain `WebMvcMetricsFilter`; the tag semantics are the same. ## The tags (KeyValues) - **`uri`** — the *route template that matched the handler*, e.g. `/api/users/{id}`, `root` for `/`, `NOT_FOUND` for unmatched, `REDIRECTION` for 3xx. This is the anti-high-cardinality design: without templating, `/users/1`, `/users/2` … would each be a separate time series and blow up your metrics store. - **`method`** — HTTP verb. - **`status`** — numeric HTTP status as a string. - **`outcome`** — SUCCESS (2xx), REDIRECTION (3xx), CLIENT_ERROR (4xx), SERVER_ERROR (5xx), INFORMATIONAL, UNKNOWN. - **`exception`** — simple class name of an exception that propagated (e.g. `IllegalStateException`), else `none`. ## Latency / histograms A Timer by default publishes count + sum + max. To get quantiles you either: - **client-side percentiles**: `management.metrics.distribution.percentiles.http.server.requests=0.95,0.99` (computed in-app, NOT aggregatable across instances), or - **percentile histograms** (preferred for Prometheus): `management.metrics.distribution.percentiles-histogram.http.server.requests=true`, which emits cumulative buckets so quantiles are computed in the backend (`histogram_quantile`) and are aggregatable. You can also set SLO boundaries with `management.metrics.distribution.slo.http.server.requests=50ms,100ms,...`. ## Gotchas - **Custom tags**: implement a `ServerRequestObservationConvention` (or legacy `WebMvcTagsContributor`) — do NOT put raw request-path or user-id as a tag (cardinality explosion). - **404s**: uri is tagged `NOT_FOUND`, not the actual missing path, again to bound cardinality. - **Async / streaming**: timing covers the full request lifecycle including async dispatch. - **Only observed tag combinations appear** in /actuator/metrics; an endpoint never hit yet has no series. - **`@Timed`** on a controller method adds an extra timer but is not required — the auto filter already times everything. ## When to use Use it as your primary API dashboard/alert source (p99 latency, 5xx rate per uri). Reserve custom timers for business operations not tied to an HTTP endpoint.

  • Why is the uri tag a route template and not the actual path?
    To bound cardinality. Raw paths like /users/1, /users/2 would each become a distinct time series and overwhelm the metrics backend. The template /users/{id} collapses them into one series while still distinguishing endpoints. Unmatched paths become NOT_FOUND for the same reason.
  • What's the difference between percentiles and percentiles-histogram config?
    percentiles computes quantiles inside each instance — cheap but NOT aggregatable across instances. percentiles-histogram emits cumulative buckets so the backend (e.g. Prometheus histogram_quantile) computes aggregatable quantiles across the whole fleet. Prefer histograms for distributed systems.

saying these in an interview costs you the question

  • Claiming the uri tag holds the raw request path
  • Thinking client-side percentiles can be averaged/aggregated across instances
  • Saying you must annotate every controller with @Timed to get http.server.requests
  • Putting user id or full URL as a metric tag

context