1. What is @ControllerAdvice?
@ControllerAdvice is a specialization of the @Component annotation that allows you to write global interceptors across multiple @RequestMapping controllers. It acts as a centralized interceptor for exceptions, data binding (@InitBinder), and model attributes (@ModelAttribute).
- When combined with
@ResponseBody, it becomes@RestControllerAdvice, automatically serializing exception response bodies into JSON/XML.
2. @ExceptionHandler and Spring AOP Connection
The @ExceptionHandler annotation marks a method as the designated handler for specific exception types thrown inside controllers.
How it Connects to Spring AOP
While
@ControllerAdviceclasses are instantiated and managed by the Spring IoC container, exception handling is not strictly processed via standard Spring AOP aspect interceptors (MethodInterceptor). Instead, Spring MVC uses a dedicated dispatcher strategy calledExceptionHandlerExceptionResolver.When a controller method throws an exception, Spring’s
DispatcherServletcatches it and delegates it to the resolver. The resolver scans registered@ControllerAdvicebeans for a method matching the thrown exception type using reflection, executes it, and returns the resultingResponseEntity.
3. Proxies, Database Connections, and @Transactional
Why do we need a proxy for database operations?
JDBC and database operations require boilerplate lifecycle management: opening a physical database connection, starting a transaction, committing or rolling back on error, and closing the connection.
Instead of writing this boilerplate in every service method, Spring uses Dynamic Proxies (JDK dynamic proxies or CGLIB proxies).
- The proxy wraps your actual service bean.
- When you call a service method externally, the proxy intercepts the call, interacts with the
TransactionManagerto open a connection/transaction, invokes your actual business logic, and handles commit/rollback/cleanup afterwards.
graph TD Client -->|1 - Calls method externally| Proxy[Spring CGLIB/JDK Proxy] Proxy -->|2 - Opens DB Connection & Transaction| TM[Transaction Manager] Proxy -->|3 - Invokes actual method| Target[Target Service Bean] Target -->|4 - Executes SQL queries| DB[(Database)] Target -->|5 - Returns result| Proxy Proxy -->|6 - Commits & Closes connection| TM
4. The Self-Invocation Trap (@Transactional Inside the Same Class)
The Self-Invocation Problem
If a method A (non-transactional) calls method B (
@Transactional) from inside the same class, the Spring proxy is completely bypassed, and transactions will not open.
What do we mean by “the same class”?
When Spring creates a transactional bean, it wraps your class in a Proxy wrapper object.
- When an external caller invokes method A, it hits the proxy first. The proxy handles the transaction, and then delegates to the target instance.
- However, once you are inside the target instance executing method A, and you call
this.methodB(), you are invoking the method directly on the raw object instance (this), completely bypassing the proxy. Therefore, Spring’s transaction interceptor never runs.
graph TD Client -->|Calls methodA external| Proxy[Spring Proxy Wrapper] Proxy -->|Intercepts & Starts Transaction| Target[Target Class Instance] Target -->|Calls this.methodB internally| Target subgraph Inside Target Class Target -->|Bypasses Proxy! No Transaction!| MethodB[methodB @Transactional] end
Does calling a @Transactional method from another @Transactional method in the same class work?
No, it still fails for the exact same reason. Even if both method A and method B are annotated with @Transactional, if method A calls method B via this.methodB() inside the same class, the proxy is bypassed. Method B will execute under whatever transaction context method A established (or none, if method A was somehow invoked unproxied), but any custom propagation behavior (like REQUIRES_NEW) configured on method B will be ignored because the proxy interceptor never saw the call to method B.