Exception Handling in Spring RESTful Web Service
1. Introduction
We might have been across some of the several ways by which we can handle exceptions in a RESTful web service application in Spring. In this article, we will try to explore the best approach we can take to achieve efficient exception handling.
2. Problem Statement
Let’s create a simple application that will identify the employee name in the REST URI. If the employee name provided in the request is numeric, let our application throw a custom exception, which we will be handling through the Exception Handlers, and accordingly return the JSON response to the client. The success response will be the JSON with employee details, while the failure response will be an error JSON with errorCode and the proper error message.
3. Implementation
Let’s first check out how our pom file entries and web.xml look like –
pom.xml
<!-- Spring dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.2.1.RELEASE</version> </dependency> <!-- Jackson JSON Processor --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.4.1</version> </dependency>
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>RESTWithSpringMVCException</display-name> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Let’s now check out the web application context.
mvc-dispatcher-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <mvc:annotation-driven /> <context:component-scan base-package="com.jcombat.controller" /> </beans>
It’s now time to create the entity classes, one for Employee and the other one for the ErrorResponse, that is to be returned as JSON in case of any exception in any of the layers in our application.
Employee.java
package com.jcombat.bean; public class Employee { private String empId; private String name; public String getEmpId() { return empId; } public void setEmpId(String empId) { this.empId = empId; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
ErrorResponse.java
package com.jcombat.bean; public class ErrorResponse { private int errorCode; private String message; public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
We need to have our own custom exception class as well. I hope we all are already aware of custom exceptions. Let’s quickly create one for our application.
EmployeeException.java
package com.jcombat.exception; public class EmployeeException extends Exception { private static final long serialVersionUID = 1L; private String errorMessage; public String getErrorMessage() { return errorMessage; } public EmployeeException(String errorMessage) { super(errorMessage); this.errorMessage = errorMessage; } public EmployeeException() { super(); } }
Spring provides us with @ExceptionHandler annotation to specifically handle a particular or a common type of exceptions in the controller.
Most important part here is to write the rest controller for our application.
DemoController.java
package com.jcombat.controller; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.jcombat.bean.Employee; import com.jcombat.bean.ErrorResponse; import com.jcombat.exception.EmployeeException; @RestController public class EmployeeController { @RequestMapping(value = "/{firstName}", method = RequestMethod.GET) public ResponseEntity<Employee> showMessage( @PathVariable("firstName") String firstName, @RequestParam(value = "empId", required = false, defaultValue = "00000") final String empId) throws EmployeeException { Employee employee = new Employee(); employee.setEmpId(empId); employee.setFirstName(firstName); if (StringUtils.isNumeric(firstName)) { throw new EmployeeException("Invalid employee name requested"); } return new ResponseEntity<Employee>(employee, HttpStatus.OK); } @ExceptionHandler(EmployeeException.class) public ResponseEntity<ErrorResponse> exceptionHandler(Exception ex) { ErrorResponse error = new ErrorResponse(); error.setErrorCode(HttpStatus.PRECONDITION_FAILED.value()); error.setMessage(ex.getMessage()); return new ResponseEntity<ErrorResponse>(error, HttpStatus.OK); } }
Note the @ExceptionHandler method in our controller, which should handle only the EmployeeException thrown in any of the layers of our application.
But what if a NullPointerException gets thrown from nowhere. To be on the safer side, we must have a generic exception handler in our application, which handles all other exception types, such as IOException, NullPointerException and so on. To do that, Spring introduced @ControllerAdvice in version 3.2, where can create a Controller Advice class in our application, which would be capable of handling all the global exception scenarios.
A class which annotated with @ControllerAdvice will be registered as the global exception handler.
Let’s create one for our application.
ExceptionControllerAdvice.java
package com.jcombat.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import com.jcombat.bean.ErrorResponse; @ControllerAdvice public class ExceptionControllerAdvice { @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> exceptionHandler(Exception ex) { ErrorResponse error = new ErrorResponse(); error.setErrorCode(HttpStatus.INTERNAL_SERVER_ERROR.value()); error.setMessage("Please contact your administrator"); return new ResponseEntity<ErrorResponse>(error, HttpStatus.OK); } }
This means that if we ever get an unexpected exception in our application other than the custom exception, a generic error object will be prepared, with a generic error code and error message, which will be returned as error JSON response.
In Spring version earlier than 3.2, creating with a single base controller, extending all the individual controllers, instead of @ControllerAdvice would be a better alternative.
There is something to note here. Error JSON response is not possible to be returned in Spring 3.0.x with ResponseEntity, because of the lack of support Spring 3.0.x provides. The alternative for this would be to use BeanNameViewResolver with ModelAndView as the return type. We will soon be coming up with an example application on this.
4. Running the application
Time to run the application we have created.
Make sure we have published the application to the server and started it.
Now hit the below URI on the browser – http://localhost:8080/RESTWithSpringMVCException/Ramesh?empId=1234
Let’s check out how the error response looks like. Note that we have added an IF block in the EmployeeController, which checks if the path variable for employee first name is numeric. If it’s numeric, our application throws an EmployeeException. Let’s hit the below URI –
If we need to add email as one of the path variables, better we go through the RESTful service application we have created in our previous tutorial.
5. Download the source code
Reference: | Exception Handling in Spring RESTful Web Service from our JCG partner Abhimanyu Prasad at the jCombat blog. |
Good details, good sample code. Thank you.
“firstname” has to be numeric and this is my one of thousands validation rules. Also there are thousands of business rules.
So I wonder if I do need to throw an exception for all my business rules? In some cases (actually most cases) I prefer to have an error/warning/info/message list for my business rules within a context (eg. request scope). I don’t want to break my code for business/valiation rules with exceptions.
Thanks Yusuf! Basically the article helps you understand how the exception handling needs to be done in a Spring based REST application. To throw or not to throw exception for all the business validations would be a design decision to make. You are anyhow handling the exception scenarios with a custom exception class of your own, throwing its instance accordingly. So it depends how you want to take it forward, maybe you can set a warning/message in your response object instead of throwing the custom exception instance and framing an error response. I hope I answered you close. I would… Read more »
Dont we need to set any HTTP STATUS in the case of exceptions thrown.
Nicely explained thanks.
Is it correct that the error response code should be 200 OK with the response body then containing another response code such as 412?
I would have thought that we should set the actual HTTP response code to be the same rather than it always being 200 even when an error occurs?