Exception Handling in Spring MVC with Example

Whenever we work with spring mvc then we assume that everything will always work in the our web application. But what if something goes wrong? What if, while handling a request, an exception is thrown? What response will be sent to the client when thing go awry? 

No matter what happens, good or bad, the outcome of a servlet request is a servlet response. If an exception occurs during request processing, the outcome is still a servlet response. Somehow, the exception must be translated into a response.

Spring Offers multiple default exception handling status codes and ways to translate exceptions to responses.

  • Certain Spring exceptions are automatically mapped to specific HTTP status codes.
  • An exception can be annotated with @ResponseStatus to map it to an HTTP status code.
  • A method can be annotated with @ExceptionHandler to handle the exception.
Default Http Status Code Mapping with Exceptions

404--->Not Found-->NoSuchRequestHandlingMethodException etc.
400--->Bad Request-->BindException, HttpMessageNotReadableException etc.
406--->Not Acceptable-->HttpMediaTypeNotAcceptableException
415--->Unsuppported Media Type-->HttpMediaTypeNotSupportedException
405--->Method Not Allowed-->HttpRequestMethodNotSupportedException
500--->Internal Server Error-->HttpMessageNotWritableException

These above are usually thrown by Spring framework itself as the result of something went wrong in DispatcherServlet and failed any validations. For Example you want access any method in the controller which not mapped with any request then DispatcherServet through NoSuchRequestHandlingMethodException means 404 so resulting in a response with a status code of 404 (Not Found).

In Case of Custom Exception and Error Pages

1.  Mapping Exception to HTTP Status Code

So above handling provided by spring is helpful but not more useful whenever we are taking about Custom Exception  or Custom Error Pages so in this case we have to do something more better but don't worry Spring offers a way to map exceptions to HTTP status codes via the @ResponseStatus annotation.

Suppose we are working with an application about Student Management for that we have an student controller with mapping to find student record from StudentRepository as follows
.
@Controller
public class StudentController {
  @RequestMapping("/doj-student-{rollNumber}")
  public String dojStudentByRollNumber(ModelMap model, @PathVariable(value="rollNumber") String rollNumber){
   Student student = StudentRepository.findStudentByRollNumber(rollNumber);
   String name = student.getFname()+" "+student.getLname()+" "+student.getAddress()+" "+student.getCourse();
   model.put("name", name);
   return "home";
  }

}
As see in the above code when we are passing rollNumber to find a student from StudentRepository if for any rollNumber we got student then everything is fine in the application user get student detail as response. What happens when StudentRepository return null value for any rollNumber in this case user may got Internal Server Error i.e. 500 status code error.


For handling this scenario we have our custom exception StudentNotFoundException as follows.

package com.doj.spring.web.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Student Not Found")
public class StudentNotFoundException extends RuntimeException{

 /**
  * 
  */
 private static final long serialVersionUID = -2581975292273282583L;

}

Here, a Student is retrieved by its rollNumber from the StudentRepository. If findStudentByRollNumber() returns a Student object, that Student is put into the model, and the view whose name is student is tasked with rendering it in the response. But if findStudentByRollNumber() returns null, then a StudentNotFoundException is thrown. For now, StudentController with StudentNotFoundException looks like this:

@Controller
public class StudentController {
  @RequestMapping("/doj-student-{rollNumber}")
  public String dojStudentByRollNumber(ModelMap model, @PathVariable(value="rollNumber") String rollNumber){
   Student student = StudentRepository.findStudentByRollNumber(rollNumber);
    if (student == null) {
 throw new StudentNotFoundException();
   } 
   String name = student.getFname()+" "+student.getLname()+" "+student.getAddress()+" "+student.getCourse();
   model.put("name", name);
   return "home";
  }

}

After introducing this @ResponseStatus annotation, if a StudentNotFoundException were to be thrown from a controller method, the response would have a status code of 404 and a reason of Student Not Found.

2.  ExceptionHandling methods

Mapping exceptions to status codes is simple and sufficient for many cases. But what if you want the response to carry more than just a status code that represents the error that occurred? Rather than treat the exception generically as some HTTP error, maybe you’d like to handle the exception the same way you might handle the request itself.
@Controller
public class StudentController {
  @RequestMapping("/doj-student-{rollNumber}")
  public String dojStudentByRollNumber(ModelMap model, @PathVariable(value="rollNumber") String rollNumber){
   Student student = StudentRepository.findStudentByRollNumber(rollNumber);
    if (student == null) {
 throw new StudentNotFoundException();
   } 
   String name = student.getFname()+" "+student.getLname()+" "+student.getAddress()+" "+student.getCourse();
   model.put("name", name);
   return "home";
  }


    @ExceptionHandler(StudentNotFoundException.class)
    public ModelAndView handleStudentNotFoundException(StudentNotFoundException ex) {
  Map<String, String> model = new HashMap<String, String>();
  model.put("exception", ex.toString());
  return new ModelAndView("student.error", model);

 }
}

The @ExceptionHandler annotation has been applied to the handleStudentNotFoundException() method, designating it as the go-to method when a StudentNotFoundException is thrown. It returns a ModelAndView object, which, just as with the request-handling method, specifies with the logical name of the view to render, telling the user that student not found for this rollNumber.

If @ExceptionHandler methods can handle exceptions thrown from any handler method in the same controller class, you might be wondering if there’s a way they can handle exceptions thrown from handler methods in any controller.




No comments:

Post a Comment