1. Overview

Exception handling is one of the most important features of any software application, either a monolithic web or REST full API or microservice. Certainly, we will not want our end-users to see a long error stack trace of null pointer exception or any database exception which is just meaningless and annoying to a very laymen users. Even you are an experienced developer and trying to call a third party REST API  and something goes wrong, you must be able to figure out from the JSON(or whatever) response, what actually is happening under the hood. 

In this article, we are going to see the best practices to handle an exception and to return a meaningful response to the client, and if the response is propagated to a view then how to create the HTML error response. We will use spring boot with Spring MVC since it will be easy to show the best practices to handle the exceptions locally as well as globally.

Now what is the difference between local exception handling vs global exception handling? 

In SpringMVC the request is handled by Controller classes method – if any error occurred in any particular method of the controller class then it can be handled in that @Controller class only -this is the local exception handling

Alternatively you can have Multiple @Controller class throwing the same kind of exception, grouping them, and handling them by the group can be called global exception handling. The advantage of global exception handling is all the code necessary for handling any kind of error in the whole of your application can be separated and modularized.

Note that we will use @RestController instead of @Controller  since we will use REST API instead of the web application but this article is applicable to the web application as well.

2.Spring Boot Global Exception Handling Example

In this example, we will see how can we handle a RuntimeException that will be thrown from CustomerController classes getCustomer() method. So create a spring boot project from STS4 or using your favorite IDE with spring-boot-starter-web dependency in pom.xm. and create a class named CustomerController annotated with @RestController on top of the class with a getCustomer() method.

@RestController
public class CustomerController {
  
@GetMapping("/customer")
  public Customer getCustomer() {
    
    throw new RuntimeException("Hey Customer, We are busy, please come back later");	}
}

getCustomer() will do nothing but throw a RuntimeException() which simulates any error that may occur during the execution of that request.

If you hit the URL http://localhost:8080/customer with a browser(or any REST client) then you will see a white label error page with the message

“This application has no explicit mapping for /error, so you are seeing this as a fallback.

Mon May 18 13:59:17 IST 2020

There was an unexpected error (type=Internal Server Error, status=500).

No message available”

In postman

{
   "timestamp": "2020-05-18T08:41:18.428+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "No message available",
    "path": "/customer"
}

But We have not mentioned the path with /error. Then where does this white label error come from?  Well by default, Spring Boot provides an /error mapping that handles all errors in a sensible way.

We can definitely implement our own controller that will override this default behavior. For that, we have to create a class implementing ErrorController as follows

@Component
public class MyErrorController implements ErrorController {

  @Override
  public String getErrorPath() {
    
    return "Hey customer, we are busy, please come back later";
  }

}

Now if you hit the URL with http://localhost:8080/customer with a browser(or any REST client) , then instead of that default white label error page you can see the more sensible response. 

“Hey Customer, We are busy, please come back later”. 

Well, so far so good. This is one of the ways we can generate a global response for all of the unhandled Exception. But we want to handle our exception in our own method.

For that we need to use @ExceptionHandler annotation in that particular controller class for which we want to handle our exception. Let’s try this.

@ExceptionHandler(value= {RuntimeException.class})
public ResponseEntity<String> getError() {
    
    return new ResponseEntity<>("Hey, Customer, error ocuured during execution", HttpStatus.INTERNAL_SERVER_ERROR);
    
  }
 

Note that @ExceptionHandler takes an argument of type array of exception class that this method will handle. So if we have multiple methods throwing the same type of exception then all those requests will be handled by this same @ExcptionHandler method.

But what if we have multiple controller class throwing the same type of exception, say IOException? Do we need to write a separate @ExcptionHandler method for a separate Controller class?

Here comes the @ControllerAdvise annotation which can be used to handle exception globally. We have to create a class with @ControllerAdvise annotation.

@ControllerAdvice
public class MyGlobalExceptionHandler {
  @ExceptionHandler(value={IOException.class})
  public ResponseEntity<String> handleIOException(){
    return new ResponseEntity<String>("An error occured reading file, please try later", 
        HttpStatus.OK);
  }
}
 

Now wherever within the same package, if any IOException is thrown in any controller class then it will be handled by this handleIOException() method and you will see the response.

  “An error occured reading file, please try later”.

You can also define a class annotated with @ControllerAdvice to customize the JSON document return for a particular controller and/or exception type, as shown in the following example:

@ControllerAdvice(basePackage = “your.basepackage.name”)

@ControllerAdvice(basePackageClass = “CustomerController.class”)

3.Conclusion

Earlier we saw how to use ErrorController to map all the unhandled error of your application so that instead of the default while label error page you can redirect the error to a method which will send a more sensible response. But instead of writing your own implementation you use BasicErrorController which is provided by Spring Boot.

This is particularly useful if you want to add a handler for a new content type (the default is to handle text/HTML specifically and provide a fallback for everything else). To do so,  extend BasicErrorController, add a public method with a @RequestMapping that has a

produces attribute, and create a bean of your new type

If you want to display a custom HTML error page for given status code, you can add a file to an /error folder. Error pages can either be static HTML (that is, added under any of the static resource folders)or be built by using templates. The name of the file should be the exact status code or a series mask.

For example, to map 404 to a static HTML file, your folder structure would be as follows:

src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>

4.References

 

 

 

Was this post helpful?

Leave a Reply

Your email address will not be published. Required fields are marked *