Backend Development 19 min read

Implementing Unified User Login, Exception Handling, and Data Response in Spring Boot with AOP and Interceptors

This article demonstrates how to use Spring Boot AOP and HandlerInterceptor to create a unified user login permission check, standardized exception handling with @ControllerAdvice, and consistent JSON response formatting, providing step‑by‑step code examples and configuration details for building robust backend services.

Architect's Guide
Architect's Guide
Architect's Guide
Implementing Unified User Login, Exception Handling, and Data Response in Spring Boot with AOP and Interceptors

The article introduces a Spring Boot module that unifies three core functions: user login permission verification, uniform data format return, and centralized exception handling, using AOP and interceptor techniques.

1. User login permission validation

Initially, each controller method contains repetitive login checks, which leads to maintenance overhead. The original implementation looks like this:

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/m1")
    public Object method(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            return true; // logged in
        } else {
            return false; // not logged in
        }
    }
    // other methods ...
}

Because every method repeats this logic, a common AOP solution is considered.

1.2 Spring AOP approach

Using a @Before or @Around advice to perform the check, the skeleton code is:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class UserAspect {
    @Pointcut("execution(* com.example.demo.controller..*.*(..))")
    public void pointcut() {}
    @Before("pointcut()")
    public void doBefore() {}
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Around method start");
        Object obj = joinPoint.proceed();
        System.out.println("Around method end");
        return obj;
    }
}

Two major problems appear: the HttpSession object cannot be accessed inside the advice, and it is difficult to exclude specific endpoints such as login and registration from interception.

1.3 Spring Interceptor solution

Spring provides the HandlerInterceptor interface, which allows pre‑handling of requests before the controller is invoked.

Custom interceptor implementation:

package com.example.demo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/** Login interceptor */
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            return true; // allow
        }
        log.error("Current user has no access permission");
        response.setStatus(401);
        return false; // block
    }
}

Registering the interceptor in a WebMvcConfigurer implementation:

package com.example.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // intercept all
                .excludePathPatterns("/user/login", "/user/reg"); // exclude login & registration
    }
}

The addPathPatterns method defines which URLs are intercepted, while excludePathPatterns lists the URLs that bypass the interceptor.

1.4 Interceptor execution principle

During request processing, DispatcherServlet calls applyPreHandle , which iterates over all registered HandlerInterceptor instances and invokes their preHandle methods. If any interceptor returns false , the request processing stops.

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ...
    if (!mappedHandler.applyPreHandle(request, response)) {
        return; // interceptor blocked the request
    }
    // controller execution
    // ...
}

The source of applyPreHandle shows the loop over the interceptor list and the call to preHandle :

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = (HandlerInterceptor) this.interceptorList.get(i);
        if (!interceptor.preHandle(request, response, this.handler)) {
            triggerAfterCompletion(request, response, null);
            return false;
        }
    }
    return true;
}

2. Unified exception handling

Using @ControllerAdvice together with @ExceptionHandler , the application can catch all exceptions and return a uniform JSON structure:

package com.example.demo.config;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
@ControllerAdvice
public class ErrorAdvice {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public HashMap
exceptionAdvice(Exception e) {
        HashMap
result = new HashMap<>();
        result.put("code", "-1");
        result.put("msg", e.getMessage());
        return result;
    }
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public HashMap
arithmeticAdvice(ArithmeticException e) {
        HashMap
result = new HashMap<>();
        result.put("code", "-2");
        result.put("msg", e.getMessage());
        return result;
    }
}

Additional examples show handling of NullPointerException and custom messages, illustrating how the most specific handler is chosen.

3. Unified data response format

To ensure every API returns a consistent JSON payload, a ResponseBodyAdvice implementation wraps the original body into a standard structure:

package com.example.demo.common;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice
{
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof HashMap) {
            return body; // already wrapped
        }
        if (body instanceof String) {
            // special handling for plain strings
            return new com.fasterxml.jackson.databind.ObjectMapper()
                    .writeValueAsString(AjaxResult.success(body));
        }
        return AjaxResult.success(body);
    }
}

The utility class AjaxResult provides static methods success and fail to build the standard map with fields code , msg , and data :

package com.example.demo.common;
import java.util.HashMap;
public class AjaxResult {
    public static HashMap
success(Object data) {
        HashMap
result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "");
        result.put("data", data);
        return result;
    }
    public static HashMap
fail(int code, String msg) {
        HashMap
result = new HashMap<>();
        result.put("code", code);
        result.put("msg", msg);
        result.put("data", "");
        return result;
    }
}

With these components, the application achieves a clean separation of concerns: authentication logic lives in the interceptor, error handling is centralized via @ControllerAdvice , and every response conforms to a predictable JSON schema, simplifying front‑end integration and future maintenance.

aopException HandlingSpring BootInterceptorUnified Response
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.