DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • Frequently Used Annotations in Spring Boot Applications
  • A Practical Guide to Creating a Spring Modulith Project

Trending

  • Modern Test Automation With AI (LLM) and Playwright MCP
  • Efficient API Communication With Spring WebClient
  • Introducing Graph Concepts in Java With Eclipse JNoSQL
  • SaaS in an Enterprise - An Implementation Roadmap
  1. DZone
  2. Coding
  3. Frameworks
  4. Best Practice for Exception Handling In Spring Boot

Best Practice for Exception Handling In Spring Boot

Keep your exception handling at a central place in your application using @ControllerAdvice

By 
Akash Bhingole user avatar
Akash Bhingole
·
Updated Sep. 02, 20 · Code Snippet
Likes (13)
Comment
Save
Tweet
Share
128.0K Views

Join the DZone community and get the full member experience.

Join For Free

Whenever we think of handling exceptions at any level of our code, we fall under writing everywhere try catch block in our code, and then after some days when we try to go through our code, we find most of the code is filled with handling exceptions. This degrades the readability of our code and also duplication of a lot of logger messages, which we can easily avoid. Here, we will try to learn the powerful feature provided by Spring Boot to avoid these duplications and improve the readability of code while handling exceptions in our application.

As we all know, exception handling is the most important and a crucial thing in securing Spring Boot Rest APIs, which helps us to perform conditional and unconditional checkings for our code and handle any kind of exception in a proper way. Along with its benefits, it also complicates the code and makes the code not easily readable to unknown users. Using try catch blocks anywhere in your code is not recommended because we are not able to read the code properly and also it increases more unwanted lines in your class. So we need to have a common place where we can manage and handle all kinds of exceptions and send the respective error code for the API response depending on the exception types. In this blog, we will try to know a simple way that will make our code be in a better format related to the handling of exceptions provided in SpringBoot. Review other best practices for securing API access tokens.

Description

SpringBoot provides a very powerful annotation called @ControllerAdvide under package org.springframework.web.bind.annotation. This annotation makes our life easy to handle all kinds of exceptions at a central place in our application. We don't need to catch any exception at each method or class separately instead you can just throw the exception from the method and then it will be caught under the central exception handler class annotated by @ControllerAdvide. Any class annotated with @ControllerAdvice will become a controller-advice class which will be responsible for handling exceptions. Under this class, we make use of annotations provided as @ExceptionHandler, @ModelAttribute, @InitBinder. Review a guide covering all Spring Boot Annotations.

Exception handling methods annotated with @ExceptionHandler will catch the exception thrown by the declared class and we can perform various things whenever we come through the related type exceptions.

@ControllerAdvice constructor comes with some special arguments, which allows you to scan only the related portion of your application and handle only those exceptions thrown by the respective classes mentioned in the constructor. By default, it will scan and handle all the classes in your application. Below are some types which we can use to restrict only specific classes to handle exceptions. Related: Learn more about Java Exceptions.

1) annotations - Controllers that are annotated with the mentioned annotations will be assisted by the @ControllerAdvice annotated class and are eligible for exception of those classes

eg. @ControllerAdvice(annotations = RestController.class) - Here the exception helper annotated by @ControllerAdvice will catch all the exceptions thrown by the @RestController annotation classes. 

2) basePackages - By Specifying the packages that we want to scan and handling exceptions for the same.

eg. @ControllerAdvice(basePackages = "org.example.controllers") - This will only scan call the mentioned package and handle the exceptions for the same.

3) assignableTypes - This argument will make sure to scan and handle the exceptions from the mentioned classes

eg. @ControllerAdvice(assignableTypes = {ControllerInterface.class, 
AbstractController.class})

Before Using @ControllerAdvice 

In the below code snippet, we see there are many duplications of lines, and the controller code is not easily readable because of multiple try and catch blocks in each API. 

Java
 




xxxxxxxxxx
1
61


 
1
@RestController
2
@RequestMapping(path = "/employees")
3
public class EmployeeController {
4

          
5
    private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
6
    
7
    private EmployeeDao employeeDao;
8
  
9
    @GetMapping(path="/{employeeId}", produces = "application/json")
10
    public ResponseEntity<Employee> getEmployees(@PathVariable Long employeeId) {
11
        ResponseEntity<Employee> response = null;
12
        try {
13
            if(null==employeeId || positionId.equals(0L)) {
14
                throw new InvalidInputException("Employee Id is not valid");
15
            }
16
            employee = employeeDao.getEmployeeDetails(employeeId);
17
            response = new ResponseEntity<Employee>(employee,HttpStatus.OK);
18
        }
19
        catch(InvalidInputException e) {
20
            Logger.error("Invalid Input:",e.getMessage());
21
            response = new ResponseEntity<Employee>(employee,HttpStatus.BAD_REQUEST);
22
        }
23
        catch(BusinessException e) {
24
            Logger.error("Business Exception:",e.getMessage());
25
            response = new ResponseEntity<Employee>(employee,HttpStatus.INTERNAL_SERVER_ERROR);
26
        }
27
        catch(Exception e) {
28
            Logger.error("System Error:",e.getMessage());
29
            response = new ResponseEntity<Employee>(employee,HttpStatus.INTERNAL_SERVER_ERROR);
30
        }
31
        return response;
32
    }
33
    
34
    @GetMapping(path="/address/{employeeId}", produces = "application/json")
35
    public ResponseEntity<Address> getEmployeeAddress(@PathVariable Long employeeId,@RequestHeader Long userId) {
36
        ResponseEntity<Address> response = null;
37
        try {
38
            if(null==employeeId || positionId.equals(0L)) {
39
                throw new InvalidInputException("Employee Id is not valid");
40
            }
41
            if(null==userId || userId.equals(0L)) {
42
                throw new UnauthorizedException("Unauthorized user");
43
            }
44
            address = employeeDao.getEmployeeAddress(employeeId);
45
            response = new ResponseEntity<Address>(address,HttpStatus.OK);
46
        }
47
        catch(UnauthorizedException e) {
48
            Logger.error("Unauthorized:",e.getMessage());
49
            response = new ResponseEntity<Address>(address,HttpStatus.BAD_REQUEST);
50
        }
51
        catch(InvalidInputException e) {
52
            Logger.error("Invalid Input:",e.getMessage());
53
            response = new ResponseEntity<Address>(address,HttpStatus.BAD_REQUEST);
54
        }
55
        catch(Exception e) {
56
            Logger.error("System Error:",e.getMessage());
57
            response = new ResponseEntity<Address>(address,HttpStatus.INTERNAL_SERVER_ERROR);
58
        }
59
        return response;
60
    }
61
}



After Using @ControllerAdvice 

The below code snippet makes the code easily readable and also reduces duplications of lines.

Java
 




x
58


 
1
@RestController
2
@RequestMapping(path = "/employees")
3
public class EmployeeController {
4

          
5
    private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
6

          
7
    @GetMapping(path="/{employeeId}", produces = "application/json")
8
    public ResponseEntity<Employee> getEmployees(@PathVariable Long employeeId) {
9
        if(null==employeeId || positionId.equals(0L)) {
10
            throw new InvalidInputException("Employee Id is not valid");
11
        }
12
        Employee employee = employeeDao.getEmployeeDetails(employeeId);
13
        return new ResponseEntity<Employee>(employee,HttpStatus.OK);;
14
    }
15
    
16
    @GetMapping(path="/address/{employeeId}", produces = "application/json")
17
    public ResponseEntity<Address> getEmployeeAddress(@PathVariable Long employeeId,@RequestHeader Long userId) {
18
        if(null==employeeId || employeeId.equals(0L)) {
19
            throw new InvalidInputException("Employee Id is not valid");
20
        }
21
        if(null==userId || userId.equals(0L)) {
22
            throw new UnauthorizedException("Unauthorized user");
23
        }
24
        Address address = employeeDao.getEmployeeAddress(employeeId,userId);
25
        return new ResponseEntity<Address>(address,HttpStatus.OK);
26
    }
27
}
28

          
29

          
30
@ControllerAdvice
31
public class ExceptionHelper {
32

          
33
    private static final Logger logger = LoggerFactory.getLogger(ExceptionHelper.class);
34
    
35
    @ExceptionHandler(value = { InvalidInputException.class })
36
    public ResponseEntity<Object> handleInvalidInputException(InvalidInputException ex) {
37
        LOGGER.error("Invalid Input Exception: ",ex.getMessage());
38
        return new ResponseEntity<Object>(ex.getMessage(),HttpStatus.BAD_REQUEST);
39
    }
40
    
41
    @ExceptionHandler(value = { Unauthorized.class })
42
    public ResponseEntity<Object> handleUnauthorizedException(Unauthorized ex) {
43
        LOGGER.error("Unauthorized Exception: ",ex.getMessage());
44
        return new ResponseEntity<Object>(ex.getMessage(),HttpStatus.BAD_REQUEST);
45
    }
46
    
47
    @ExceptionHandler(value = { BusinessException.class })
48
    public ResponseEntity<Object> handleBusinessException(BusinessException ex) {
49
        LOGGER.error("Business Exception: ",ex.getMessage());
50
        return new ResponseEntity<Object>(ex.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR);
51
    }
52
    
53
    @ExceptionHandler(value = { Exception.class })
54
    public ResponseEntity<Object> handleException(Exception ex) {
55
        LOGGER.error("Exception: ",ex.getMessage());
56
        return new ResponseEntity<Object>(ex.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR);
57
    }
58
}


By going through the above example, we find that using @ControllerAdvice will solve our many issues of maintaining code quality, as well as increase readability of the code. This will help everyone to debug all the errors at a common place of your application. We can also easily update loggers for any kind of errors and maintain the uniformity for error messages using this approach.

Spring Framework Spring Boot Best practice application Annotation Error code Blocks EGS (program)

Opinions expressed by DZone contributors are their own.

Related

  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • Frequently Used Annotations in Spring Boot Applications
  • A Practical Guide to Creating a Spring Modulith Project

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!