Understanding @ControllerAdvice annotation
@ControllerAdvice is a sub type of @Component annotation and it useful to handle exceptions across the whole application in a single place.
When to use @ControllerAdvice?
If you have number of controllers, handling exception from each controller method is a tedious task. With @ControllerAdvice you can handle all the exceptions in a single(Global) place.
@ControllerAdvice is a annotation-based global exception handling interceptor.
Example of @ControllerAdvice
In this article I’ll explain @ControllerAdvice annotation with a simple REST API.
I’m going to handle exceptions thrown by two controllers: AdminController and CustomerController.

Project Structure
Here I have defined two exceptions: AdminException and CustomerNotFoundException.
I’m going to handle those two exceptions and send a meaningful response to the user using @ControllerAdvice.
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(CustomerNotFoundException.class)
public ResponseEntity customerNotFoundException(Exception ex){
//Create customer response
String output = "Customer not found " + new Date() + " " + ex.getMessage();
return new ResponseEntity(output, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(AdminException.class)
public ResponseEntity invalidNameException(Exception ex){
//Create customer response
String output = "Invalid Admin " + new Date() + " " + ex.getMessage();
return new ResponseEntity(output, HttpStatus.BAD_REQUEST);
}
}
I have created a class annotated with @ControllerAdvice and inside it I have created separate methods to handle each exception. With @ExceptionHandler annotation, I can define which exception should be handled inside each method.
These are my two controllers. For testing purpose I have thrown the defined exceptions when user sends “Invalid” as the request parameter.
@RestController
public class AdminController {
@GetMapping(value = "/admin")
public ResponseEntity getCustomer(@RequestParam(name = "admin") String admin) throws AdminException {
if (admin.equals("Invalid")){//Just for testing
throw new AdminException("Invalid Admin: " + admin);
}
return new ResponseEntity("Admin Request Success", HttpStatus.OK);
}
}
@RestController
public class CustomerController {
@GetMapping(value = "/customer")
public ResponseEntity getCustomer(@RequestParam(name = "name") String name) throws CustomerNotFoundException {
if (name.equals("Invalid")){//Just for testing
throw new CustomerNotFoundException("Invalid Customer: " + name);
}
return new ResponseEntity("Customer Request Success", HttpStatus.OK);
}
}
Let’s run the application for success scenario.
D:\>curl -i http://localhost:8080/customer?name=Prasad
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 24
Date: Thu, 18 Jun 2020 00:58:49 GMT
Customer Request Success
D:\>curl -i http://localhost:8080/admin?admin=AdminName
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 21
Date: Thu, 18 Jun 2020 01:00:30 GMT
Admin Request Success
Now we know that both APIs are working properly. Let’s execute the exceptional scenario where our Controller advice should send use formatted output.
D:\>curl -i http://localhost:8080/customer?name=Invalid
HTTP/1.1 400
Content-Type: text/plain;charset=UTF-8
Content-Length: 73
Date: Thu, 18 Jun 2020 01:04:33 GMT
Connection: close
Customer not found Thu Jun 18 09:04:33 CST 2020 Invalid Customer: Invalid
D:\>curl -i http://localhost:8080/admin?admin=Invalid
HTTP/1.1 400
Content-Type: text/plain;charset=UTF-8
Content-Length: 65
Date: Thu, 18 Jun 2020 01:07:21 GMT
Connection: close
Invalid Admin Thu Jun 18 09:07:21 CST 2020 Invalid Admin: Invalid
Now you can see that we have handled exceptions thrown by multiple controllers and forwarded a costume response to the user.
How to control the access?
By default ControllerAdvice will apply to all the classes which are annotated with @Controller or @RestController annotations. But there are few ways to control the access.
By specifying the base package
@ControllerAdvice("com.prasadct.controllerAdvice.controllerAdvice.controller")
@ControllerAdvice(value = "com.prasadct.controllerAdvice.controllerAdvice.controller")
@ControllerAdvice(basePackages = "com.prasadct.controllerAdvice.controllerAdvice.controller")
All of the above three configurations are valid. All of them only apply to the controllers inside that specific package: “com.prasadct.controllerAdvice.controllerAdvice.controller”
Also you can specify array of packages by specifying those inside curly brackets.
@ControllerAdvice(value = {"pkg1, pkg2, pkg3"})
By Specifying the class
You can apply ControllerAdvice to a specific class or array of classes.
@ControllerAdvice(basePackageClasses = AdminController.class)
@ControllerAdvice(basePackageClasses = {AdminController.class, CustomerController.class})
Important points
- If there are multiple ControllerAdvices and multiple handlers for same exception, you can’t guaranteed that which method will handle the exception. Better you avoid such kind of cases.
- You can extend your ControllerAdvice with ResponseEntityExceptionHandler and it provides to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods.
- There is a more specialized version of exception handler wrapped with @ResponseBody called @RestControllerAdvice which is convenient for RESTful web services.
Conclusion
Handling exceptions globally with @ControllerAdvice allows us to make our business logic clean and transfer all exception handling to a separate component. Also we can provide a unique error response across all APIs.
Sample Code
Please find my example project from Github.
Related articles:
What is Spring Boot and Why it is the best way to start learning Spring Framework?