skip to content

You add @Timed to a service method but no metric shows up under /actuator/metrics. Why, and how do you fix it?

level: middleimportance: must knowfreq 48%

answer

  1. annotation = metadata only, aspect does the work
  2. need spring-boot-starter-aop (aspectjweaver)
  3. Boot 2.5+ MetricsAspectsAutoConfiguration auto-registers
  4. older Boot -> register TimedAspect/CountedAspect @Bean
  5. controller @Timed uses a different (web) path

basics

~20 s

The @Timed annotation does nothing by itself — a TimedAspect bean has to intercept the method. If Spring AOP or that aspect bean is missing, nothing is recorded. Add spring-boot-starter-aop and make sure TimedAspect (and CountedAspect) are registered.

solid answer

~40 s

@Timed/@Counted are inert metadata; the actual instrumentation is done by the `TimedAspect`/`CountedAspect` AOP aspects, which must exist as beans and require Spring AOP (AspectJ/aspectjweaver on the classpath). If neither is present, the annotation is silently ignored — no error, no metric. In Spring Boot 2.5+, adding `spring-boot-starter-aop` triggers `MetricsAspectsAutoConfiguration`, which registers both aspects automatically. On older Boot versions (or if you excluded AOP) you must register them yourself: `@Bean TimedAspect timedAspect(MeterRegistry r) { return new TimedAspect(r); }` and the same for `CountedAspect`. Also confirm the bean is Spring-managed and the call goes through the proxy — a self-invocation won't be intercepted. Finally, a Timer only appears after it has recorded at least once, so the method must have actually been called.

code

java · 15 lines
java
// Manual registration for Boot versions before 2.5,
// or when you want explicit control.
@Configuration
public class MicrometerAspectsConfig {

    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }

    @Bean
    public CountedAspect countedAspect(MeterRegistry registry) {
        return new CountedAspect(registry);
    }
}

go deeper

for a junior

Know the annotation alone isn't enough; something must process it.

for a middle

Name TimedAspect/CountedAspect, the AOP dependency, and Boot 2.5+ auto-registration; know how to register manually.

for a senior

Distinguish the web-metrics path from the aspect path and debug silent no-ops systematically.

for a principal

Decide project-wide whether to rely on auto-config vs explicit beans, and standardize so teams don't hit silent failures.

## The root cause: annotations don't self-execute `@Timed` and `@Counted` are just annotations. They carry no behavior. The behavior lives in two Spring AOP aspects: - `io.micrometer.core.aspect.TimedAspect` - `io.micrometer.core.aspect.CountedAspect` Each is an `@Aspect` with an `@Around` advice that matches annotated methods (and annotated classes), wraps the invocation in a `Timer`/`Counter`, and records the result. If these aspects are **not registered as beans**, or Spring AOP isn't active, the annotation is **silently ignored** — you get no exception and no metric. This silence is exactly why the symptom is confusing. ## Requirements to make them work 1. **Spring AOP must be on the classpath.** Add `spring-boot-starter-aop` (which pulls in `aspectjweaver`). Without it, `@Aspect` beans can't weave. 2. **The aspect beans must exist.** - **Spring Boot 2.5+:** `MetricsAspectsAutoConfiguration` (part of Actuator autoconfig) registers `TimedAspect` and `CountedAspect` **automatically** when AOP is present. So in a typical modern app, `starter-aop` + `actuator` is enough. - **Older Boot, or if you disabled it:** register manually. 3. **A `MeterRegistry` must be present** (Actuator provides one, e.g. the Prometheus registry). 4. **The target must be a Spring bean invoked through its proxy** — see the self-invocation gotcha. 5. **The method must actually run at least once.** Micrometer Timers are lazily materialized on first record; a never-called method shows no meter. ## Manual registration (older Boot / explicit control) ```java @Configuration public class MetricsAspectConfig { @Bean public TimedAspect timedAspect(MeterRegistry registry) { return new TimedAspect(registry); } @Bean public CountedAspect countedAspect(MeterRegistry registry) { return new CountedAspect(registry); } } ``` ## Distinguish from @Timed on Spring MVC controllers Note that `@Timed` on **Spring MVC/WebFlux controller** handler methods is honored by a **different** mechanism — the web metrics instrumentation (`http.server.requests` via a filter/observation), not `TimedAspect`. That path works even without the AOP aspect. The aspect is what handles `@Timed` on **arbitrary beans** (services, repositories). Candidates often conflate the two: controller `@Timed` working doesn't prove the aspect is wired. ## Debug checklist - Is `spring-boot-starter-aop` a dependency? - Is there a `TimedAspect`/`CountedAspect` bean? (`/actuator/beans` or context introspection) - Is `spring.aop.proxy-target-class` / AOP enabled (auto by default)? - Was the method called via another bean (proxy), not `this.method()`? - Did the method execute at least once, and are you looking at the right metric name (default `method.timed`)?

  • In a fresh Spring Boot 3 app with actuator and spring-boot-starter-aop, do you still need to register TimedAspect manually?
    No. MetricsAspectsAutoConfiguration registers TimedAspect and CountedAspect automatically once AOP is on the classpath. Manual registration is only needed on older Boot or if you exclude/override that autoconfiguration.
  • @Timed on your @RestController method works, but on a @Service it doesn't. What's different?
    Controller handler @Timed is honored by the web metrics instrumentation (http.server.requests), independent of the AOP aspect. Service @Timed needs TimedAspect + AOP. So the service path is missing the aspect/AOP, while the controller path uses a separate mechanism.

saying these in an interview costs you the question

  • Claiming @Timed always works with zero extra dependencies (ignores the AOP/aspect requirement).
  • Assuming controller @Timed and service @Timed use the same machinery.
  • Expecting an exception when the aspect is missing — it fails silently.

context