Saturday, July 11, 2015

Jersey : Handle invalid json requests by overriding exception handling

In enterprise applications we usually encounter situations where we get invalid request for our RESTful webservice. The processing fails but client doesn't know the exact cause of problem. In the below article I explain how can we handle invalid request by overriding the exception handling of a Jersey based RESTful webservice.

Jersey RESTful Web Services framework is open source, production quality, framework for developing RESTful Web Services in Java that provides support for JAX-RS APIs and serves as a JAX-RS (JSR 311 & JSR 339) Reference Implementation.

Implementation


web.xml


In web.xml, register "com.sun.jersey.spi.container.servlet.ServletContainer", and puts your Jersey service folder under init-param = "org.gv.blog"

<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>org.gv.blog</param-value>
 </init-param>
 <init-param>
  <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
  <param-value>true</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
 <servlet-name>jersey-serlvet</servlet-name>
 <url-pattern>/rest/*</url-pattern>
</servlet-mapping> 

MyRestService.java

REST service implementation
package org.gv.blog; 

@Path("/myService") 
public class MyRestService { 
 @POST 
 @Path("/sendContact") 
 @Consumes(MediaType.APPLICATION_JSON) 
 @Produces(MediaType.TEXT_PLAIN) 
 public Response getMsg(Contact contact) { 
  System.out.println("Contact is : " + contact.getName() + contact.getPhone() + contact.getEmail()); 
  return "Contact accepted"; 
 } 
}

The RESTful webservice expects json input in below format
{
 "name": "Gaurav Varma",
 "phone": 0009991122,
 "email": "gaurav.yourfriend@gmail.com" 
}

Contact.java

The input is mapped to a POJO as below.
public class Contact {

 private String name;
 private BigDecimal phone;
 private String email;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public BigDecimal getPhone() {
  return phone;
 }

 public void setPhone(BigDecimal phone) {
  this.phone = phone;
 }

 public String getEmail() {
  return email;
 }

 public void setEmail(String email) {
  this.email = email;
 }

}

Problem Statement and Solution

Suppose we get an invalid request in input. As per default handling, jersey won't be able to read the request, fail with an exception and return HTTP 500 in response. The client would not be able to know what went wrong. To avoid this we can implement our own exception mapper and override jersey's exception handling.


Case 1. Invalid JSON format

We get an input which is not a valid json:
{
 "name": "Gaurav Varma", IamInvalid
 "phone": 0009991122,
 "email": "gaurav.yourfriend@gmail.com" 
}

This will throw a JsonParseException. To handle this we can implement our own exception mapper.
@Provider
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException>{

    @Override
    public Response toResponse(JsonParseException exception)
    {
        return Response
                .status(Response.Status.BAD_REQUEST)
                .entity("This is an invalid json. The request can not be parsed")
                .type( MediaType.APPLICATION_JSON)
                .build();
    }
    
}

Case 2. Invalid attribute in request

We get an input which is a valid json but contains attribute which is not readable by the webservice:
{
 "nameInvalid": "Gaurav Varma", 
 "phone": 0009991122,
 "email": "gaurav.yourfriend@gmail.com" 
}

This will throw an UnrecognizedPropertyException. To handle this we can implement our own exception mapper.
@Provider
public class UnrecognizedPropertyExceptionMapper implements ExceptionMapper<UnrecognizedPropertyException>{

    @Override
    public Response toResponse(UnrecognizedPropertyException exception)
    {
        return Response
                .status(Response.Status.BAD_REQUEST)
                .entity("This is an invalid request. The field " + exception.getUnrecognizedPropertyName() + " is not recognized by the system.")
                .type( MediaType.TEXT_PLAIN)
                .build();
    }
    
}

Case 3. Invalid attribute type in request

We get an input which is a valid json and contains attribute names expected by the webservice, but the type of attribute is not as expected.
{
 "name": "Gaurav Varma", 
 "phone": "I am not a number",
 "email": "gaurav.yourfriend@gmail.com" 
}

This will throw a JsonMappingException. To handle this we can implement our own exception mapper.
@Provider
public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException >{

    @Override
    public Response toResponse(JsonMappingException exception)
    {
        return Response
                .status(Response.Status.BAD_REQUEST)
                .entity("This is an invalid request. At least one field format is not readable by the system.")
                .type( MediaType.TEXT_PLAIN)
                .build();
    }
    
}

Note: 

The above three will send back HTTP response 400. We can modify the response code/message/type as per our need. Make sure the mappers are present in the same package or child of the package declared in web.xml (org.gv.blog in this case) This makes sure they are accessible to jersey servlet.

21 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi Gaurav,

    I really liked your simple and elegant solution for handling the exceptions. I am trying to implement your solution to handle exceptions in my application and I have made sure the exception mapper in the web.xml. Still the excpetion mapper doesn't kick-in when i supply wrong fields in JSON request body. I directly get a HTTP500 error with javax.servlet.ServletException: com.sun.jersey.api.container.MappableContainerException.

    Do you have any suggestions on what i am missing? Any help will be highly appreciated.


    com.sun.jersey.config.property.packages
    mypackage.entires.here

    ReplyDelete
  3. Hi Raghunath,

    Did you keep the exceptionMapper implementation in the same/child package as declared for jersey in web.xml? I yes, please send me the code and I can suggest further.

    Thanks,
    Gaurav

    ReplyDelete
  4. Thanks for sharing this informative information.You may also refer.....Exception handling in java An exception or exceptional event is a problem that arises during the execution of a program..
    http://www.s4techno.com/blog/2016/07/12/exception-handling/

    ReplyDelete
  5. how to get sevice name or method in Exception mapper

    ReplyDelete
  6. how to get sevice name or method name in JsonMappingExceptionMapper ,UnrecognizedPropertyExceptionMapper,JsonMappingExceptionMapper

    ReplyDelete
  7. This comment has been removed by a blog administrator.

    ReplyDelete
  8. This beginner IPS Cloud Java tutorial describes fundamentals of programming in the Java ... Method references enable you to do this; they are compact, easy-to-read ...

    ReplyDelete
  9. Your article saved my time. Thank you Varma!

    ReplyDelete
  10. How to catch WebApplicationException ?

    ReplyDelete
    Replies
    1. A similar handling to implement ExceptionMapper /WebApplicationException/ should handle that as well.

      Delete
  11. Excellent, that is what i needed, thanks so much

    ReplyDelete
  12. You know your projects stand out of the herd. There is something special about them. It seems to me all of them are really brilliant!
    shipping containers

    ReplyDelete
  13. I love the way you write and share your niche! Very interesting and different! Keep it coming!
    shipping containers

    ReplyDelete
  14. How to handle following JSON Exception:
    Caused by: javax.json.bind.JsonbException: Error deserialize JSON value into type: class java.lang.Integer.

    ReplyDelete
  15. HI. Here is two question. 1. When I add the exception mapper class inner the service class, it doesn't work. When I add the exception mapper class parallel to the service class, it works. Why did the previous situation fail? 2. If I should contains all of your above three cases, how can I merge the three exception mapper into one class?

    ReplyDelete
  16. Excellent! Thank You Very Much :)

    ReplyDelete