skip to content

Explain Micrometer's 'registry per monitoring system' model and the role of CompositeMeterRegistry.

level: middleimportance: must knowfreq 60%

answer

  1. one registry impl per backend
  2. composite = fan-out MeterRegistry
  3. Boot's primary bean IS the composite
  4. add()/remove() delegates at runtime
  5. empty composite = silent no-op

basics

~20 s

Each monitoring backend has its own MeterRegistry implementation (one per system). To send the same metrics to several backends at once, Micrometer uses a CompositeMeterRegistry that holds child registries and forwards every meter operation to all of them.

solid answer

~40 s

Micrometer follows a 'registry per monitoring system' design: `PrometheusMeterRegistry`, `DatadogMeterRegistry`, `CloudWatchMeterRegistry`, etc. Each concrete registry knows one backend's naming rules, publishing cadence, and transport. When you want to publish to more than one backend simultaneously, you don't instrument twice — you use a `CompositeMeterRegistry`, which is itself a `MeterRegistry` that fans out. It holds a set of delegate registries; creating a meter on the composite creates a corresponding meter in each delegate, and recording on the composite records to all of them. In Spring Boot this is exactly how multi-backend works: Boot creates one `CompositeMeterRegistry` as the primary bean and adds every auto-configured system registry to it, so your injected `MeterRegistry` is the composite. You can add or remove delegates at runtime via `add()`/`remove()`. A composite with no delegates silently drops data.

code

java · 26 lines
java
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;

public class CompositeDemo {
    public static void main(String[] args) {
        SimpleMeterRegistry a = new SimpleMeterRegistry();
        SimpleMeterRegistry b = new SimpleMeterRegistry();

        CompositeMeterRegistry composite = new CompositeMeterRegistry();
        composite.add(a);
        composite.add(b);

        // One instrumentation call fans out to BOTH delegates
        composite.counter("jobs.processed").increment();

        System.out.println(a.get("jobs.processed").counter().count()); // 1.0
        System.out.println(b.get("jobs.processed").counter().count()); // 1.0

        // Delegates can change at runtime; existing meters back-fill into new ones
        SimpleMeterRegistry c = new SimpleMeterRegistry();
        composite.add(c);
        composite.counter("jobs.processed").increment();
        System.out.println(c.get("jobs.processed").counter().count()); // 1.0
    }
}

go deeper

for a junior

Know that different backends have different registries and a composite can hold several.

for a middle

Explain fan-out semantics, runtime add/remove, and that Boot's injected bean is the composite.

for a senior

Discuss per-child naming conventions/filters, back-fill of meters into late-added delegates, and the empty-composite no-op.

for a principal

Reason about where filters/customizers belong (composite vs child), nested composites, and operational consequences of add/remove during runtime.

**The design principle:** Micrometer deliberately provides *one registry implementation per monitoring system* rather than one universal registry with pluggable exporters. Examples: `PrometheusMeterRegistry` (pull-based scrape endpoint), `DatadogMeterRegistry`, `CloudWatchMeterRegistry`, `NewRelicMeterRegistry`, `GraphiteMeterRegistry`, `SimpleMeterRegistry` (in-memory). Each one encapsulates that backend's specifics: **naming convention** (e.g. Prometheus wants `http_server_requests_seconds`, others use dots), **push vs pull** semantics, **publishing interval** (`step`), rate/counter reset behavior, and the client that ships data. **Why one-per-system matters:** naming and data-model rules differ enough that a single registry can't serve them all cleanly. Keeping them separate means each registry applies its own `NamingConvention` and can be configured independently (different steps, different tags). **The multi-backend problem:** what if you must publish the *same* instrumentation to two systems at once — say Prometheus for local scraping and Datadog for hosted dashboards? Instrumenting twice would be duplicative and error-prone. **CompositeMeterRegistry** solves this. It is a `MeterRegistry` implementation whose meters are **thin proxies that fan out** to a set of child ('delegate') registries. Behavior: - `composite.add(registry)` / `composite.remove(registry)` manage delegates; delegates can change at **runtime**. - Creating a meter on the composite lazily creates a matching meter in each current delegate. - Recording (increment, record time, sample gauge) is forwarded to **every** delegate, each of which applies its own naming/convention and publishes on its own schedule. - Delegates added *after* a meter was created are back-filled — the composite registers the already-known meters into the newly added registry. - A composite with **zero** delegates is a valid no-op: meter operations succeed but data goes nowhere (a common cause of 'my metrics disappeared'). - You can nest composites (a composite inside a composite) since a composite is itself a `MeterRegistry`. **In Spring Boot specifically:** the actuator auto-configuration creates a **`CompositeMeterRegistry` as the primary `MeterRegistry` bean** and registers every auto-configured system registry into it. So when you inject `MeterRegistry`, you almost always receive the composite. This is what makes 'add another backend by adding a starter' work with zero code change — the new system registry just joins the composite. Boot also applies `MeterRegistryCustomizer` beans (e.g. common tags) — note that customizers applied to the composite propagate to the children; customizers targeting a specific child type apply only there. **Gotchas:** - Because the composite fans out, per-backend `MeterFilter`s (deny/accept/cardinality limits) are configured *on the child registries*, not just the composite — though Boot lets you register filters that apply broadly. - `SimpleMeterRegistry` is *not* added to the composite when a real system registry exists; it's only the fallback. - Removing a delegate stops future publishing to it but doesn't retroactively delete already-shipped data.

  • When you inject MeterRegistry into a Spring bean, which concrete type do you usually get, and why does that matter?
    Usually a CompositeMeterRegistry — Boot makes it the primary bean and adds each system registry to it. It matters because it means you can add a new backend by adding a starter with no code change, and because with no child registries data silently goes nowhere.
  • How do you send metrics to Prometheus and Datadog at the same time without instrumenting twice?
    Add both micrometer-registry-prometheus and micrometer-registry-datadog starters. Boot auto-configures both system registries and adds them to the shared CompositeMeterRegistry, so every meter you record fans out to both automatically.

saying these in an interview costs you the question

  • Thinking you must record each metric once per backend.
  • Believing CompositeMeterRegistry itself exports to a backend — it only forwards to delegates.
  • Assuming a composite with no delegates throws or warns; it silently discards data.
  • Claiming there is a single universal registry with pluggable exporters.

context