Newsletter |
Exception Handling in RESTful Web Services (JAX-RS) with Jersey
Exception handling in RESTful (JAX-RS) web services is a very important concept, in this article I am going to explain it step by step with an example. FYI. check this article for Creating Simple Maven RESTful Web Service Project in Eclipse. I am directly going to start with directory structure of the current example.
Directory structure
As we are dealing with exception handling, I am going to create a service which will throw an exception 🙂
pom.xml
<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>RESTExceptionHandlingExample</groupId> <artifactId>RESTExceptionHandlingExample</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <repositories> <repository> <id>maven2-repository.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/2/</url> <layout>default</layout> </repository> </repositories> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-server</artifactId> <version>1.8</version> </dependency> </dependencies> <build> <finalName>RESTExceptionHandlingExample</finalName> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerVersion>1.8</compilerVersion> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:web="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd http://xmlns.jcp.org/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.4"> <display-name>RESTExceptionHandlingExample</display-name> <servlet> <servlet-name>jersey-serlvet</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>com.java4s</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jersey-serlvet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
RESTResource.java
package com.java4s; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @Path("/customers") public class RESTResource { @GET @Path("/checkProfile/{id}") public Response getAdminDetails(@PathParam("id") String id) { String msg = RESTService.checkCustomerStatus(id); return Response.status(200).entity(msg).build(); } }
RESTService.java
package com.java4s; public class RESTService { CallDB cdb = new CallDB(); public String checkCustomerStatus(String custId){ MyData da = cdb.getStatus(custId); return da.getStatus().trim(); } }
CallDB.java
package com.java4s; public class CallDB { public MyData getStatus(String custId) { // Lets say, database logic will go here and setting the output in MyData bean MyData da = new MyData(); // da.setStatus("Valid"); return da; } }
MyData.java
package com.java4s; public class MyData { String status; public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } }
Explanation
So we are good to run the application now, if you observe I have just created 4 java classes till now..
- RESTResource.java
- RESTService.java
- CallDB.java
- MyData.java
The application will start by hitting the following URL [ I am using 2017 as my server port, this might be different for your server]
http://localhost:2017/RESTExceptionHandlingExample/customers/checkProfile/100
- If you hit the above URL, the flow will come to RESTResource.java, in that at line number 17, I am calling checkCustomerStatus( – ) function of RESTService class, by passing the id
- Consider, CallDB is a class which handle all database related stuff, so in RESTService.java > line number 11, I am calling getStatus( – ) of CallDB
- In CallDB.java > consider we had a database call at line number 7, after that creating MyData class object and setting all the data that I retrieved from the database and returning it at line number 13, if you observe line number 11, I have commented the setter method, means I am not setting status, so by default it contains NULL value ( if we call its getter method, it will return NULL)
- Now the flow will come to RESTService.java > at line number 11, I am calling getStatus(), as this gives null value [as we have not set anything in CallDB > line 11], on that null I am calling .trim() again, which will give NullPointerException 🙂
Hmm.. so successfully we are able to create a service, which will throw a NullPointerException 🙂 If you hit the URL, you will see..
Its the Tomcat default error page, showing the exception as NullPointerException but I don’t think its the right way of displaying the errors to the consumers! lets try to display the error with some custom error message, in order to do that I am going to create a custom (user defined) run time exception, lets say my custom exception class name is CustomerDataNotFoundException.
CustomerDataNotFoundException.java
package com.java4s.ExceptionHandlingRelated; public class CustomerDataNotFoundException extends RuntimeException{ private static final long serialVersionUID = 1L; public CustomerDataNotFoundException(String exceptionMsg) { super(exceptionMsg); } }
Here I have just created an exception class with constructor which takes String as an argument, now let me change RESTService.java as..
RESTService.java
package com.java4s; import com.java4s.ExceptionHandlingRelated.CustomerDataNotFoundException; public class RESTService { CallDB cdb = new CallDB(); public String checkCustomerStatus(String custId){ MyData da = cdb.getStatus(custId); if(da.getStatus() == null) { throw new CustomerDataNotFoundException("Customer status not found with id "+custId); } return da.getStatus().trim(); } }
I am checking the status with if condition. If status is NULL, throwing out the custom exception by passing some meaningful message. Lets run the application and see..
Its throwing our exception with the message we have sent, not bad 😉 but still this is not the right way of showing the errors.
What happens behind the scenes
Actually we are throwing CustomerDataNotFoundException if the status is NULL, if you observe, we are not handling that exception in RESTService, instead simply throwing with our message. So the exception keeps bubbling up and will come to RESTResource (here also we are not handling ) and so from there to JAX-RS and finally will reach Tomcat server container, and server will show its default error page, that’s what we are seeing in the above image.
So in order to stop exception bubbling up to the tomcat server container, we need to create an exception mapper.
CustomerDataNotFoundExceptionMapper.java
package com.java4s.ExceptionHandlingRelated; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @Provider public class CustomerDataNotFoundExceptionMapper implements ExceptionMapper<CustomerDataNotFoundException>{ public Response toResponse(CustomerDataNotFoundException ex) { return Response.status(Status.NOT_FOUND) .entity(new ErrorProps("404", ex.getMessage())) .build(); } }
ErrorProps.java
package com.java4s.ExceptionHandlingRelated; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class ErrorProps { private String status; private String errorMessage; public ErrorProps(){} public ErrorProps(String statusFromOutside, String errorMessageFromOutside) { this.status = statusFromOutside; this.errorMessage = errorMessageFromOutside; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } }
Explanation
- Created an exception mapper CustomerDataNotFoundExceptionMapper for CustomerDataNotFoundException
- All exception mappers should implement ExceptionMapper interface of type generic, for now I am going to use this exception mapper only for our exception, so I have implemented ExceptionMapper of type CustomerDataNotFoundException [check at line number 9]
- We need to override the toResponse method of ExceptionMapper interface, which takes exception as an argument, in this case CustomerDataNotFoundException
- I want to display my exception details as an XML, so created a simple java model ErrorProps.java and annotated with @XmlRootElement
- Now come back to mapper class toResponse method, there I am returning Response object
Response.status( – ) :- setting the current status
.entity( – ) :- passing ErrorProps class object by setting required values, here I am setting status as 404, and our custom exception message - Finally annotate our mapper class with @Provider annotation, so that JAX-RS will register this mapper to intercept the response when particular exception was thrown
Go ahead and run the application and see…
We did it 🙂
But this is only for NullPointerException, but how about the other exceptions ? for that we need to modify the mapper. Let me do it by creating new mapper class.
GenericExceptionMapper.java
package com.java4s.ExceptionHandlingRelated; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @Provider public class GenericExceptionMapper implements ExceptionMapper<Throwable>{ public Response toResponse(Throwable ex) { if(ex instanceof CustomerDataNotFoundException) { return Response.status(Status.NOT_FOUND) .entity(new ErrorProps("404", ex.getMessage())) .build(); } else { return Response.status(Status.INTERNAL_SERVER_ERROR) .entity(new ErrorProps("Some error code, 500 or somthing", ex.getMessage())) .build(); } } }
What are the changes ?
Implement ExceptionMapper of type Throwable, instead of our own exception. If you check the above class, at line number 13, I am checking whether the Throwable is the instance of CustomerDataNotFoundException, if its true, I will build my Response accordingly, like this you can handle any number of exceptions in a single class, you can download and play with it.
That’s it friends, hope you enjoy the article 😉
You Might Also Like
::. About the Author .:: | ||
Very easy to understand for all concepts. Please provide Design Pattern tutorial
I have seen lot blogs and Information on other sites But in this Blog Information is very useful
RestResource.java File need to be corrected. However downloaded example already having right code.
Hi Kindly post Rest Client and How to publish Restful web services
Please include web service (rest and soap)security topic.thanks.we like all your explanations….
Very Nicely Explained.
Thank you Sir. Quit understandable, please do add code for JSON response as well. I bit confused with that concept and on other online resources its difficult to understand.
Please include an article for OAuth authentication for REST
Thanks, it was good Simple and well formed example..!!
Great tutorial ,
But also include the first approach https://stackoverflow.com/questions/583973/jax-rs-jersey-how-to-customize-error-handling
In Single word … ultimate…
Superb explanation in easy language. Cleared all my doubts. Self descriptive. Found so many blogs but this one is ultimate one for me
Hi,
Some one can you please help me how to implement Oauth2 with Jesry Rest API.