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:
- 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 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
- 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:
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.