What is a Micrometer Counter and when should you use one instead of a Gauge?
answer
- Counter = only goes up
- increment() / increment(n>=0)
- backends compute rate() from the total
- Gauge = up-and-down instantaneous
- resets to 0 on restart
basics
~20 sA Counter records a value that only ever goes up (a total, like requests served). Use a Counter for cumulative counts you increment; use a Gauge for a value that can go up and down, like current queue size.
solid answer
~40 sA Micrometer Counter is a meter for a single monotonically increasing value — you call increment() as events happen, and it reports the running total. It's ideal for cumulative event counts: HTTP requests served, orders placed, errors thrown. You never set a Counter directly; you only add to it (increment() adds 1, increment(n) adds n where n must be non-negative). Downstream systems (Prometheus, etc.) typically compute a rate from the ever-growing total. Use a Gauge instead when the value can both rise and fall and represents an instantaneous measurement — active connections, queue depth, cache size, memory used. A quick test: 'total number of things that happened' -> Counter; 'current value right now' -> Gauge.
code
java · 21 linesimport io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final Counter ordersPlaced;
public OrderService(MeterRegistry registry) {
this.ordersPlaced = Counter.builder("orders.placed")
.description("Total orders placed since startup")
.tag("channel", "web")
.register(registry);
}
public void placeOrder(Order order) {
// ... business logic ...
ordersPlaced.increment(); // monotonic: only ever +1
}
}go deeper
Must know Counter = only goes up, use increment(); Gauge = current value that can go down.
Should also know the rate() consumption model, non-negative increment contract, and tag cardinality.
Explains FunctionCounter, restart-reset handling by backends, and naming/tagging conventions across registries.
Frames Counter vs Gauge as a data-model choice affecting aggregation across instances and lossless-between-scrapes semantics.
**Micrometer** is the metrics facade Spring Boot uses under the hood (via `spring-boot-starter-actuator` plus a registry like `micrometer-registry-prometheus`). It defines a small set of *meter* types; the two most fundamental are **Counter** and **Gauge**. **Counter** represents a single, **monotonically increasing** number — it only ever goes up (or resets to zero when the application restarts). You create one from a `MeterRegistry` and record events by calling `increment()`: - `counter.increment()` adds 1. - `counter.increment(amount)` adds `amount`. The amount must be **non-negative** — Micrometer's contract is that a Counter never decreases; passing a negative value is a misuse. Because the stored value is a cumulative total, monitoring systems rarely graph the raw number. Instead they compute a **rate** — e.g. Prometheus `rate(http_requests_total[5m])` gives requests per second. The Counter's job is just to keep an accurate running total; the *derivative* is what humans look at. **When to use a Counter:** anything phrased as 'how many X have happened since the app started' — requests handled, messages consumed, retries attempted, exceptions caught, bytes written. The event is discrete and additive. **Gauge**, by contrast, holds an **instantaneous** value that can go **up or down**: current thread-pool active count, current queue depth, current cache entry count, free disk space. A Gauge is *sampled* — Micrometer reads the current value when the registry scrapes, rather than you pushing increments. **Why not just use a Gauge for everything?** A Gauge you `set()` manually loses information between samples (spikes that happen between scrapes vanish), and it can't be safely summed across instances. A Counter captures every increment, so no events are lost between scrapes and rates are exact. **Creating a Counter — two common styles:** 1. Fluent builder: `Counter.builder("orders.placed").tag("type", "digital").register(registry)`. 2. Convenience: `registry.counter("orders.placed", "type", "digital")`. **Naming & tags:** use dot-separated lowercase names (`orders.placed`) — Micrometer translates to each backend's convention (Prometheus turns it into `orders_placed_total`). Attach **tags** (dimensions) for slicing, but keep tag *values* **bounded** (low cardinality) — never user IDs or raw URLs, or you create an unbounded number of time series. **Edge cases / gotchas:** - A Counter resets to 0 on restart; backends handle this via rate functions that detect counter resets — don't try to persist it yourself. - Don't call `increment(-1)` to 'undo' — that violates the monotonic contract. If you need up-and-down, you want a Gauge. - `FunctionCounter` is a variant that reads a monotonic total from an existing object (e.g. a library's internal counter) instead of you calling increment.
- Why do monitoring systems graph a rate rather than the raw Counter value?The raw value is an ever-growing cumulative total, which isn't meaningful on its own. The rate (derivative over a window) shows current throughput — e.g. requests/second — and rate functions also handle the Counter resetting to zero on restart.
- What happens if you call increment(-5) on a Counter?It violates the Counter's monotonic contract. Micrometer's Counter is meant only to increase; negative amounts are a misuse. If you need a value that decreases, use a Gauge instead.
saying these in an interview costs you the question
- Thinking a Counter can be set to an arbitrary value
- Using increment(-1) to decrement a Counter
- Graphing the raw cumulative total instead of its rate