How do MeterRegistryCustomizer and MeterFilter interact with a CompositeMeterRegistry — where do common tags and filters actually apply?
answer
- customizer = per-registry config hook
- commonTags via registry.config()
- filter on composite = whole fan-out
- type-target child for backend-specific rules
- filters run in order, before creation
basics
~20 sIn Spring Boot you customize registries with MeterRegistryCustomizer beans (e.g. add common tags) and shape/filter meters with MeterFilter. Boot applies customizers to each registry including the composite; a filter on the composite affects all children fan-out.
solid answer
~40 sSpring Boot applies every `MeterRegistryCustomizer<MeterRegistry>` bean to each auto-configured registry — including the primary `CompositeMeterRegistry` and each child system registry. The idiomatic way to add application-wide dimensions is `registry.config().commonTags("application", name, "region", "eu")` inside a customizer; common tags added on the composite propagate to fan-out meters. `MeterFilter`s (deny, accept, rename, map, `maximumAllowableTags`, distribution config) are registered via `registry.config().meterFilter(...)`; when applied to the composite they govern what the composite forwards. Because each child registry also has its own `config()`, you can apply backend-specific rules (e.g. Prometheus-only histogram buckets) by customizing only that registry type. Order matters: filters run in registration order and are evaluated before a meter is created, so deny filters can prevent registration entirely. Boot also lets you set common tags and cardinality limits via `management.metrics.*` properties.
code
java · 27 linesimport io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class MetricsConfig {
// Applied to EVERY registry, including the composite -> tags fan out everywhere
@Bean
MeterRegistryCustomizer<MeterRegistry> commonTags() {
return registry -> registry.config()
.commonTags("application", "katajob", "region", "eu-west-1")
// cardinality guard applied globally
.meterFilter(MeterFilter.maximumAllowableTags(
"http.server.requests", "uri", 100, MeterFilter.deny()));
}
// Type-targeted: only the Prometheus child registry gets this rule
@Bean
MeterRegistryCustomizer<PrometheusMeterRegistry> prometheusOnly() {
return registry -> registry.config()
.meterFilter(MeterFilter.denyNameStartsWith("debug"));
}
}go deeper
Know common tags are added via a MeterRegistryCustomizer, not per call site.
Explain MeterFilter deny/accept and where customizers apply in Boot.
Reason about composite-vs-child targeting, filter order/timing, and cardinality guards.
Set org-wide policy: which tags/filters live at composite vs backend level, cost control per backend, and idempotency of customizers.
Two extension points shape metrics in Spring Boot, and understanding how they combine with the **composite** is the senior-level point. **1. `MeterRegistryCustomizer<T extends MeterRegistry>`** — a functional bean interface Boot invokes for every registry it auto-configures. Boot applies it to **each** registry it creates, which includes the primary `CompositeMeterRegistry` *and* each child (Prometheus, Datadog, …). Typical use is adding **common tags** — dimensions attached to every meter: ```java @Bean MeterRegistryCustomizer<MeterRegistry> commonTags(@Value("${spring.application.name}") String app) { return registry -> registry.config().commonTags("application", app, "region", "eu-west-1"); } ``` Because the customizer is applied to the composite, and the composite fans out, every child sees the tag on every meter. You can **type-target** a customizer — `MeterRegistryCustomizer<PrometheusMeterRegistry>` — to only touch that backend (e.g. set a Prometheus-specific naming convention or histogram config), leveraging Java generics matching. **2. `MeterFilter`** — the mechanism to **transform, deny, accept, or configure distribution** on meters. Registered via `registry.config().meterFilter(filter)` (often inside a customizer). Capabilities include: - `MeterFilter.deny(...)` / `accept(...)` — drop or keep meters by predicate. - `MeterFilter.denyNameStartsWith("jvm")` — coarse suppression. - `MeterFilter.ignoreTags(...)` / rename via mapping. - `MeterFilter.maximumAllowableTags(...)` and `maximumAllowableMetrics(...)` — **cardinality guards** to cap tag/series explosion. - Distribution config (percentiles, SLO/histogram buckets) via `configure(...)`. **Where filters apply with a composite:** filters registered on the **composite** are evaluated when a meter is created through the composite, so they govern the whole fan-out — a deny filter there stops the meter from being registered anywhere. Filters registered on an **individual child** only affect that backend's copy. This lets you, for example, keep high-cardinality debug metrics out of a paid backend while allowing them locally in Prometheus by filtering only the Datadog child. **Evaluation semantics / gotchas:** - Filters run **in the order registered**, and **before** the meter is created. A meter denied by a filter is never registered. - Adding a filter **after** meters already exist does **not** retroactively remove them — configure filters at startup. - Common tags are themselves implemented as a `MeterFilter`, so tag additions and denies interleave by registration order. - Boot's property-driven config (`management.metrics.tags.*`, `management.metrics.distribution.*`, `management.metrics.enable.*`) is translated into filters/customizers under the hood. - Applying the same customizer to both composite and children can double-apply logic that isn't idempotent — prefer applying common tags at one level (Boot's default of applying to all is fine because commonTags is idempotent by key). **When to use which:** use a **customizer** to *configure* a registry (tags, naming convention, filters, distribution defaults); use a **MeterFilter** as the concrete rule inside it. Target the composite for global policy, target a child type for backend-specific policy.
- How would you send a high-cardinality debug metric to Prometheus but keep it out of a paid backend like Datadog?Register a deny MeterFilter only on the Datadog child registry (via a MeterRegistryCustomizer<DatadogMeterRegistry>), leaving the Prometheus child unfiltered. Filters on a child affect only that backend's fan-out copy.
- Why must MeterFilters be registered at startup rather than lazily?Filters are evaluated when a meter is created; a deny filter added after the meter already exists will not retroactively remove it. Startup registration ensures the rule is in effect before any instrumentation runs.
saying these in an interview costs you the question
- Thinking common tags must be added manually at every call site instead of via a customizer.
- Believing a filter added after meters exist retroactively deletes them.
- Assuming a filter on the composite doesn't affect children (it governs the fan-out).
- Not knowing filters run in registration order and before meter creation.