· 4 min read ·
java

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 Understanding @controllerAdvice prasadct.com

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.

What is the difference between @Component, @Repository, @Service, and @Controller annotations in Spring?

What is Spring Boot and Why it is the best way to start learning Spring Framework?