7.4 Interceptors and API Logging Automation
In Spring, the HandlerInterceptor allows you to perform specific logic commonly by intercepting the request before and after the DispatcherServlet calls the Controller.
It is very frequently used for authentication (Authentication check), generic request/response format manipulation, and importantly, for writing API call logs regarding requests sent by clients.
HandlerInterceptor Interface
To implement an interceptor, inherit (or implement) HandlerInterceptor. It has three main methods.
preHandle(): Executed before entering the controller. (Most frequently used). If it returnsfalse, the method does not proceed to the next step (the controller).postHandle(): Executed after the controller execution, before view rendering. (Not very frequently used in REST APIs).afterCompletion(): Executed after all view rendering and response transmission tasks are completely finished. It is called whether it finished successfully or an exception occurred, so it is used for time measurement or final resource cleanup.
API Call Logging Interceptor Implementation Example
This is an interceptor that measures and logs the time a request came in, the URI, and the time taken after the request processing is finished.
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Slf4j
@Component
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// Temporarily store the arrival time in the request object's attribute.
request.setAttribute("startTime", System.currentTimeMillis());
log.info("[API Request] {} {}", request.getMethod(), request.getRequestURI());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
long startTime = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
log.info("[API Response] {} {} - Processing time: {}ms, Response code: {}",
request.getMethod(), request.getRequestURI(), duration, response.getStatus());
if (ex != null) {
log.error("[API Error] Exception occurred: ", ex);
}
}
}
Registering the Interceptor
To make the created interceptor work, you need to register it in the Spring container's WebMvc configuration. Implement WebMvcConfigurer and override the addInterceptors method.
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final LoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/api/**") // The path patterns we'll apply it to
.excludePathPatterns("/api/exclude"); // Patterns to exclude from log monitoring
}
}
By setting it up like this, even if you add hundreds of controllers later, all request and response processing under /api/ will be systematically logged without the developer having to manually insert System.out.println statements. These logs become invaluable resources for analyzing the causes of failures or monitoring the performance of slow APIs.
Pro Tips — Integrating Global Error Tracking Channels (Slack, Sentry)
In live production services, merely printing an error to the internal console guarantees no one will notice it. Fatal exceptions (e.g., Payment Failures, Database Environment Errors) must be aggressively architected to push instant alerts directly into the dev team's Slack channels or Sentry instances.
Attaching Notification Hooks conceptually to your ExceptionHandler
Inject your external webhook logic specifically into the fallback handler (Exception.class) resting inside your global @RestControllerAdvice.
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {
private final SlackNotifier slackNotifier; // Slack API Webhook Bean
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAllUncaughtException(Exception ex, HttpServletRequest request) {
log.error("Unknown Unhandled Error Caught: {}", ex.getMessage(), ex);
// Trace error stack and fire async alert webhook
String errorContext = String.format("URL: %s | Message: %s", request.getRequestURI(), ex.getMessage());
slackNotifier.sendAsyncAlert(errorContext);
return ResponseEntity.internalServerError().body(new ErrorResponse("SERVER_ERROR", "An internal server error occurred. The team has been aggressively notified."));
}
}
If you configure an alert payload trigger indiscriminately for every native NullPointerException, your system will maliciously DDoD your Slack API threshold limits with spam bombs. It is absolutely paramount to intentionally suppress pre-defined, anticipated CustomBusinessExceptions silently, while reserving webhook pushes solely for entirely unhandled top-level Exception instances or custom exceptions flagged inherently with Extreme Severity levels (Level: FATAL).