Sunday, 9 August 2015

6. JAX-RS WebService : Sub Resource locators

So far, We have seen how to bind URI patterns using @Path annotation to a specific Java method.This URI binding is static. JAX-RS also provides way to dispatch requests through sub-resource locators dynamically. Sub-resource locators are Java methods annotated with @Path, but with no HTTP method annotation, like @GET, annotated to them. This type of method returns an resource object that is, itself,  a JAX-RS annotated service that knows how to process the remainder of the request. 

This is best explained using example.

Let's expand our previous example of Product. Till now, we have product resource which serves requests related to products. Now, each products could have review comments. We could add methods to return, add, update, delete review comments in product resource itself however there are few disadvantages with this approach. 
First, review comment itself is another resource in its own sense. So having methods related to  review comment inside "ProductResource" is not completely correct. 
Second, there will be too many methods mapping to each URI for review comments in single "ProductResource" class. What if there are 'likes' to review comments? 
We can not keep on adding more methods to same "ProductResource" class and make monolithic design.

This is where sub resource locator helps. In example below, ProductResource is a root resource as this class is annotated with @Path. The getReviewCommentsResource() is sub-resource locator method.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package in.blogspot.ashish4java.invoicedetails;

import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("products")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public class ProductResource {

 @Path("{productId}/reviewcomments")
 public final ReviewCommentResource getReviewCommentsResource() {
  return new ReviewCommentResource();
 }

}
*I have deleted other methods from this class to keep it simple.

ReviewCommentResource class is as below,

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package in.blogspot.ashish4java.invoicedetails;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

/**
 * @author admin
 * This class is for review comment resource
 *
 */
public class ReviewCommentResource {

     /**
  * This method returns all review comments for particular product. Currently
  * implementation just returns text with productID appended to it.
  *
  * @param productId
  *            the productId for specific product.
  * @return Response JAX-RS response object.
  */
 @GET
 public final Response getReviewComments(final @PathParam("productId") int productId) {
  return Response.status(Status.OK)
    .entity("This is review comment for product " + productId)
    .build();
 }

 /**
  * This method returns particular review comment for particular productId.
  * Current implementation returns text with reviewCommentId and productId.
  *
  * @param reviewCommentId
  *            the reviewCommentId of specific review comment.
  * @param productId
  *            the productId of specific product.
  * @return Response JAX-RS response object.
  */
 @GET
 @Path("{reviewCommentId}")
 public final Response getReviewComment(final @PathParam("reviewCommentId") int reviewCommentId,
   final @PathParam("productId") int productId) {
  return Response.status(Status.OK)
    .entity("This is review comment for " + reviewCommentId
      + " and product ID " + productId).build();
 }

}

If the client is invoked

GET /webapi/products/4/reviewcomments

The ProductResource.getReviewCommentsResource() will be invoked first. This method locate the sub-resource object which will serve the request and provide it. This request will be dispatched to ReviewCommentsResource.getReviewComments(int productId)

Another example is,

GET /webapi/products/4/reviewcomments/2

In this case, request will be dispatched to ReviewCommentsResource.getReviewComment(int reviewCommentId, int productId)

Another, interesting feature of this is, we can return Object type from sub-resource locator method as well as below,

1
2
3
4
@Path("{productId}/reviewcomments")
 public final Object getReviewCommentsResource() {
  return new ReviewCommentResource();
 }

and JAX-RS dynamically locate the resource. This is especially really helpful if we need to return resource object based on input parameter. So we can use factory-method design pattern within it to decide which resource needs to be returned.

This is end of tutorial. Till now, we haven't covered examples for @MatrixParam, @QueryParam, @HeaderParam annotations. We have just touched upon how to add 'location' header when adding new product through POST method. This is very helpful in implementing HATEOAS (Hypermedia as the Engine of Application State). We have not seen how to implement JAX-RS client as well till now. I will try to add more tutorials for these examples in future. till then, STAY TUNED!

Wednesday, 22 July 2015

JAXB - Java Architecture for XML Bindning


JAXB stands for Java Architecture for XML Binding. It is used to convert XML to java object and java object to XML. JAXB provides an API for reading and writing Java objects to and from XML documents.

JAXB is memory efficient and provides fast and efficient way to bind XML and Java objects.


 

JAXB provides below functionalities,

  • Unmarshalling: Reading XML documents to JAVA content tree
  • Marshalling: Writing Java content tree to XML document
  • Schema Generation: JAXB provide ways to generate XML schema from Java objects
  • Apart from this, it provides way to validate. Validation is the process of verifying that XML document meets all the constraints mentioned in schema. JAXB 2.0 provides way to validate at marshalling and unmarshalling time. Validation at marshalling time is very useful especially in web service where user don’t invalidate XML document when modifying the document in JAXB.

In this tutorial, we will see how to create XML document from Java object (UnMarshal), Java object to XML document (Marshal), creating schema file from Java beans and how to use EventHandler for validation.

First, create model object as below,

Address:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package in.blogspot.ashish4java.userdetails.model;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(propOrder = { "address_line_1", "address_line_2", "address_line_3", "address_line_4", "city" })
@XmlRootElement(name="Address")
public class Address {

 public Address() {
  super();
 }

 public Address(String city, String address_line_1, String address_line_2, String address_line_3,
   String address_line_4) {
  super();
  this.city = city;
  this.address_line_1 = address_line_1;
  this.address_line_2 = address_line_2;
  this.address_line_3 = address_line_3;
  this.address_line_4 = address_line_4;
 }

 private String city;
 private String address_line_1;
 private String address_line_2;
 private String address_line_3;
 private String address_line_4;

 @XmlElement(name="City", nillable=false, required=true)
 public String getCity() {
  return city;
 }

 public void setCity(String city) {
  this.city = city;
 }

 @XmlElement(name="AddressLine1", nillable=false, required=true)
 public String getAddress_line_1() {
  return address_line_1;
 }

 public void setAddress_line_1(String address_line_1) {
  this.address_line_1 = address_line_1;
 }

 @XmlElement(name="AddressLine2", nillable=false, required=true)
 public String getAddress_line_2() {
  return address_line_2;
 }

 public void setAddress_line_2(String address_line_2) {
  this.address_line_2 = address_line_2;
 }

 @XmlElement(name="AddressLine3")
 public String getAddress_line_3() {
  return address_line_3;
 }

 public void setAddress_line_3(String address_line_3) {
  this.address_line_3 = address_line_3;
 }

 @XmlElement(name="AddressLine4")
 public String getAddress_line_4() {
  return address_line_4;
 }

 public void setAddress_line_4(String address_line_4) {
  this.address_line_4 = address_line_4;
 }

 @Override
 public String toString() {
  return "Address [city=" + city + ", address_line_1=" + address_line_1 
    + ", address_line_2=" + address_line_2
    + ", address_line_3=" + address_line_3 
    + ", address_line_4=" + address_line_4 + "]";
 }

}


User:

1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
package in.blogspot.ashish4java.userdetails.model;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(propOrder = { "userId", "firstName", "surName", "addressList" })
@XmlRootElement(name="User")
public class User {

 public User() {
  super();
 }

 public User(String firstName, String surName, List<Address> addressList, int userId) {
  super();
  this.firstName = firstName;
  this.surName = surName;
  this.addressList = addressList;
  this.userId = userId;
 }

 private String firstName;
 private String surName;
 private List<Address> addressList;
 private int userId;

 /**
  * @return the firstName
  */
 @XmlElement(name="FirstName", nillable=false, required=true)
 public String getFirstName() {
  return firstName;
 }

 /**
  * @param firstName
  *            the firstName to set
  */
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 /**
  * @return the surName
  */
 @XmlElement(name="SurName", nillable=false, required=true)
 public String getSurName() {
  return surName;
 }

 /**
  * @param surName
  *            the surName to set
  */
 public void setSurName(String surName) {
  this.surName = surName;
 }

 /**
  * @return the addressList
  */
 @XmlElementWrapper(name="Addresses", required=true, nillable=false)
 @XmlElement(name="Address", type=Address.class, nillable=false, required=true)
 public List<Address> getAddressList() {
  return addressList;
 }

 /**
  * @param addressList
  *            the addressList to set
  */
 public void setAddressList(List<Address> addressList) {
  this.addressList = addressList;
 }

 /**
  * @return the userId
  */
 @XmlElement(name="UserId", nillable=false, required=true)
 public int getUserId() {
  return userId;
 }

 /**
  * @param userId
  *            the userId to set
  */
 public void setUserId(int userId) {
  this.userId = userId;
 }

 @Override
 public String toString() {
  return "User [firstName=" + firstName + ", surName=" + surName 
    + ", addressList=" + addressList + ", userId="
    + userId + "]";
 }

}

Note down use of annotations like @XmlType to define ordering of field in XML document, @XmlElement to define whether property is required or not, nullable or not. @XmlElementWrapper is used to create wrapper around collections(list) of addresses.

Now, generate schema, marshall and unmarshall using these java beans:


1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package in.blogspot.ashish4java.userdetails.helper;

import java.io.File;
import java.io.IOException;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.SchemaOutputResolver;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.xml.sax.SAXException;

import in.blogspot.ashish4java.userdetails.model.Address;
import in.blogspot.ashish4java.userdetails.model.User;

/**
 * @author admin
 *
 */
public class MarshallerAndUnMarshaller {

 public static final JAXBContext jc = initContext();
 
 //Location of schema file
 private static final String SCHEMA_FILE = "src/in/blogspot/ashish4java/userdetails/data/UserSchema.xsd";
 
 //location of XML document
 public static final String USER_XML_FILE = "src/in/blogspot/ashish4java/userdetails/data/User.xml";

 private static JAXBContext initContext() {
  try {
   return JAXBContext.newInstance(User.class, Address.class);
  } catch (JAXBException e) {
   System.out.println("Exception initialising context " + e.getMessage());
  }
  return null;
 }

 /**
  * Method to write to XML document from Java object. This method sets the
  * schema document first and uses ValidationEventHandler to validate before
  * writing to XML document file as formatted output. However validation
  * fails, unmarshalling continues for rest of document.
  * 
  * @param user
  *            java object which needs to marshall to XML document.
  */
 public final void MarshalToXML(User user) {
  try {
   final Marshaller marshaller = jc.createMarshaller();
   marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
   marshaller.marshal(user, System.out);
   System.out.println();
   final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
   final Schema schema = schemaFactory.newSchema(new File(SCHEMA_FILE));
   marshaller.setSchema(schema);
   marshaller.setEventHandler(new ValidationEventHandler() {
    @Override
    public boolean handleEvent(ValidationEvent event) {
     System.out.println("MarshalToXML - Schema Validation Issue : Severity : " + event.getSeverity());
     System.out.println("MarshalToXML - Schema Validation Issue : Message : " + event.getMessage());
     return true;
    }
   });
   marshaller.marshal(user, new File(USER_XML_FILE));
  } catch (JAXBException | SAXException e) {
   System.out.println("Exception " + e.getMessage());
   System.out.println("Exception " + e.toString());
  }
  System.out.println();
  System.out.println();
 }

 /**
  * It takes XML document file as input and reads it to Java content tree.
  * Method does validation with schema before reading XML document. However
  * validation fails, unmarshalling continues for rest of document.
  * 
  * @param File
  *            to read to Java content tree.
  */
 public final void UnMarshalToJava(File f) {
  try {
   final Unmarshaller unMarshaller = jc.createUnmarshaller();
   final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
   final Schema schema = schemaFactory.newSchema(new File(SCHEMA_FILE));
   unMarshaller.setSchema(schema);
   unMarshaller.setEventHandler(new ValidationEventHandler() {
    @Override
    public boolean handleEvent(ValidationEvent event) {
     System.out.println("UnMarshalToJava - Schema Validation Issue : Severity : " + event.getSeverity());
     System.out.println("UnMarshalToJava - Schema Validation Issue : Message : " + event.getMessage());
     System.out.println("UnMarshalToJava - Schema Validation Issue : LineNumber : "
       + event.getLocator().getLineNumber());
     System.out.println("UnMarshalToJava - Schame Validation Issue : ColumnNumber : "
       + event.getLocator().getColumnNumber());
     return true;
    }
   });
   final User user = (User) unMarshaller.unmarshal(f);
   System.out.println();
   System.out.println("Unmarshlled User object is " + user);
  } catch (JAXBException | SAXException e) {
   System.out.println("Exception in UnMarshalToJava " + e.getMessage());
  }
 }

 /**
  * This method generate the schema at specified location using Java classes
  * as specified in JAXBContext.
  */
 public final void generateSchema() {
  try {
   jc.generateSchema(new SchemaOutputResolver() {

    @Override
    public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException {
     final File file = new File(SCHEMA_FILE);
     final StreamResult result = new StreamResult(file);
     result.setSystemId(file.toURI().toURL().toString());
     return result;
    }
   });
  } catch (IOException e) {
   System.out.println("Exception in generateSchema " + e.getMessage());
  }
 }

}

handleEvent method of ValidationeventHandler returns true which means, unmarshalling or marshalling to continue even if there are validation error occurs.

Now, we just need to call each of above methods,

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package in.blogspot.ashish4java.userdetails;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import in.blogspot.ashish4java.userdetails.helper.MarshallerAndUnMarshaller;
import in.blogspot.ashish4java.userdetails.model.Address;
import in.blogspot.ashish4java.userdetails.model.User;

public class Main {

 public static void main(String[] args) {
  User user = createUser();
  MarshallerAndUnMarshaller marshUnMarsh = new MarshallerAndUnMarshaller();
  marshUnMarsh.generateSchema();
  marshUnMarsh.MarshalToXML(user);
  marshUnMarsh
    .UnMarshalToJava(new File(MarshallerAndUnMarshaller.USER_XML_FILE));
 }

 private static User createUser() {
  User user = new User("Ashish", null, createAddressList(), 1);
  return user;
 }

 private static List<Address> createAddressList() {
  Address address1 = new Address("Sydney", "CBD", "Australia", null, null);
  Address address2 = new Address("Mumbai", null, "India", null, null);
  Address address3 = new Address("London", "Zone1", "United kingdom", null, null);
  List<Address> addressList = new ArrayList<Address>();
  addressList.add(address1);
  addressList.add(address2);
  addressList.add(address3);
  return addressList;
 }

}

With this, on calling executing this program, we get below output,

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User>
    <UserId>1</UserId>
    <FirstName>Ashish</FirstName>
    <Addresses>
        <Address>
            <AddressLine1>CBD</AddressLine1>
            <AddressLine2>Australia</AddressLine2>
            <City>Sydney</City>
        </Address>
        <Address>
            <AddressLine2>India</AddressLine2>
            <City>Mumbai</City>
        </Address>
        <Address>
            <AddressLine1>Zone1</AddressLine1>
            <AddressLine2>United kingdom</AddressLine2>
            <City>London</City>
        </Address>
    </Addresses>
</User>

MarshalToXML - Schema Validation Issue : Severity : 2
MarshalToXML - Schema Validation Issue : Message : cvc-complex-type.2.4.a: Invalid content was found starting with element 'Addresses'. One of '{SurName}' is expected.
MarshalToXML - Schema Validation Issue : Severity : 2
MarshalToXML - Schema Validation Issue : Message : cvc-complex-type.2.4.a: Invalid content was found starting with element 'AddressLine2'. One of '{AddressLine1}' is expected.


UnMarshalToJava - Schema Validation Issue : Severity : 2
UnMarshalToJava - Schema Validation Issue : Message : cvc-complex-type.2.4.a: Invalid content was found starting with element 'Addresses'. One of '{SurName}' is expected.
UnMarshalToJava - Schema Validation Issue : LineNumber : 5
UnMarshalToJava - Schame Validation Issue : ColumnNumber : 16
UnMarshalToJava - Schema Validation Issue : Severity : 2
UnMarshalToJava - Schema Validation Issue : Message : cvc-complex-type.2.4.a: Invalid content was found starting with element 'AddressLine2'. One of '{AddressLine1}' is expected.
UnMarshalToJava - Schema Validation Issue : LineNumber : 12
UnMarshalToJava - Schame Validation Issue : ColumnNumber : 27

Unmarshlled User object is User [firstName=Ashish, surName=null, addressList=[Address [city=Sydney, address_line_1=CBD, address_line_2=Australia, address_line_3=null, address_line_4=null], Address [city=Mumbai, address_line_1=null, address_line_2=India, address_line_3=null, address_line_4=null], Address [city=London, address_line_1=Zone1, address_line_2=United kingdom, address_line_3=null, address_line_4=null]], userId=1]

There are validation errors thrown as we have not set 'SurName' field in user object and 'AddressLine2' is also null in one of Address object.

We have not covered 'xjc' which is XML to JAVA compiler to generate java classes from schema document. This is mainly used in 'contract-first' approach where schema document contract is defined first and based on that, Java classes are generated.

This is all about JAXB in this tutorial. Thanks and STAY TUNED!

Friday, 17 July 2015

5. JAX-RS WebService : Exception(Fault) handling

We have seen all success scenarios till now. However what if there is exception? how to handle those in RESTful webservices? Handling of exception mainly includes how to return meaningful message to end client.

There are couple of ways to handle exception in RESTful webservices. First we will look into using "ExceptionMapper". javax.ws.rs.ext.ExceptionMapper is interface which have one method to be implemented 'toResponse(E exception)'.

We can implement this interface and create mapper for each type of exception which application can throw. We can have one generic mapper as well to cover all types of exception like mapper for "Throwable".

In last tutorial, we have seen deletion of product method which takes productId and based on that, it deletes the product. But what if there is no product for given productId and application throws 'EntityNotFoundException'?

Let's see how response looks like with this exception,

delete method is below:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 @DELETE
 @Path("{productId}")
 @Produces(MediaType.TEXT_PLAIN)
 public final Response deleteProduct(@PathParam("productId") final int productId) throws EntityNotFoundException {
  final ProductDao productDao = new ProductDao();
  final boolean isDeleted = productDao.deleteProduct(productId);
  if (isDeleted) {
   return Response.status(Status.OK)
     .header("RESOURCE_DELETED", "TRUE")
     .entity("Resource deleted successfully!").build();
  } else {
   throw new EntityNotFoundException("Product with id " + productId + " not found.");
  }

 }

EntityNotFoundException is custom exception type as below:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package in.blogspot.ashish4java.invoicedetails.exception;

public class EntityNotFoundException extends Exception {

 /**
  * 
  */
 private static final long serialVersionUID = -3227525992494807317L;


 public EntityNotFoundException(final String errorMessage) {
  super(errorMessage);
 }
 
}

With this, if there is deletion request for product which doesn't exists then response looks like below,



What is problem with this response:

  • It doesn't return user friendly error message. It returns standard tomcat error message which doesn't provide additional information on why exception was thrown.
  • Status code returned is 500 which is 'Internal Server Error' however as this is business error we want to set to something like "404 Not Found" for resource not found.

So, let's create ExceptionMapper for this as below,


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package in.blogspot.ashish4java.invoicedetails.exception;

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 EntityNotFoundExceptionMapper implements ExceptionMapper<EntityNotFoundException> {

 @Override
 public Response toResponse(EntityNotFoundException e) {
  return Response.status(Status.NOT_FOUND).header("RESOURCE_FOUND", "FALSE")
    .entity(e.getMessage())
    .build();
 }

}

Note down @Provider annotation which registers this exception mapper in JAX-RS runtime. This declares that the class is of interest to the JAX-RS runtime. toResponse builds the response object by setting status code, response header and error message as response content.

With this, when an application throws an EntityNotFoundException, the toresponse method of the EntityNotFoundExceptionMapper instance will be invoked.

 If there is request to delete Product which does not exists then response will be like below,


Status code is 404 Not found and response content clearly states reason for what went wrong.

This is all about exception mappers.

The other way to handle exception is to throw 'WebApplicationException' or one of subclasses of it. However sometimes this approach is not preferable. This approach either needs to modify existing custom exception type which needs to extend 'WebApplicationException' or have to use existing subclass of WebApplicationException exception. 

In both scenarios, this approach doesn't provide flexibility as first approach of using Exceptionmapper. 

In next tutorial, we will see how to use subresources. Thanks for reading this and STAY TUNED!