Friday, September 23, 2022

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>
  <!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl-api -->
  <dependency>
    <groupId>javax.servlet.jsp.jstl</groupId>
    <artifactId>jstl-api</artifactId>
    <version>1.2</version>
  </dependency>
  <dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
  </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 is also there (defaulterror.jsp), 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() (request path - /user), 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. In case SQLException is thrown from the application it doesn’t resolve the exception just the status code 404 is returned. So it displays the error page configured for request path ("/error"). This path ("/error") is defined in the web.xml through this tag.

<error-page>
   <location>/error</location>
</error-page>

ErrorController.java

This is the controller used for the path ("/error").

import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ErrorController {
 @RequestMapping(path = "/error")
 public ModelAndView handle(HttpServletRequest request) {
	 ModelAndView mv = new ModelAndView();
	 String errorMsg = "";
	 int httpErrorCode = getErrorCode(request);
     // Add more status codes as required
	 if(httpErrorCode == 404) {
		 errorMsg = "Required resource not found, please check the data or contact admin.";
	 }else {
		 errorMsg = "An error occurred in the application, please contact admin.";
	 }
	 mv.addObject("errorMsg", errorMsg);
	 mv.setViewName("defaulterror");
	 return mv;
 }
 
 private int getErrorCode(HttpServletRequest httpRequest) {
     return (Integer) httpRequest.getAttribute("javax.servlet.error.status_code");
 }
}

As you can see the request path is - /error here and this controller returns view as "defaulterror".

Deployment descriptor for Spring MVC exception handling

Apart from the DispatcherServlet and URL mapping configuration web.xml also has the setup for the error page. This same error page is used for any response status code returned by the application.

<?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.

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 with the value as "defaulterror" which is same as returned by ErrorController. So the same default error page is used if any status code is returned and as the default view if there is no exception handler to handle the thrown exception.

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"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Error Page</title>
</head>
<body>
  <!-- if errorMsg is empty that means exception is not handled by any handler and coming here  
   because of the defaultErrorView property in SimpleMappingExceptionResolver. If errorMsg is not empty
   that means coming through ErrorController (there is a response status code)-->
  <c:if test="${empty errorMsg}">
    <h3>An error occurred in the application, please contact admin.</h3>
  </c:if>
    <h3>${errorMsg}</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

Refer Spring Web MVC Example With Annotations to see how to do a Maven build and deploy Spring MVC application in Tomcat server.

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 returns response status code 404 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