Sunday, January 17, 2021

Spring MVC Exception Handling - @ExceptionHandler And @ControllerAdvice Example

In the post Spring MVC Exception Handling Tutorial we have already got an overview of how exception handling can be configured in Spring MVC application. In this post we’ll see a Spring MVC exception handling example using @ExceptionHandler, @ControllerAdvice, @ResponseStatus annotations and by rendering a default error page when exception is not resolved by any HandlerExceptionResolver.

Technologies used

Following is the list of tools used for the Spring MVC exception handling example.

  1. Spring 5.0.8 Release (Spring core, spring web, spring webmvc).
  2. Java 10
  3. Tomcat server V 9.0.10
  4. Eclipse IDE

Maven Dependencies

Maven is used for managing dependencies in this Spring MVC exception handling example.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>spring-mvc</groupId>
  <artifactId>spring-mvc</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>Spring MVC</name>
  <description>Spring MVC example</description>
  <properties>
    <spring.version>5.0.8.RELEASE</spring.version>
  </properties>
  <dependencies>
    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-core</artifactId>
       <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.0</version>
        <scope>provided</scope>
    </dependency>
  </dependencies>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
          <release>10</release>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.2.1</version>
        <configuration>
          <warSourceDirectory>WebContent</warSourceDirectory>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Project structure

Here is the final project structure of the Spring MVC exception handling example for reference.

Spring MVC exception handling project structure

Spring MVC exception handling example flow

In this example there is a user registration form (userRegister.jsp) having fields first name, last name and email. Once the form is submitted verification of the fields is done and exception is thrown if the values are not as expected.

There is one exception handler method in the controller class itself. There is also a global exception handler class created for handling exceptions globally using the @ControllerAdvice annotation. These exception handlers are example of ExceptionHandlerExceptionResolver in Spring MVC exception handling.

Bean definition for SimpleMappingExceptionResolver is also configured in the Spring MVC configuration file for handling any exception of type MyException, which is a custom exception class. This handling covers the SimpleMappingExceptionResolver in Spring MVC exception handling.

A default error page rendered by Servlet container is also set, that error page is displayed if an exception is not resolved by any HandlerExceptionResolver or if the response status is set to an error status (i.e. 4xx, 5xx).

Spring MVC exception handling – Controller Classes

MessageController.java

import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.http.HttpServletRequest;
import org.netjs.exception.MyException;
import org.netjs.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MessageController {    
  @RequestMapping(value = "/user", method = RequestMethod.GET)
  public String registerUser(Model model) {    
    model.addAttribute(new User());
    return "userRegister";
  }
    
  @RequestMapping(value = "/registerUser", method = RequestMethod.POST)
  public String showUser(@ModelAttribute("user") User user, Model model) throws Exception{
    model.addAttribute("User", user);
    if(user.getFirstName().length() < 5) {    
      // is handled by the configured SimpleMappingExceptionResolver
      throw new MyException("First name too short exception");
    }if(user.getEmail().equals("")) {
      // is handled by the handleIOException method within this controller
      throw new IOException("Email not entered exception");
    }if(!user.getEmail().contains("@")) {
      // handled in UniversalExceptionController
      throw new IllegalArgumentException("Email not valid exception");
    }if(user.getEmail().equals("123@abc")) {
      // handled in UniversalExceptionController
      throw new SQLException("Email not found excpetion");
    }if(user.getLastName().length() < 3) {
      // not resolved by any HandlerExceptionResolver - default error
      throw new Exception("Last name exception");
    }else {
      return "user";
    }
  }
    
  @ExceptionHandler(IOException.class)
  public ModelAndView handleIOException(HttpServletRequest request, Exception ex){
    ModelAndView mv = new ModelAndView();
    mv.addObject("exception", ex);
    mv.setViewName("error");
    return mv;
  }
}

Using the handler method registerUser(), instance of User Model bean is set and then the logical view name is returned which resolves to the registration form.

Submission of the form (request path - /registerUser) is handled by the method showUser(). In this method there are some field validations that result in different exceptions being thrown.

In the controller class there is an exception handler method handleIOException() that is used to resolve exceptions of type IOException. In the exception handler method a new ModelAndView object is created where exception message is set and the view name is set that resolves to error.jsp.

UniversalExceptionController.java

Writing exception handlers in each controller will make it a repetitive exercise. It is better to configure a global exception handler which can handle exceptions globally. Following class is set to do that.

import java.sql.SQLException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class UniversalExceptionController {
 
 @ExceptionHandler({IllegalArgumentException.class, NumberFormatException.class})
 public ModelAndView IAExceptionHandler(Exception ex) {
  ModelAndView mv = new ModelAndView();
  mv.addObject("exception", ex);
  mv.setViewName("error");
  return mv;
 }
 
 @ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Email not found")
 @ExceptionHandler(SQLException.class)
 public void SQLExceptionHandler(Exception ex) {
  
 }
}

As you can see class is annotated with @ControllerAdvice annotation. Using this annotation you can set up a class with exception handler methods that are used as global exception handlers.

First method in the class IAExceptionHandler handles IllegalArgumentException and NumberFormatException thrown from any controller. As the value of @ExceptionHandler you can provide an array of exception classes if you want to pass more than one exception class.

Second method uses @ResponseStatus annotation, with HTTP status as not found (404) along with value attribute as SQLException. As you can see the exception handler method doesn’t resolve the exception and is not returning any value moreover the error status is 4xx so it will display the default error page.

ErrorController.java

This is the controller used for the default error.

@Controller
public class ErrorController {
 @RequestMapping(path = "/error")
 public String handle(HttpServletRequest request) {
  return "defaulterror";
 }
}

As you can see the request path is - /error here.

Deployment descriptor for Spring MVC exception handling

Default error page which is to be displayed in case exception is not resolved by any HandlerExceptionResolver is configured in web.xml. Thus, apart from the DispatcherServlet and URL mapping configuration web.xml also has the setup for the error page.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
  <display-name>spring-mvc</display-name>
  <servlet>
    <servlet-name>mvcexample</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvcexample</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <error-page>
    <location>/error</location>
  </error-page>
</web-app>

As you can see the location is /error for the error page, we have already seen the ErrorController that has the handler method for this request path.

If you want to configure different pages for different response status codes then you can configure them as following in web.xml, that will be an example of ResponseStatusExceptionResolver.

<error-page>
  <!-- Forbidden status code -->
  <error-code>403</error-code>
  <location>/WEB-INF/jsp/Error403.jsp</location>
</error-page>
<error-page>
  <!-- Missing resource status code -->
  <error-code>404</error-code>
  <location>/WEB-INF/jsp/Error404.jsp</location>
</error-page>

Spring Configuration file

The Spring configuration file is as follows.

mvcexample-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">
        
  <mvc:annotation-driven />
  <context:component-scan base-package="org.netjs.controller" />
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="prefix" value="/WEB-INF/jsp/" />
      <property name="suffix" value=".jsp" />
    </bean>
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
      <property name="exceptionMappings">
        <props>
          <prop key="org.netjs.exception.MyException">error</prop>
        </props>
      </property>
      <property name="defaultErrorView" value="defaulterror"/>
    </bean>
</beans>

As you can see SimpleMappingExceptionResolver is configured here to handle exception of type MyException which is a custom exception class, view name returned is error which resolves to error.jsp. Default error view is also configured.

Custom exception class

The custom exception class MyException configured in SimpleMappingExceptionResolver bean definition is as follows.

 
public class MyException extends RuntimeException {
 private static final long serialVersionUID = -1292317882781432620L;
 private String msg;
 
 public MyException(String msg){
  this.msg = msg;
 }
 
 public MyException(String msg, Throwable cause){
  super(cause);
  this.msg = msg;
 }
 
 @Override
 public String getMessage() {
  return msg;
 } 
}

Model bean in Spring MVC exception handling example

User.java

public class User {

 private String firstName;
 private String lastName;
 private String email;
 
 public String getFirstName() {
  return firstName;
 }
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }
 public String getLastName() {
  return lastName;
 }
 public void setLastName(String lastName) {
  this.lastName = lastName;
 }
 public String getEmail() {
  return email;
 }
 public void setEmail(String email) {
  this.email = email;
 }
}

Spring MVC exception handling example - Views

JSPs used in the Spring MVC exception handling example are listed below.

User registration form – userRegister.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>User Registration</title>
</head>
<body>
  <form:form action="registerUser" modelAttribute="user" method="post">
    <table>
      <tr>
        <td>
          <form:label path="firstName">First Name</form:label>
        </td>
        <td>
          <form:input path="firstName" id="firstname" />
        </td>
      </tr>
      <tr>
        <td>
          <form:label path="lastName">Last Name</form:label>
        </td>
        <td>
          <form:input path="lastName" id="lastname" />
        </td>
      </tr>
      <tr>
        <td>
          <form:label path="email">Email</form:label>
        </td>
        <td>
          <form:input path="email" id="email" />                 
        </td>
      </tr>
      <tr>
        <td><input type="submit" value="Submit"></td>
      </tr>
    </table>
  </form:form>
</body>
</html>

user.jsp

This jsp shows the user data if no exception is thrown.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Spring MVC tutorial - User</title>
</head>
<body>
<table>
  <tr>
    <td>First Name: ${User.firstName}</td>
  </tr>
  <tr>
    <td>Last Name: ${User.lastName}</td>
  </tr>
  <tr>
    <td>Email: ${User.email}</td>
  </tr>
</table>
</body>
</html>

error.jsp

This is the error page which is displayed if an exception is thrown.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Error Page</title>
</head>
<body>
    <h3>Error while registering user</h3>
    Error Message - ${exception.getMessage()}
</body>
</html>

defaulterror.jsp

This is the default error page which is displayed if the exception is not resolved.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Error Page</title>
</head>
<body>
    <h3>An error occurred in the application, please contact admin.</h3>
</body>
</html>

Deploying and running the Spring MVC exception handling example

Once the Maven build is done and application is deployed in Tomcat server you can run it using this URL- http://localhost:8080/spring-mvc/user

Here are some of the screen shots for different values.

If length of first name < 5

If email is not entered

If email doesn’t contain @

If email is entered as 123@abc

This leads to SQLException which is not resolved by any HandlerExceptionResolver so default error page is displayed.

If last name length < 3

In that case Exception is thrown. Since there is no exception handler method that handles exception of type Exception so it remains unresolved resulting in display of default error page.

That's all for this topic Spring MVC Exception Handling - @ExceptionHandler And @ControllerAdvice Example. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Spring Tutorial Page


Related Topics

  1. Spring MVC Form Example With Bean Validation
  2. Spring MVC Redirect Example
  3. Difference Between @Controller And @RestController Annotations in Spring
  4. Spring NamedParameterJdbcTemplate Insert, Update And Delete Example
  5. Spring Transaction Management Example - @Transactional Annotation and JDBC

You may also like-

  1. registerShutdownHook() Method in Spring Framework
  2. BeanFactoryPostProcessor in Spring Framework
  3. How to Inject Prototype Scoped Bean into a Singleton Bean in Spring
  4. Difference Between component-scan And annotation-config in Spring
  5. Zipping Files And Folders in Java
  6. How to Create PDF From XML Using Apache FOP
  7. Java Object Cloning - clone() Method
  8. Transient Keyword in Java With Examples

No comments:

Post a Comment