Learnitweb

Monitoring Method Execution Time in Spring Boot with @Timed and AOP

Why @Timed?

Injecting metric logic directly into controllers (or services) introduces code scattering — business logic mixed with monitoring logic.

To solve this:

  • We use @Timed from Micrometer’s annotation module
  • Let Spring AOP handle method interception and metric collection

This brings us cleaner, more maintainable code.

Step 1: Add Dependencies

First, add the necessary dependencies to your pom.xml.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-core</artifactId>
</dependency>

<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-annotation</artifactId>
</dependency>

Make sure you’re using Spring Boot 2.7.x or higher.

Step 2: Enable the Timed Aspect

You must manually register a TimedAspect bean so Spring can intercept methods annotated with @Timed.

@Configuration
public class MonitoringConfig {

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

Step 3: Use @Timed in Your Controller

Let’s annotate a controller method.

@RestController
@RequestMapping("/quiz")
public class QuizController {

    @Timed(value = "quiz.timer", extraTags = {"source", "quizSummary"})
    @GetMapping("/summary")
    public String getQuizSummary() {
        // Simulate fetching data
        return "Quiz Summary";
    }

    @Timed(value = "quiz.timer", extraTags = {"source", "levelSummary"})
    @GetMapping("/level/{id}")
    public String getLevelById(@PathVariable String id) {
        return "Level ID: " + id;
    }
}

What happens under the hood?

  • Spring uses class-based proxies (CGLIB) because QuizController doesn’t implement an interface.
  • These proxies intercept the method call.
  • The TimedAspect wraps it in an Around Advice that:
    • Starts a timer before the method executes
    • Stops the timer after it completes
    • Sends the metric data to Micrometer

So when you hit /quiz/summary, Spring:

  1. Routes the request to a proxy
  2. The proxy starts the timer
  3. Your actual method runs
  4. The proxy stops the timer and publishes the metric

Step 4: Call the Endpoints

You can now use tools like Postman or curl to hit your endpoints:

GET http://localhost:8080/quiz/summary
GET http://localhost:8080/quiz/level/1

Call each a few times to generate metrics.

Step 5: Check the Metrics

Now navigate to:

http://localhost:8080/actuator/metrics/quiz.timer

You’ll see entries like:

{
  "name": "quiz.timer",
  "measurements": [...],
  "availableTags": [
    {
      "tag": "source",
      "values": ["quizSummary", "levelSummary"]
    }
  ]
}

You can drill down:

http://localhost:8080/actuator/metrics/quiz.timer?tag=source:quizSummary

or

http://localhost:8080/actuator/metrics/quiz.timer?tag=source:levelSummary

Understanding the code

@Timed(...)

This is the heart of the example — the @Timed annotation from Micrometer (used internally by Spring Boot Actuator) is used to automatically record the duration of method execution.

Explanation of parameters:

  • value = "quiz.timer"
    This sets the name of the metric. The timer will be accessible under /actuator/metrics/quiz.timer.
  • extraTags = {"source", "quizSummary"}
    These are key-value pairs that act as tags (or labels), allowing you to distinguish between different usages of the same timer metric. For example:
    • One timer is tagged as source=quizSummary
    • Another is tagged as source=levelSummary

This allows for dimension-based querying, which is more flexible than hierarchical metrics.

Viewing the Metrics

If your application has the Spring Actuator endpoint enabled (in application.properties):

management.endpoints.web.exposure.include=*
management.endpoint.metrics.enabled=true

Then you can access:

  • All available metrics: http://localhost:8080/actuator/metrics
  • Specific metric: http://localhost:8080/actuator/metrics/quiz.timer

You can query with tags, for example:

http://localhost:8080/actuator/metrics/quiz.timer?tag=source:quizSummary
http://localhost:8080/actuator/metrics/quiz.timer?tag=source:levelSummary

This allows you to analyze how long different methods took to execute.