What is Micrometer's Observation API, and how do you record a piece of work using an ObservationRegistry?
answer
- createNotStarted then observe()
- registry fans one Observation to many signals
- name = low-cardinality like a metric name
- no handlers = no-op
- observe() stops even on exception
basics
~20 sThe Observation API lets you wrap a piece of code once so Spring can produce monitoring signals from it. You create an Observation on an ObservationRegistry with a name and call observe(...) around your code.
solid answer
~40 sThe Observation API (package io.micrometer.observation) is Micrometer's single instrumentation entry point. You instrument code once, and configured handlers turn that one Observation into metrics, traces, and logs. The central object is the ObservationRegistry, which holds the handlers; Spring Boot auto-configures one as a bean. Typical usage: Observation.createNotStarted("order.process", registry) returns a not-yet-started Observation, then observe(Runnable) starts it, runs your code, records errors, and stops it — even on exception. You give it a stable, low-cardinality name (like a metric name) and optionally attach KeyValues (tags). Without the API you'd wire a Timer, a tracing span, and log statements separately; with it you write the instrumentation once and let the registry fan it out.
code
java · 22 linesimport io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.stereotype.Service;
@Service
class OrderService {
private final ObservationRegistry registry; // auto-configured by Spring Boot
OrderService(ObservationRegistry registry) {
this.registry = registry;
}
Receipt process(Order order) {
// observe() starts, runs, records errors, and always stops the Observation
return Observation.createNotStarted("order.process", registry)
.lowCardinalityKeyValue("type", order.type())
.observe(() -> doProcess(order));
}
private Receipt doProcess(Order order) { /* ... */ return new Receipt(); }
}go deeper
Should know the observe() one-liner and that the registry is injected.
Explains createNotStarted vs start, and that no-handler = no-op.
Contrasts the API with hand-wiring Timer+span+logs and knows the name-cardinality rule.
Frames it as the single instrumentation seam that decouples business code from which signals ship.
## What the Observation API is The **Observation API** lives in Micrometer (`io.micrometer.observation.*`) and is the foundation Spring Boot 3+/Spring Framework 6+ uses for observability. Its slogan is **"instrument once, get many signals."** You describe a unit of work as an **Observation**; the registry's handlers then emit whatever signals are configured — a **metric** (timer/counter via Micrometer), a **trace span** (via Micrometer Tracing → Brave/OpenTelemetry), and structured **logs/events** — all from that single instrumentation. ## ObservationRegistry `ObservationRegistry` is the hub. It holds a list of **`ObservationHandler`** instances and an **`ObservationConfig`**. When an Observation starts/stops, the registry notifies every handler whose `supportsContext(...)` returns true. Spring Boot **auto-configures an `ObservationRegistry` bean**, wiring in handlers for metrics (`DefaultMeterObservationHandler`) and, if tracing is on the classpath, tracing handlers. You just inject the bean. A registry with **no handlers is a no-op** — Observations do nothing. There's also `ObservationRegistry.NOOP` for tests or code paths where you don't want signals. ## Creating and running an Observation Common factory methods (all static on `Observation`): - `Observation.createNotStarted(name, registry)` — builds an Observation but does **not** start it. You still need to `start()` it (or use `observe`). - `Observation.start(name, registry)` — creates **and** starts in one call. - `Observation.createNotStarted(name, contextSupplier, registry)` — with a custom `Observation.Context`. To run work inside it: - `observation.observe(Runnable)` / `observe(Supplier<T>)` — starts it, opens a scope, runs the code, records any thrown exception via `error(...)`, and **always** stops it in a finally block. This is the recommended, leak-proof form. - Manual form: `start()` … `openScope()` … your code … `error(e)` on failure … `scope.close()` … `stop()`. ## The name The Observation **name** is like a metric name: **low cardinality, stable, dot-separated** (e.g. `"order.process"`). It becomes the timer name and the span name. Don't put IDs in it — use KeyValues for varying data. ## Minimal example ```java Observation.createNotStarted("order.process", registry) .observe(() -> orderService.process(order)); ``` ## Gotchas - **Not-started ≠ running.** `createNotStarted` alone emits nothing; you must start it or call `observe`. - **No handlers = nothing happens.** If metrics/tracing aren't configured, the Observation is silently inert. - Prefer `observe(...)` over manual start/stop so you never leak an unstopped Observation on exception. - The name must stay stable; varying it explodes cardinality. ## When to use Use it for **custom business spans/timers** you want reflected in all three pillars at once — e.g., a payment step, a batch job, an external call — instead of hand-wiring a `Timer` plus a tracer span plus logs.
- What happens if the ObservationRegistry has no registered handlers?The Observation becomes a no-op — start/stop and observe do nothing and emit no metric, span, or log. That's also what ObservationRegistry.NOOP gives you deliberately.
- Why prefer observe(Runnable) over manual start()/stop()?observe wraps the call in try/catch/finally: it records exceptions via error(...) and guarantees stop() runs, so you never leak an unstopped Observation or lose error signals on the exception path.
saying these in an interview costs you the question
- Thinking createNotStarted already starts recording (it does not).
- Putting high-cardinality data like an order ID into the Observation name.
- Assuming an Observation always produces output even without any configured handler.
- Confusing ObservationRegistry with MeterRegistry — they are different objects.