Hello! Welcome to the final module of your course on building a production-grade rate limiter.
In our last few lessons, you've built a powerful load testing utility that not only measures performance metrics like latency percentiles but also verifies the functional correctness of your rate limiter by asserting the counts of successful and denied requests. Your system is now robust and reliable under test conditions.
However, in a live production environment, we need more than post-mortem analysis. We need observability—the ability to ask questions about our system's behavior and get answers in real-time. This is crucial for debugging issues, understanding usage patterns, and ensuring the system is healthy.
In this lesson, you will take the first step toward making your rate limiter observable. You will learn to integrate Micrometer with Spring Boot for custom metrics collection, turning your rate limiter's internal actions into quantifiable data that can be monitored.
1. The Observability Toolkit: Actuator and Micrometer
To instrument our Spring Boot application, we'll use two key components that work together: Spring Boot Actuator and Micrometer.
-
Spring Boot Actuator: This is a Spring Boot sub-project that adds production-ready features to your application. It exposes a set of HTTP endpoints (e.g.,
/health,/info,/metrics) that provide insight into the application's runtime state. -
Micrometer: This is a powerful instrumentation library that acts as a metrics facade, much like SLF4J is a facade for logging. It provides a simple, vendor-neutral API for collecting metrics in your code. Spring Boot Actuator uses Micrometer under the hood to gather and expose metrics.
The following diagram illustrates this architecture. Your application code uses the Micrometer API to define and record metrics. Micrometer organizes these into a MeterRegistry, and Spring Boot Actuator then exposes them through an endpoint (like /actuator/prometheus) for a monitoring system to collect.

To start, let's explore what Actuator provides out of the box and how to configure it.
Monitoring and Metrics for Spring | with Prometheus - Grafana - Actuator
This video from kanezi gives an excellent overview of Spring Boot Actuator. It explains its purpose and demonstrates how to enable and explore its various endpoints.
Watch the following segments: 00:00:00 - 00:01:25: Understand the motivation behind Actuator. 00:01:25 - 00:05:27: See how to enable and expose Actuator endpoints through configuration. Pay attention to the distinction between enabled and exposed properties. 00:09:44 - 00:10:41: This part introduces Micrometer as the metrics facade powering the /metrics endpoint, connecting the two concepts.
2. Setting Up Your Project for Metrics
Let's get your hands dirty. The first step is to add the necessary dependencies and configure your application to expose metrics in a format that a monitoring system like Prometheus can understand.
Add Dependencies to build.gradle
You need two dependencies:
spring-boot-starter-actuator: Includes all the core Actuator functionality.micrometer-registry-prometheus: Tells Micrometer to format metrics for Prometheus.
Add the following to the dependencies block in your build.gradle file:
dependencies {
// ... your existing dependencies
// For Observability: Actuator and Micrometer/Prometheus
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
}
After adding these, make sure to reload your Gradle project in your IDE.
Configure application.yml
Next, you need to tell Actuator to expose the prometheus endpoint. By default, for security reasons, most endpoints are not exposed over HTTP.
Add the following configuration to your src/main/resources/application.yml file:
management:
endpoints:
web:
exposure:
include: "prometheus,health,info"
metrics:
export:
prometheus:
enabled: true
tags: # These tags are applied to all metrics
application: ${spring.application.name}
This configuration does two things:
- It exposes the
/actuator/prometheus,/actuator/health, and/actuator/infoendpoints. - It adds a common
applicationtag to every metric reported by your service, which is a best practice for organizing metrics.
3. Understanding Micrometer's Meter Types
Before we write code, it's essential to understand the "instruments" Micrometer provides. There are four core types of meters.
This video from Pavan Elthepu clearly explains the four main types of Prometheus/Micrometer metrics. Understanding their different use cases is key to choosing the right instrument for the job.
Watch from the beginning to 05:12. Focus on the explanation and examples for Counter, Gauge, Histogram, and Summary. Think about which one would be most suitable for counting allowed vs. denied requests.
As you just saw, the primary meter types are:
| Meter Type | Use Case | Example for Our Rate Limiter |
|---|---|---|
Counter |
For values that only increase or reset to zero. | Total requests processed, total requests denied. |
Gauge |
For values that can go up and down. | Number of unique keys currently being tracked. |
Timer |
For measuring the duration of short-lived events. | Time taken to check a request against a rule. |
DistributionSummary |
For tracking the distribution of non-time values. | N/A (Could be used for request payload size). |
For our immediate goal of tracking allowed and denied requests, the Counter is the perfect tool.
4. Instrumenting the Rate Limiter
Now, let's apply this knowledge to instrument your RateLimiterInterceptor. Our goal is to count every request that passes through the interceptor and label it as either allowed or denied.
The central component for creating meters in Micrometer is the MeterRegistry. Spring Boot automatically configures one for you, so you can simply inject it into your interceptor.
-
Inject
MeterRegistryintoRateLimiterInterceptorModify your
RateLimiterInterceptorto acceptMeterRegistryin its constructor.import io.micrometer.core.instrument.MeterRegistry; // Add this import import org.springframework.stereotype.Component; @Component public class RateLimiterInterceptor implements HandlerInterceptor { // ... other fields like rateLimiterFactory, keyResolver, etc. private final MeterRegistry meterRegistry; public RateLimiterInterceptor(RateLimiterFactory rateLimiterFactory, RateLimitKeyResolver keyResolver, MeterRegistry meterRegistry) { // Add meterRegistry this.rateLimiterFactory = rateLimiterFactory; this.keyResolver = keyResolver; this.meterRegistry = meterRegistry; // Store it } // ... } -
Increment a Tagged Counter
Now, in the
preHandlemethod, after your rate limiting logic has made a decision, you'll use themeterRegistryto increment a counter. We will use a single counter namedratelimiter.decisions.totaland use a tag to create two distinct time series: one for allowed requests and one for denied requests.This is the most powerful feature of modern metrics systems. Instead of creating two separate metrics (
allowed_requestsanddenied_requests), we use one metric with adecisiondimension.Update your
preHandlemethod:@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // ... your existing logic to resolve key and get the rule ... String key = keyResolver.resolve(request); // Assume you have a way to get the rule, for now, we'll focus on the decision RateLimiter rateLimiter = rateLimiterFactory.getRateLimiter(/* rule parameters */); boolean isAllowed = rateLimiter.isAllowed(key); // --- METRICS INSTRUMENTATION --- String decision = isAllowed ? "allowed" : "denied"; meterRegistry.counter("ratelimiter.decisions.total", "decision", decision).increment(); // ------------------------------- if (isAllowed) { return true; } else { response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); return false; } }The line
meterRegistry.counter("ratelimiter.decisions.total", "decision", decision).increment();is the idiomatic way to handle this in Micrometer. It efficiently finds or creates aCounterwith the specified name and tags and increments it.
For a deeper dive into practical Micrometer examples, the following resource is very helpful.
How to Create Custom Metrics with Micrometer - OneUptime
This article from OneUptime, "How to Create Custom Metrics with Micrometer", provides clear, practical code examples for implementing various meter types.
Read the sections "Working with MeterRegistry" and "Counter - Tracking Cumulative Values", including "Counter with Tags". This reinforces the pattern we just used: defining a counter with dynamic tags to add context.
5. Verifying Your New Metrics
With the code in place, let's verify that it's working.
- Run your Spring Boot application.
- Send some requests to your rate-limited endpoint. Make sure you send enough to trigger both allowed and denied responses. For example, if your limit is 10 requests per minute, send 15 requests quickly.
- Open your browser and navigate to
http://localhost:8080/actuator/prometheus.
You will see a large text output containing all the metrics your application is exporting. Scroll through or use your browser's find function (Ctrl/Cmd + F) to search for ratelimiter_decisions_total. You should see something like this:
# HELP ratelimiter_decisions_total_total
# TYPE ratelimiter_decisions_total_total counter
ratelimiter_decisions_total_total{application="rate-limiter",decision="allowed",} 10.0
ratelimiter_decisions_total_total{application="rate-limiter",decision="denied",} 5.0
(Note: Prometheus clients often append _total to counter names.)
Seeing these lines confirms that your custom metric is successfully being collected and exposed. You are now capturing the fundamental behavior of your rate limiter!
Conclusion
In this lesson, you've successfully laid the foundation for an observable rate limiter. You integrated Spring Boot Actuator and Micrometer, configured your application to expose metrics for Prometheus, and instrumented your core logic to produce valuable, custom metrics.
Key Takeaways:
- Observability is critical for operating services in production.
- Spring Boot Actuator exposes operational endpoints, while Micrometer provides the API to create the metrics.
- Counters are the ideal meter type for tracking events that only accumulate, like request counts.
- Tags (or labels/dimensions) are the key to creating rich, filterable metrics, allowing you to slice and dice your data (e.g., by
decision). - You can verify your custom metrics by inspecting the
/actuator/prometheusendpoint.
Next Steps
You've successfully created a custom metric. The next step is to make it even more powerful. Currently, you can see the total number of allowed and denied requests. But what if you have multiple rate-limiting rules? How would you know which rule is responsible for the denials?
In the next lesson, you will instrument the rate limiter to track request counts and allowed/denied decisions per rule. This will involve adding more tags to your counter, giving you the granular insight needed to manage a multi-tenant, production-grade rate-limiting service.