skip to content

What is Metrics.globalRegistry, when would you use it, and what are the pitfalls versus injecting a MeterRegistry?

level: seniorimportance: should knowfreq 40%

answer

  1. static app-wide CompositeMeterRegistry
  2. for code that can't inject
  3. Metrics.addRegistry(backend)
  4. Boot composite != globalRegistry by default
  5. static state hurts test isolation

basics

~20 s

Metrics.globalRegistry is a static, application-wide CompositeMeterRegistry. It lets code without dependency injection record metrics. The pitfall in Spring: unless you wire the Boot registry into it, metrics recorded there aren't exported by Boot's own registry.

solid answer

~40 s

`io.micrometer.core.instrument.Metrics.globalRegistry` is a static `CompositeMeterRegistry` provided by Micrometer for situations where you can't inject a `MeterRegistry` — static utilities, library code, or legacy code without a Spring context. You record via `Metrics.counter(...)` or by passing `Metrics.globalRegistry` to a builder. Because it's a composite, you attach real backends with `Metrics.addRegistry(prometheusRegistry)`. The main pitfall in Spring Boot: Boot creates its *own* `CompositeMeterRegistry` bean and does **not** automatically make the global registry a delegate of it (nor vice versa) unless configured. So metrics sent to `globalRegistry` won't reach your Prometheus endpoint unless you explicitly `add` your backend to the global registry — or add the global registry to Boot's composite. Prefer constructor injection; reserve the global registry for code that genuinely has no access to the container. Being static, it also complicates test isolation.

code

java · 30 lines
java
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;

@Configuration
class MetricsBridgeConfig {

    private final MeterRegistry bootRegistry; // Boot's CompositeMeterRegistry

    MetricsBridgeConfig(MeterRegistry bootRegistry) {
        this.bootRegistry = bootRegistry;
    }

    // Bridge library code that instruments against Metrics.globalRegistry
    // so those meters are exported by Boot's configured backends.
    @EventListener(ApplicationReadyEvent.class)
    void bridgeGlobalRegistry() {
        Metrics.addRegistry(bootRegistry);
    }
}

// Library/static code that has no way to inject a registry:
class LegacyCache {
    void evict() {
        Metrics.counter("cache.evictions", "region", "users").increment();
    }
}

go deeper

for a junior

Know it's a static registry for code that can't inject one.

for a middle

Explain it's a composite you must attach backends to, and prefer injection.

for a senior

Diagnose the Boot-composite-vs-globalRegistry disconnect and bridge it correctly; note test-isolation costs.

for a principal

Set policy: injection by default, globalRegistry only for un-injectable library code, with an explicit, documented bridge and double-registration awareness.

**What it is:** `Metrics` is a Micrometer helper class exposing a single static field, `Metrics.globalRegistry`, of type `CompositeMeterRegistry`. It exists so that code with **no dependency-injection access** to a `MeterRegistry` can still instrument. You use it two ways: ```java Metrics.counter("cache.evictions", "region", "users").increment(); // or Timer.builder("lib.call").register(Metrics.globalRegistry); ``` Because it's a **composite**, it has no backend of its own until you attach one: `Metrics.addRegistry(new PrometheusMeterRegistry(...))` / `Metrics.removeRegistry(...)`. **Legitimate use cases:** - **Static/utility code** or framework/library code that can't take a `MeterRegistry` constructor argument. - **Third-party libraries** that self-instrument against the global registry by convention (some do), expecting the app to wire a backend in. - Quick experiments where DI plumbing isn't worth it. **The critical Spring Boot pitfall:** Boot's auto-configuration builds its **own** `CompositeMeterRegistry` bean (the one you inject) and, by default, **does not connect it to `Metrics.globalRegistry`.** They are two separate composites. Consequences: - Metrics you record on `Metrics.globalRegistry` will **not** appear at `/actuator/prometheus` unless you add your Prometheus registry to the global registry too (`Metrics.addRegistry(...)`), or add the global registry as a delegate of Boot's composite. - Conversely, code injecting the Boot registry won't see meters that only live on the global registry. - This mismatch is a classic 'my library's metrics are missing' bug. A common fix is a small configuration that bridges them, e.g. inject the Boot `MeterRegistry` and call `Metrics.addRegistry(registry)` at startup — but be aware of double-registration if a meter is created on both. **Why injection is preferred:** - **Testability:** the global registry is static process-wide state; tests can leak meters into each other. You must call `Metrics.globalRegistry.clear()` / remove registries between tests. Injected registries are per-context and reset naturally. - **Explicitness:** dependencies are visible in the constructor. - **Multiple contexts:** with several application contexts (integration tests, modular apps), a single static registry is ambiguous. **Gotchas / edge cases:** - `Metrics.globalRegistry.clear()` removes meters; `Metrics.removeRegistry(...)` detaches a backend. - Meters created on the global registry *before* a backend is added are back-filled when you add one (composite semantics). - Don't scatter `Metrics.counter(...)` calls throughout normal Spring application code just to avoid injecting — it's an anti-pattern that hides dependencies and risks the wiring gap above.

  • Why might metrics recorded via Metrics.globalRegistry be missing from /actuator/prometheus in a default Spring Boot app?
    Boot's injected CompositeMeterRegistry and Metrics.globalRegistry are separate composites; Boot doesn't auto-link them. Unless you add the Prometheus/Boot registry to the global registry (or add the global registry to Boot's composite), those meters have no exporting delegate.
  • What test hygiene does the global registry require?
    Because it is static, process-wide state, meters and attached registries persist across tests. You should clear it (Metrics.globalRegistry.clear()) and/or remove registries between tests to avoid cross-test contamination — a problem injected per-context registries don't have.

saying these in an interview costs you the question

  • Assuming Spring Boot automatically wires its registry into Metrics.globalRegistry.
  • Using Metrics.counter(...) everywhere instead of injecting a MeterRegistry.
  • Thinking globalRegistry exports on its own — it's an empty composite until you addRegistry.
  • Ignoring that static registry state breaks test isolation.

context