Why @Timed?
Injecting metric logic directly into controllers (or services) introduces code scattering — business logic mixed with monitoring logic.
To solve this:
- We use
@Timedfrom 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
QuizControllerdoesn’t implement an interface. - These proxies intercept the method call.
- The
TimedAspectwraps 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:
- Routes the request to a proxy
- The proxy starts the timer
- Your actual method runs
- 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 https://localhost:8080/quiz/summary GET https://localhost:8080/quiz/level/1
Call each a few times to generate metrics.
Step 5: Check the Metrics
Now navigate to:
https://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:
https://localhost:8080/actuator/metrics/quiz.timer?tag=source:quizSummary
or
https://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
- One timer is tagged as
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:
https://localhost:8080/actuator/metrics - Specific metric:
https://localhost:8080/actuator/metrics/quiz.timer
You can query with tags, for example:
https://localhost:8080/actuator/metrics/quiz.timer?tag=source:quizSummary https://localhost:8080/actuator/metrics/quiz.timer?tag=source:levelSummary
This allows you to analyze how long different methods took to execute.
