Monitoring URL Request Frequency and Response Times with Spring Boot Actuator

1. Dependency Configuration

Include the Actuator depandency in your pom.xml:

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

2. Request Data Storage Implementation

Use a ConcurrentHashMap to store request metrics per URI (timestamp + response time):

@Component
public class RequestMetricsCollector {
    private final ConcurrentHashMap<String, Queue<RequestMetric>> metricsMap = new ConcurrentHashMap<>();

    private static class RequestMetric {
        long requestTime;
        long processingTime;

        RequestMetric(long requestTime, long processingTime) {
            this.requestTime = requestTime;
            this.processingTime = processingTime;
        }
    }

    public synchronized void logRequest(String uri, long processingTime) {
        long currentTime = System.currentTimeMillis();
        metricsMap.compute(uri, (key, existingQueue) -> {
            if (existingQueue == null) {
                existingQueue = new ConcurrentLinkedQueue<>();
            }
            existingQueue.add(new RequestMetric(currentTime, processingTime));
            
            if (existingQueue.size() > 100000) {
                existingQueue.poll();
            }
            removeOldEntries(existingQueue, currentTime);
            return existingQueue;
        });
    }

    private void removeOldEntries(Queue<RequestMetric> queue, long currentTime) {
        while (!queue.isEmpty() && (currentTime - queue.peek().requestTime > 86400000)) {
            queue.poll();
        }
    }

    public Map<String, Map<String, Object>> computeMetrics() {
        long timeThreshold = System.currentTimeMillis() - 86400000;
        Map<String, Map<String, Object>> metricsResult = new HashMap<>();

        metricsMap.forEach((uri, metrics) -> {
            List<RequestMetric> recentMetrics = metrics.stream()
                    .filter(m -> m.requestTime >= timeThreshold)
                    .collect(Collectors.toList());

            if (!recentMetrics.isEmpty()) {
                int requestCount = recentMetrics.size();
                long totalTime = recentMetrics.stream().mapToLong(m -> m.processingTime).sum();
                long peakTime = recentMetrics.stream().mapToLong(m -> m.processingTime).max().orElse(0);

                Map<String, Object> uriStats = new HashMap<>();
                uriStats.put("requestCount", requestCount);
                uriStats.put("averageTime", totalTime / requestCount);
                uriStats.put("peakTime", peakTime);
                metricsResult.put(uri, uriStats);
            }
        });

        return metricsResult.entrySet().stream()
                .sorted((entry1, entry2) -> 
                    Integer.compare((Integer) entry2.getValue().get("requestCount"), 
                                   (Integer) entry1.getValue().get("requestCount")))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (e1, e2) -> e1,
                        LinkedHashMap::new
                ));
    }
}

3. AOP Aspect for Request Monitoring

@Aspect
@Component
public class RequestMonitoringAspect {
    @Autowired
    private RequestMetricsCollector metricsCollector;

    @Around("execution(* com.example..*Controller.*(..))")
    public Object monitorRequest(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest httpRequest = ((ServletRequestAttributes) 
                RequestContextHolder.currentRequestAttributes()).getRequest();
        String requestUri = httpRequest.getRequestURI();
        long beginTime = System.currentTimeMillis();
        
        try {
            return joinPoint.proceed();
        } finally {
            long elapsedTime = System.currentTimeMillis() - beginTime;
            metricsCollector.logRequest(requestUri, elapsedTime);
        }
    }
}

4. Custom Actuator Endpoint

@Endpoint(id = "request-metrics")
@Component
public class RequestMetricsEndpoint {
    @Autowired
    private RequestMetricsCollector metricsCollector;

    @ReadOperation
    public Map<String, Object> retrieveMetrics() {
        return new LinkedHashMap<String, Object>() {{
            put("timestamp", System.currentTimeMillis());
            put("metrics", metricsCollector.computeMetrics());
        }};
    }
}

Execute requests to your application endpoints (e.g., /api/users, /api/orders) to collect metrics automatically.

Accessing Statistics

Retrieve 24-hour metrics via custom endpoint:

GET /actuator/request-metrics

{
  "timestamp": 1717250000000,
  "metrics": {
    "/api/users": {
      "requestCount": 1500,
      "averageTime": 45,
      "peakTime": 1200
    },
    "/api/orders": {
      "requestCount": 1200,
      "averageTime": 80,
      "peakTime": 2500
    }
  }
}

Scheduled Data Cleanup

Perform hourly cleanup of expired data:

@Scheduled(fixedRate = 3600000)
public void performCleanup() {
    long currentMillis = System.currentTimeMillis();
    metricsMap.forEach((uri, queue) -> 
        queue.removeIf(metric -> (currentMillis - metric.requestTime) > 86400000)
    );
}

Security Configuration

Restrict endpoint access using Spring Security:

@Configuration
public class EndpointSecurityConfig {
    @Bean
    SecurityFilterChain securityConfig(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> 
            auth.requestMatchers("/actuator/request-metrics").hasRole("ADMIN")
        );
        return http.build();
    }
}

Tags: Spring Boot Actuator aop ConcurrentHashMap Spring Security monitoring

Posted on Tue, 18 Aug 2026 16:50:14 +0000 by khenriks