skip to content

How does Micrometer translate a meter name and tags for Prometheus versus OTLP? Walk through what happens to a timer named http.server.requests.

level: seniorimportance: should knowfreq 45%

answer

  1. dot.case internal → registry NamingConvention
  2. Prometheus: snake_case + _seconds/_total/_count/_sum/_bucket
  3. OTLP: keeps dotted name (OTel semconv)
  4. tags → Prometheus labels / OTLP attributes
  5. don't hand-code unit suffixes

basics

~10 s

Micrometer uses dot-separated names internally. The Prometheus registry rewrites them to snake_case and adds unit/_total suffixes (http_server_requests_seconds...), turning tags into labels. The OTLP registry keeps the dotted name (http.server.requests) per OpenTelemetry conventions.

solid answer

~40 s

Micrometer records meters with a naming convention that is dot-delimited and lowercase — e.g. `http.server.requests` with tags `method`, `status`, `uri`. Each registry applies its own `NamingConvention` at export. `PrometheusMeterRegistry` sanitizes the name to Prometheus rules: dots and other illegal characters become underscores (`http_server_requests`), the meter's base unit is appended as a suffix (a timer in seconds → `_seconds`), and a monotonically increasing counter gets `_total`; timers/summaries also emit `_count`, `_sum`, and `_bucket` series. Tags become Prometheus **labels**. `OtlpMeterRegistry` follows OpenTelemetry semantic conventions and preserves the dotted name `http.server.requests`, exporting tags as OTLP attributes. So the identical in-code meter surfaces as `http_server_requests_seconds_count{uri="/api"}` in Prometheus but `http.server.requests` with attributes in OTLP — a real gotcha when the same metric flows through both pipelines.

code

java · 21 lines
java
import io.micrometer.core.instrument.*;

Timer timer = Timer.builder("http.server.requests") // canonical dotted name
        .tag("uri", "/api")
        .tag("status", "200")
        .register(registry);

timer.record(() -> handle());

// Exported identities of the SAME meter:
//
// Prometheus (/actuator/prometheus):
//   http_server_requests_seconds_count{uri="/api",status="200"} 5
//   http_server_requests_seconds_sum{uri="/api",status="200"}   0.42
//
// OTLP (pushed to collector):
//   name = "http.server.requests"  (attributes: uri=/api, status=200)

// Counter example:
Counter c = registry.counter("orders.placed");
// Prometheus: orders_placed_total   |   OTLP: orders.placed

go deeper

for a junior

Know that dots become underscores in Prometheus and tags become labels.

for a middle

Recall the _total, _count, _sum, _seconds suffix rules and that OTLP keeps dotted names.

for a senior

Explain per-registry NamingConvention, unit suffixing from base units, and cross-pipeline naming pitfalls.

for a principal

Govern naming/cardinality across the estate (collector-side normalization, MeterFilter policy) rather than per-service hacks.

**Micrometer's internal naming:** You name meters using a lowercase, dot-separated convention (`orders.placed`, `http.server.requests`) and attach dimensions as **tags** (key/value pairs). Micrometer deliberately keeps names backend-agnostic; each registry owns a `NamingConvention` that maps this canonical form to what its backend requires. This lets one instrumentation call render correctly everywhere. **Prometheus mapping (`PrometheusMeterRegistry`):** Prometheus has strict name rules — names match `[a-zA-Z_:][a-zA-Z0-9_:]*` and by convention include a unit suffix. Micrometer's Prometheus naming convention therefore: - Replaces illegal characters (dots, hyphens) with underscores: `http.server.requests` → `http_server_requests`. - Appends the **base unit**: a `Timer` measures time, published in **seconds**, so you get the `_seconds` family. A `DistributionSummary` in bytes → `_bytes`. - Appends `_total` to monotonic counters: `orders.placed` → `orders_placed_total`. - Expands composite meters into multiple series: a timer emits `_count` (number of events), `_sum` (total time), and, if percentile histograms are enabled, `_bucket` series plus `+Inf`. - Tags become **labels**: `Timer.builder(...).tag("uri","/api")` → `{uri="/api"}`. Result example: `http_server_requests_seconds_count{method="GET",status="200",uri="/api"}`. **OTLP mapping (`OtlpMeterRegistry`):** OpenTelemetry's semantic conventions use dotted, namespaced names, so the OTLP registry **preserves** `http.server.requests` and carries tags as OTLP **attributes** rather than reformatting to snake_case. It also encodes the OTel instrument type and unit metadata in the protobuf payload. This means downstream OTel-native backends see the canonical dotted name. (When such data is later re-exported to Prometheus by a collector, the collector applies its own dot→underscore translation — so the naming difference reappears at that boundary.) **Why it matters / gotchas:** - **Dashboards and alerts are name-specific.** A PromQL query for `http_server_requests_seconds_count` won't match OTLP-native `http.server.requests`; teams running both pipelines must account for the two spellings. - **Unit suffixes are automatic, not free-form.** Don't bake units into the meter name yourself (`request.time.seconds`) — you'll get double suffixes or violate conventions. Let the registry add them from the meter's base unit. - **Counters and `_total`.** If you look for `orders_placed` and it's missing, remember Prometheus appended `_total`. - **Label/tag cardinality.** High-cardinality tags (raw user ids, unbounded URIs) explode the number of series/attributes in both systems; this is the dominant scaling risk, independent of naming. - **Custom conventions.** You can override behavior with a custom `NamingConvention` or `MeterFilter`, but doing so per-registry to force matching names across backends is fragile — prefer aligning at the collector. Overall: same meter in code, two different external identities. Know that Prometheus = snake_case + unit/`_total` + `_count`/`_sum`/`_bucket`, OTLP = dotted OTel-convention names + attributes.

  • Why does a counter named orders.placed show up as orders_placed_total in Prometheus?
    Prometheus convention marks monotonically increasing counters with a _total suffix, and Micrometer's Prometheus NamingConvention appends it automatically (plus converting the dot to an underscore).
  • Should you include the unit in the meter name yourself?
    No. The registry appends the base unit (e.g. _seconds for a Timer) from the meter's declared unit. Hard-coding it risks double suffixes and breaks the OTLP dotted-name convention.

saying these in an interview costs you the question

  • Assuming the meter name is byte-identical across Prometheus and OTLP.
  • Manually appending _seconds/_total into the meter name.
  • Forgetting Prometheus adds _count/_sum/_bucket series for a single timer.

context