skip to content

How do you add your own MeterBinder or common tags alongside the auto-configured meters, and when should you prefer that over @Timed/manual meters?

level: seniorimportance: nice to knowfreq 28%

answer

  1. MeterBinder @Bean auto-bound by MeterRegistryPostProcessor
  2. Gauge over live object = weak reference, sampled on scrape
  3. common tags: MeterRegistryCustomizer / MeterFilter.commonTags / management.metrics.tags.*
  4. Gauge=state, Counter/Timer=events, @Timed needs TimedAspect
  5. customizers run before binders so built-ins get common tags

basics

~20 s

Expose a MeterBinder @Bean and Boot binds it to the registry automatically, just like the built-ins. To add tags to every meter (built-in included) register a MeterRegistryCustomizer or MeterFilter.commonTags. Prefer a MeterBinder when you're exposing the state of a long-lived resource.

solid answer

~40 s

Boot's `MeterRegistryPostProcessor` binds every `MeterBinder` bean to the primary `MeterRegistry`, so your custom binder is treated exactly like `JvmMemoryMetrics`. Use a MeterBinder when you want to expose the ongoing state of a resource (queue depth, cache size, pool stats) as gauges tied to a live object — the binder holds the reference and reports on scrape. To decorate ALL meters (built-ins plus yours) with common tags such as application/region/instance, register a `MeterRegistryCustomizer<MeterRegistry>` that calls `registry.config().commonTags(...)`, or a `MeterFilter.commonTags(...)` bean — both apply before meters are created. Prefer a MeterBinder/gauge for continuous state; prefer `@Timed` or an injected `Timer`/`Counter` for discrete events (a method call, a business action). Don't hold strong references that leak; Micrometer gauges keep a weak reference to the measured object by design.

code

java · 23 lines
java
@Configuration
class CustomMetrics {

    // 1) Your own MeterBinder — bound to the registry just like the built-ins.
    @Bean
    MeterBinder cacheMetrics(MyCache cache) {
        return registry -> Gauge.builder("app.cache.entries", cache, MyCache::size)
                                .register(registry);
    }

    // 2) Common tags applied to EVERY meter (built-in + custom).
    @Bean
    MeterRegistryCustomizer<MeterRegistry> commonTags(
            @Value("${spring.application.name}") String app) {
        return registry -> registry.config().commonTags("application", app);
    }

    // 3) Needed if you use @Timed on methods.
    @Bean
    TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}

go deeper

for a junior

Know you can add a MeterBinder bean and it shows up automatically.

for a middle

Choose Gauge-via-binder for state vs Counter/Timer for events; add common tags via config.

for a senior

Explain the customizer-before-binder ordering and the weak-reference gauge semantics.

for a principal

Define an org-wide tagging/instrumentation standard layered cleanly over the built-ins.

## Registering your own MeterBinder The same machinery that wires the built-ins wires yours. Boot's `MeterRegistryPostProcessor` (an internal `BeanPostProcessor`) collects all `MeterBinder` beans and, once the primary `MeterRegistry` is ready, calls `binder.bindTo(registry)`. So: ```java @Bean MeterBinder queueMetrics(WorkQueue queue) { return registry -> Gauge.builder("app.queue.depth", queue, WorkQueue::size) .description("pending jobs") .register(registry); } ``` That gauge now appears next to `jvm.memory.used` in `/actuator/metrics`. A `MeterBinder` is ideal when the metric is the *current state of a long-lived object*: you pass the object + a `ToDoubleFunction`, and Micrometer samples it on scrape. Micrometer holds a **weak reference** to that object, so if it's GC'd the gauge reads NaN — keep the object alive (it usually is, being a bean). ## Common tags for every meter To stamp `application`, `region`, `instance`, `env` on all meters (built-in AND custom) so dashboards can slice by service: - `MeterRegistryCustomizer<MeterRegistry>` bean: `registry -> registry.config().commonTags("application", "orders")`. Boot applies customizers before binders run, so built-ins get them too. - or a `MeterFilter.commonTags(Tags.of(...))` bean. - or declaratively: `management.metrics.tags.application=orders`. Order matters: common tags must be configured before meters are created; Boot sequences `MeterRegistryCustomizer` before `MeterBinder` binding for exactly this reason. ## MeterBinder vs @Timed vs manual meter — when - **MeterBinder / Gauge**: continuous state you *observe* (queue depth, connection count, cache entries, feature-flag count). Pull-based; no event to record. - **`@Timed`** (from `io.micrometer.core.annotation.Timed`, needs `TimedAspect`): quick timing of a specific method; good for a handful of service methods. - **Injected `Timer`/`Counter`/`DistributionSummary`**: discrete business events where you want explicit control, tags per event, or to record success/failure counters (`orders.placed`, `payment.failed`). Rule of thumb: if you'd implement it with a getter that returns 'how many/how much right now', use a Gauge via MeterBinder; if it's 'something happened / took this long', use a Counter/Timer. ## Gotchas - Don't register the same meter name+tags twice with conflicting types — Micrometer will warn/return the existing one. - Gauges must not be built over values you mutate and re-register; register once over the live source object. - Avoid strong references in a static field that outlive the object (leak); rely on the weak-reference gauge pattern. - `@Timed` requires you to define a `TimedAspect` bean (Boot doesn't always auto-create it) and AOP on the classpath. ## When to prefer over built-ins You never replace built-ins; you *augment* them. Add a MeterBinder for domain resources the JVM/HTTP binders can't see, and common tags so all meters (yours + built-in) are queryable per service/instance.

  • You register a Gauge over a temporary object and it later reads NaN. Why?
    Micrometer gauges hold a weak reference to the measured object so they don't prevent GC. If that object is collected, the gauge has nothing to sample and reports NaN. Register the gauge over a long-lived bean/collection, not a throwaway.
  • How do you ensure your common application tag also lands on the auto-configured jvm/http meters?
    Register the common tags via a MeterRegistryCustomizer (or MeterFilter.commonTags / management.metrics.tags.*). Boot applies MeterRegistryCustomizers to the registry before it binds the built-in MeterBinders, so the built-in meters inherit the common tags too.

saying these in an interview costs you the question

  • Thinking you must manually call bindTo — Boot does it for any MeterBinder bean
  • Using a Gauge for a discrete event or a Counter for continuous state
  • Expecting @Timed to work without a TimedAspect bean
  • Registering a gauge over a short-lived object and being surprised by NaN

context