Sunday, 19 August 2012

Spring REST Tutorial

In this post I’ll show you how to implement a RESTful web service using the Spring framework. REST support was introduced as part of Spring 3 and is built on top of the existing Spring MVC stack, so anyone that has worked with Spring MVC in the past should find the REST support very easy to grasp. I’ll provide a step by step guide so even if you’re not familiar with Spring MVC you should be able get a RESTful service up and running quickly.

What is REST?
REST is an architectural style that has evolved from existing web technologies to allow client applications to communicate with a server using a simple and familiar approach. The approach is familiar because REST is built on top of the HTTP protocol, a protocol that has formed the backbone of the web for years. REST leverages the existing capabilities of HTTP and uses them to provide an architectural approach for implementing a simple and effective client server model.
This formal definition of REST and what constitutes pure RESTful behaviour is well beyond the scope of this blog. It’s definitely something that’s worth discussing so I’ll be posting on this topic pretty soon. For now though I’ll take you through the process of building, deploying and testing a simple RESTful service using the Spring framework.

Technologies Used
Our REST example will be built using the following technology stack.
  • Spring MVC 3.1
  • Maven
  • Tomcat
It is assumed that readers have a basic knowledge of the technologies listed above. To help you get up and running as quick as possible I’ll attach the full project so that you can download and import it into STS (or Eclipse). 

Creating a Project Skeleton.
We'll start off by creating a basic Spring web project. You can either create it by hand or use something like Maven or Spring Roo to generate the basic project structure for you. Given the simplicity of our sample application you will probably create it by hand. Figure 1.0 below shows what the project structure should look like.

The main project components are described below.
  • src/main/java folder – this folder structure will contain all java source required by your project. This includes the MVC controller that will process REST requests and a few other bits and pieces we'll cover later.
  • src/main/resources folder – this folder structure will contain all resources required by your project. This could contain any number of configuration files in an enterprise application but given the simplicity of our demo app we’ll just have the log4j configuration here.
  • META-INF/context.xml – this file contains resources that can be used by Tomcat at runtime. This file is empty in our sample application but in an enterprise application would contain JNDI references to any number of enterprise resources (data sources, MQ managers etc) 
  • rest-services-config.xml - this file contains definitions for the various Spring beans required by our application. This will be covered in detail later.
  • Web.xml – This file is the base configuration for our java web application and is required as part of the standard Java Servlet specification. It is used to bootstrap the application on server start up and contains references to any Servlets or filters required by the application. This will be covered in detail later.
  • POM.xml – this is our Maven configuration and contains information necessary to resolve dependencies (external libraries) and build a deployable WAR file.
Figure 1.0 - Project Structure
Once you have the above project skeleton in place you’ll need to start configuring the application. We’ll start off with Web.xml which is shown below. I’ve commented each section to explain exactly what is being configured and how it impacts the application.
1:  <?xml version="1.0" encoding="UTF-8"?>  
2:  <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
3:             xmlns="http://java.sun.com/xml/ns/javaee"  
4:             xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  
5:             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  
6:             id="WebApp_ID"  
7:             version="2.5">  
8:       <display-name>REST Sample</display-name>  
9:       <!--  
10:            Main configuration file for this Spring web application.  
11:       -->  
12:       <context-param>  
13:            <param-name>contextConfigLocation</param-name>  
14:            <param-value>  
15:                 /WEB-INF/config/rest-services-config.xml  
16:            </param-value>  
17:       </context-param>  
18:       <!--  
19:            Loads the Spring web application context using the config file defined above.  
20:       -->  
21:       <listener>  
22:            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
23:       </listener>  
24:       <!--  
25:            Define the Spring Dispatcher Servlet for the REST services.  
26:            The 'contextConfiguration' param with an empty value means that the  
27:            Spring Context won't try to load a default file called restservices-servlet.xml  
28:        -->  
29:       <servlet>  
30:            <servlet-name>restservices</servlet-name>  
31:            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
32:            <init-param>  
33:                 <param-name>contextConfigLocation</param-name>  
34:                 <param-value></param-value>  
35:            </init-param>  
36:            <load-on-startup>1</load-on-startup>  
37:       </servlet>  
38:       <!--  
39:            This Servlet mapping means that this Servlet will handle all incoming requests  
40:        -->  
41:       <servlet-mapping>  
42:            <servlet-name>restservices</servlet-name>  
43:            <url-pattern>/</url-pattern>  
44:       </servlet-mapping>  
45:  </web-app>  
Next we’ll set up rest-services-config.xml the content of which is shown below. I’ve commented each section to explain exactly what is being configured and how it impacts the application.
 <?xml version="1.0" encoding="UTF-8"?>  
 <beans xmlns="http://www.springframework.org/schema/beans"  
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
           xmlns:oxm="http://www.springframework.org/schema/oxm"  
           xmlns:util="http://www.springframework.org/schema/util"  
           xmlns:mvc="http://www.springframework.org/schema/mvc"  
           xmlns:context="http://www.springframework.org/schema/context"  
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
                                    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-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/oxm  
                                    http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd  
                                    http://www.springframework.org/schema/util  
                                     http://www.springframework.org/schema/util/spring-util-3.0.xsd">  
      <!--  
            Enables automatic mapping of fund objects to and from JSON  
      -->  
      <mvc:annotation-driven/>  
      <!--  
            Setup spring to pull in @Controller, @RequestMapping, etc Configuration scans specified packages  
            for classes configured as Spring managed beans and automatically sets up objects annotated with  
            @Controller, @Service etc.  
      -->  
      <context:component-scan base-package="com.blog.samples.webservices.rest" />  
      <context:component-scan base-package="com.blog.samples.services" />  
      <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />  
      <!--  
            Configures view for returning JSON to the client  
      -->  
      <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">  
           <property name="contentType" value="text/plain"/>  
      </bean>  
      <!--  
            maps handler methods based on HTTP paths
      -->  
      <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">  
           <property name="messageConverters">  
                <util:list id="beanList">  
                     <ref bean="jsonMessageConverter"/>  
                </util:list>  
           </property>  
      </bean>  
      <!--  
            Converts JSON to POJO and vice versa  
      -->  
      <bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>  
 </beans>  
The final part of the project skeleton is our maven POM file. This contains build and dependency details for our application and is defined below.
1:  <?xml version="1.0"?>  
2:  <project xmlns="http://maven.apache.org/POM/4.0.0"  
3:             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
4:             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0  
5:                                      http://maven.apache.org/maven-v4_0_0.xsd">  
6:       <artifactId>rest-sample</artifactId>  
7:       <modelVersion>4.0.0</modelVersion>  
8:       <inceptionYear>2011</inceptionYear>  
9:       <packaging>war</packaging>  
10:       <groupId>com.blog.rest</groupId>  
11:       <version>1.0</version>  
12:        <properties>  
13:      <releaseCandidate>1</releaseCandidate>  
14:      <spring.version>3.1.1.RELEASE</spring.version>  
15:      <java.version>1.5</java.version>  
16:      <jackson.mapper.version>1.5.6</jackson.mapper.version>  
17:      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
18:      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>  
19:      <maven.javadoc.reporting.version>2.7</maven.javadoc.reporting.version>  
20:      <commons.logging.version>1.1.1</commons.logging.version>  
21:      <log4j.version>1.2.16</log4j.version>  
22:      <context.path>rest-sample</context.path>  
23:      <jackson.mapper.version>1.5.6</jackson.mapper.version>  
24:    </properties>  
25:       <build>  
26:            <resources>  
27:                 <resource>  
28:                      <directory>src/main/resources</directory>  
29:                      <filtering>true</filtering>  
30:                 </resource>  
31:            </resources>  
32:            <plugins>  
33:                 <plugin>  
34:                      <groupId>org.apache.maven.plugins</groupId>  
35:                      <artifactId>maven-war-plugin</artifactId>  
36:                      <configuration>  
37:                           <warName>${context.path}</warName>  
38:                      </configuration>  
39:                 </plugin>  
40:            </plugins>  
41:       </build>  
42:       <dependencies>  
43:       <dependency>  
44:        <groupId>log4j</groupId>  
45:        <artifactId>log4j</artifactId>  
46:        <version>${log4j.version}</version>  
47:      </dependency>  
48:      <dependency>  
49:        <groupId>org.springframework</groupId>  
50:        <artifactId>spring-core</artifactId>  
51:        <version>${spring.version}</version>  
52:      </dependency>  
53:      <dependency>  
54:        <groupId>org.springframework</groupId>  
55:        <artifactId>spring-context</artifactId>  
56:        <version>${spring.version}</version>  
57:      </dependency>  
58:      <dependency>  
59:        <groupId>org.springframework</groupId>  
60:        <artifactId>spring-beans</artifactId>  
61:        <version>${spring.version}</version>  
62:      </dependency>  
63:      <dependency>  
64:        <groupId>org.springframework</groupId>  
65:        <artifactId>spring-aop</artifactId>  
66:        <version>${spring.version}</version>  
67:      </dependency>  
68:      <dependency>  
69:        <groupId>org.springframework</groupId>  
70:        <artifactId>spring-aspects</artifactId>  
71:        <version>${spring.version}</version>  
72:      </dependency>  
73:      <dependency>  
74:        <groupId>org.springframework</groupId>  
75:        <artifactId>spring-asm</artifactId>  
76:        <version>${spring.version}</version>  
77:      </dependency>  
78:      <dependency>  
79:        <groupId>org.springframework</groupId>  
80:        <artifactId>spring-expression</artifactId>  
81:        <version>${spring.version}</version>  
82:      </dependency>  
83:      <dependency>  
84:                 <groupId>org.springframework</groupId>  
85:                 <artifactId>spring-web</artifactId>  
86:                 <version>${spring.version}</version>  
87:            </dependency>  
88:            <dependency>  
89:                 <groupId>org.springframework</groupId>  
90:                 <artifactId>spring-webmvc</artifactId>  
91:                 <version>${spring.version}</version>  
92:            </dependency>  
93:            <dependency>  
94:                 <groupId>org.codehaus.jackson</groupId>  
95:                 <artifactId>jackson-mapper-asl</artifactId>  
96:                 <version>${jackson.mapper.version}</version>  
97:            </dependency>  
98:            <dependency>  
99:                 <groupId>javax.servlet</groupId>  
100:                 <artifactId>servlet-api</artifactId>  
101:                 <version>2.4</version>  
102:            </dependency>  
103:       </dependencies>  
104:  </project>  
At this point you should have a project skeleton in place and it should look like the workspace in figure 1.0.

Defining a Domain Object
This sample application will expose a series of RESTful methods that allow client applications to perform operations on Funds. Our fund object is a very simple POJO and is defined as follows.
1:  package com.blog.samples.domain;  
2:    
3:  import java.util.Date;  
4:  import org.codehaus.jackson.map.annotate.JsonSerialize;  
5:  import com.blog.samples.web.utils.DateSerializer;  
6:    
7:  /**  
8:   * The Class Fund.  
9:   */  
10:  public class Fund{  
11:    
12:       private String fundId;  
13:       private String fundDescription;  
14:       private double bidPrice;  
15:       private double offerPrice;  
16:       private Date lastUpdated;  
17:    
18:       public Fund()  
19:       { }  
20:    
21:       /**  
22:        * Gets the fund id.  
23:        *  
24:        * @return the fund id  
25:        */  
26:       public String getFundId() {  
27:            return fundId;  
28:       }  
29:    
30:       /**  
31:        * Sets the fund id.  
32:        *  
33:        * @param fundId the new fund id  
34:        */  
35:       public void setFundId(String fundId) {  
36:            this.fundId = fundId;  
37:       }  
38:    
39:       /**  
40:        * Gets the fund description.  
41:        *  
42:        * @return the fund description  
43:        */  
44:       public Object getFundDescription() {  
45:            return fundDescription;  
46:       }  
47:    
48:       /**  
49:        * Sets the fund description.  
50:        *  
51:        * @param fundDescription the new fund description  
52:        */  
53:       public void setFundDescription(String fundDescription) {  
54:            this.fundDescription = fundDescription;  
55:       }  
56:    
57:       /**  
58:        * Gets the bid price.  
59:        *  
60:        * @return the bid price  
61:        */  
62:       public double getBidPrice() {  
63:            return bidPrice;  
64:       }  
65:    
66:       /**  
67:        * Sets the bid price.  
68:        *  
69:        * @param bidPrice the new bid price  
70:        */  
71:       public void setBidPrice(double bidPrice) {  
72:            this.bidPrice = bidPrice;  
73:       }  
74:    
75:       /**  
76:        * Gets the offer price.  
77:        *  
78:        * @return the offer price  
79:        */  
80:       public double getOfferPrice() {  
81:            return offerPrice;  
82:       }  
83:    
84:       /**  
85:        * Sets the offer price.  
86:        *  
87:        * @param offerPrice the new offer price  
88:        */  
89:       public void setOfferPrice(double offerPrice) {  
90:            this.offerPrice = offerPrice;  
91:       }  
92:    
93:       /**  
94:        * Gets the last updated.  
95:        *  
96:        * @return the last updated  
97:        */  
98:       @JsonSerialize(using=DateSerializer.class)  
99:       public Date getLastUpdated() {  
100:            return lastUpdated;  
101:       }  
102:    
103:       /**  
104:        * Sets the last updated.  
105:        *  
106:        * @param lastUpdated the new last updated  
107:        */  
108:       public void setLastUpdated(Date lastUpdated) {  
109:            this.lastUpdated = lastUpdated;  
110:       }  
111:    
112:       @Override  
113:       public String toString() {  
114:            return "Fund [fundId=" + fundId + ", fundDescription="  
115:                      + fundDescription + ", bidPrice=" + bidPrice + ", offerPrice="  
116:                      + offerPrice + ", lastUpdated=" + lastUpdated + "]";  
117:       }  
118:  }  
This is a very simple class and the only part worth mentioning is the @JsonSerialize(using=DateSerializer.class) annotation on the getLastUpdated method. This annotation ensures that Java dates are converted into a suitable JSON date format before being returned to the client. The DateDeserializer class referenced in the annotation handles this conversion and is defined as follows.
1:  package com.blog.samples.web.utils;  
2:    
3:  import java.io.IOException;  
4:  import java.text.SimpleDateFormat;  
5:  import java.util.Date;  
6:  import org.codehaus.jackson.JsonGenerator;  
7:  import org.codehaus.jackson.JsonProcessingException;  
8:  import org.codehaus.jackson.map.JsonSerializer;  
9:  import org.codehaus.jackson.map.SerializerProvider;  
10:    
11:  /**  
12:   * The Class DateSerializer.  
13:   */  
14:  public class DateSerializer extends JsonSerializer<Date> {  
15:    
16:    /* (non-Javadoc)  
17:     * @see org.codehaus.jackson.map.JsonSerializer#serialize(java.lang.Object, org.codehaus.jackson.JsonGenerator, org.codehaus.jackson.map.SerializerProvider)  
18:     */  
19:    @Override  
20:    public void serialize(Date value_p, JsonGenerator gen, SerializerProvider prov_p)  
21:      throws IOException, JsonProcessingException  
22:    {  
23:      SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");  
24:      String formattedDate = formatter.format(value_p);  
25:      gen.writeString(formattedDate);  
26:    }  
27:  }  
Defining REST Operations
Next we’ll create a controller and define the RESTful methods that we want to expose to client applications. Following a standard REST approach we’ll define methods to handle the following different types of request.

HTTP Action
URL
Purpose
GET
http://localhost:8080/rest-sample/rest/funds/12345
Returns fund information to the client for fund Id  specified in URL (in this case 1235)
GET
http://localhost:8080/rest-sample/rest/funds/
Returns fund information to the client for all available funds
POST
http://localhost:8080/rest-sample/rest/funds/
Create a new fund on the sever using the fund data in the HTTP request body. The path to the newly created resource is returned in a HTTP header as follows Location: /rest-sample/rest/funds/12345 
PUT
http://localhost:8080/rest-sample/rest/funds/123456
Update existing fund resource with fund data in HTTP request body. The updated fund data is returned to the client. Returning the updated fund data is not absolutely necessary but can be convenient where the server sets object values like ids or timestamps. By returning the updated entity to the client we can guarantee that the client has the exact current state of that entity.
DELETE
http://localhost:8080/rest-sample/rest/funds/123456
Deletes the fund specified by the fund Id in the URL.

Get a Fund - HTTP GET
1:       @RequestMapping(value = "/rest/funds/{fundId}", method = RequestMethod.GET)  
2:       public ModelAndView getFund(@PathVariable("fundId") String fundId_p) {  
3:            Fund fund = null;  
4:    
5:            /* validate fund Id parameter */  
6:            if (isEmpty(fundId_p) || fundId_p.length() < 5) {  
7:                 String sMessage = "Error invoking getFund - Invalid fund Id parameter";  
8:                 return createErrorResponse(sMessage);  
9:            }  
10:    
11:            try {  
12:                 fund = fundService_i.getFundById(fundId_p);  
13:            } catch (Exception e) {  
14:                 String sMessage = "Error invoking getFund. [%1$s]";  
15:                 return createErrorResponse(String.format(sMessage, e.toString()));  
16:            }  
17:    
18:            logger_c.debug("Returing Fund: " + fund.toString());  
19:            return new ModelAndView(jsonView_i, DATA_FIELD, fund);  
20:       }  
This method handles HTTP GET requests for a specified fund id.
Line 1 - RequestMapping annotation ensures that incoming HTTP requests to /rest/funds/{fundId} will be routed toward this method for processing. method = RequestMethod.GET ensures that only HTTP GET requests are processed by this method.
Line 2 - @PathVariable is a convenient way of stripping a value out of the URL (fund id in this case) and assigning it to a method parameter so that it can be used in the function.
Lines 6 to 9 - Some validation to ensure the fund id specified in the URL is valid. If it isn't valid we return an error response.
Lines 11 to 16 - this is simply a call to our dummy service to get fund information for specified fund.
Line 19 - Creates a ModelAndView object using the JSON view and our fund object. We defined a MappingJacksonJsonView in rest-services-config earlier. This is used to convert the Fund object to JSON before it is returned to the client.

Get all Funds - HTTP GET
1:       @RequestMapping(value = "/rest/funds/", method = RequestMethod.GET)  
2:       public ModelAndView getFunds() {  
3:            List<Fund> funds = null;  
4:    
5:            try {  
6:                 funds = fundService_i.getAllFunds();  
7:            } catch (Exception e) {  
8:                 String sMessage = "Error getting all funds. [%1$s]";  
9:                 return createErrorResponse(String.format(sMessage, e.toString()));  
10:            }  
11:    
12:            logger_c.debug("Returing Funds: " + funds.toString());  
13:            return new ModelAndView(jsonView_i, DATA_FIELD, funds);  
14:       }  
This method handles HTTP GET requests where a fund id is not specified in the URL. In this instance all available funds are returned.
Line 1 - RequestMapping annotation ensures that incoming HTTP requests to /rest/funds/ will be routed toward this method for processing. Note how it differs to the method above in that a specific fund id is not specified. method = RequestMethod.GET ensures that only HTTP GET requests are processed by this method. 
Lines 5 to 10 - this is simply a call to our dummy service to get all funds.
Line 13 - Creates a ModelAndView object using the JSON view and our list of fund objects. We defined a MappingJacksonJsonView in rest-services-config earlier. This is used to convert the Fund list to JSON before it is returned to the client.

Create a Fund - HTTP POST
1:       @RequestMapping(value = { "/rest/funds/" }, method = { RequestMethod.POST })  
2:       public ModelAndView createFund(@RequestBody Fund fund_p,  
3:                                      HttpServletResponse httpResponse_p, WebRequest request_p) {  
4:    
5:            Fund createdFund;  
6:            logger_c.debug("Creating Fund: " + fund_p.toString());  
7:    
8:            try {  
9:                 createdFund = fundService_i.createFund(fund_p);  
10:            } catch (Exception e) {  
11:                 String sMessage = "Error creating new fund. [%1$s]";  
12:                 return createErrorResponse(String.format(sMessage, e.toString()));  
13:            }  
14:    
15:            /* set HTTP response code */  
16:            httpResponse_p.setStatus(HttpStatus.CREATED.value());  
17:    
18:            /* set location of created resource */  
19:            httpResponse_p.setHeader("Location", request_p.getContextPath() + "/rest/funds/" + fund_p.getFundId());  
20:    
21:            /**  
22:             * Return the view  
23:             */  
24:            return new ModelAndView(jsonView_i, DATA_FIELD, createdFund);  
25:       }  

This method handles HTTP POST requests and is used to create a new fund entity.
Line 1 - RequestMapping annotation ensures that incoming HTTP requests to /rest/funds/ will be routed toward this method for processing. Note how it differs to the first two methods in that this method will process HTTP POST requests  as specified with method = RequestMethod.POST.
Line 2 - The @RequestBody annotation takes the contents of the HTTP request body and converts it to a Fund object. The HTTP request body must contain Fund data in the form of valid JSON data. You'll see how the request is set up on the client side later on. The HTTPResponse object is exposed by Spring MVC as a convenient way of setting values on the response - this is described below. Lines 8 to 13 - here we call our dummy service to to create a new fund. In our application nothing really happens here but in a real application this is where you'd probably persist the new Fund.
Line 16 - Here we set a HTTP header indicating a response status of CREATED. The client may then use this as confirmation that the request was successful and that the fund data posted to the server resulted in a new Fund entity being created.
Line 19 - We set a HTTP Location header that contains the URL to the newly created resource (our Fund). This is standard REST practice and enables the client to retrieve the created resource if required.
Line 24 - Creates a ModelAndView object using the JSON view and our newly created fund object. There are various opinions on what a POST request should actually return - in this instance I return the newly created Fund object. You might ask why you would want to return an object that the client just posted to the server? It is possible that the server manipulated the entity posted by the client in some way, perhaps added a server generated id. In this instance you'd want to return the newly created object so that the client has the latest representation of the resource. 

Update a Fund - HTTP PUT
1:       @RequestMapping(value = { "/rest/funds/{fundId}" }, method = { RequestMethod.PUT })  
2:       public ModelAndView updateFund(@RequestBody Fund fund_p, @PathVariable("fundId") String fundId_p,  
3:                                            HttpServletResponse httpResponse_p) {  
4:    
5:            logger_c.debug("Updating Fund: " + fund_p.toString());  
6:    
7:            /* validate fund Id parameter */  
8:            if (isEmpty(fundId_p) || fundId_p.length() < 5) {  
9:                 String sMessage = "Error updating fund - Invalid fund Id parameter";  
10:                 return createErrorResponse(sMessage);  
11:            }  
12:    
13:            Fund fund = null;  
14:    
15:            try {  
16:                 fund = fundService_i.updateFund(fund_p);  
17:            } catch (Exception e) {  
18:                 String sMessage = "Error updating fund. [%1$s]";  
19:                 return createErrorResponse(String.format(sMessage, e.toString()));  
20:            }  
21:    
22:            httpResponse_p.setStatus(HttpStatus.OK.value());  
23:            return new ModelAndView(jsonView_i, DATA_FIELD, fund);  
24:       }  

This method handles HTTP PUT requests and is typically used to update an existing resource, in our case an existing fund.
Line 1 - RequestMapping annotation ensures that incoming HTTP PUT requests to /rest/funds/ will be routed toward this method for processing. 
Line 2 and 3 - The @RequestBody annotation takes the contents of the HTTP request body and converts it to a Fund object. The HTTP request body must contain Fund data in the form of valid JSON data.  The HTTPResponse object is exposed by Spring MVC as a convenient way of setting values on the response - this is described below. The @PathVariable strips the fund id out of the URL and sets it as a parameter variable for convenience. Line 8 to 11 - Perform some simple validation to ensure that the fund id specified in the URL is valid.
Lines 15 to 20 - here we call the dummy service to update our fund. In our application nothing really happens here but in a real application this is where you'd probably update the fund in the database.
Line 22 - Here we set a HTTP header indicating a response status of OK. The client may then use this as confirmation that the request was successful and that the fund data sent to the server resulted in the specified fund being updated.
Line 23 - Creates a ModelAndView object using the JSON view and our updated fund object. There are various opinions on what a PUT request should actually return - in this instance I return the updated Fund object. The reasoning is the same as that explain for the POST request above - it is possible that the server manipulated the entity sent by the client in some way, perhaps added a server generated time stamp. In this instance you'd want to return the updated object so that the client has the latest representation of the resource. 

Delete a Fund - HTTP DELETE
1:       @RequestMapping(value = "/rest/funds/{fundId}", method = RequestMethod.DELETE)  
2:       public ModelAndView removeFund(@PathVariable("fundId") String fundId_p,  
3:                                            HttpServletResponse httpResponse_p) {  
4:    
5:            logger_c.debug("Deleting Fund Id: " + fundId_p.toString());  
6:    
7:            /* validate fund Id parameter */  
8:            if (isEmpty(fundId_p) || fundId_p.length() < 5) {  
9:                 String sMessage = "Error deleting fund - Invalid fund Id parameter";  
10:                 return createErrorResponse(sMessage);  
11:            }  
12:    
13:            try {  
14:                 fundService_i.deleteFund(fundId_p);  
15:            } catch (Exception e) {  
16:                 String sMessage = "Error invoking getFunds. [%1$s]";  
17:                 return createErrorResponse(String.format(sMessage, e.toString()));  
18:            }  
19:    
20:            httpResponse_p.setStatus(HttpStatus.OK.value());  
21:            return new ModelAndView(jsonView_i, DATA_FIELD, null);  
22:       }  

This method handles HTTP DELETE requests and is typically used to delete a resource, in our case a fund.
Line 1 - RequestMapping annotation ensures that incoming HTTP DELETE requests to /rest/funds/ will be routed toward this method for processing. 
Line 2 and 3 - The HTTPResponse object is exposed by Spring MVC as a convenient way of setting values on the response - this is described below. The @PathVariable strips the fund id out of the URL and sets it as a parameter variable for convenience.

Line 8 to 11 - Perform some simple validation to ensure that the fund id specified in the URL is valid.
Lines 13 to 18 - here we call our dummy service to delete the specified fund. In our application nothing really happens here but in a real application this is where you'd probably delete the fund from the database.
Line 20 - Here we set a HTTP header indicating a response status of OK. The client may then use this as confirmation that the request was successful and that the specified fund was deleted.
Line 23 - Creates a ModelAndView object using the JSON view that returns no data. The only thing the client is interested in, is know whether the request was processed successfully or not, and this will be apparent from the OK response status. 

Fund Service
Below is our fund service which exposes a number of methods that were called from the REST methods defined above. This is a dummy service and doesn't really do anything aside form return some hard coded fund information. In a real application, a service like this would use DAOs to perform  database operations corresponding to the requests being handled.  
1:  package com.blog.samples.services;  
2:    
3:  import java.util.ArrayList;  
4:  import java.util.Date;  
5:  import java.util.List;  
6:  import org.apache.log4j.Logger;  
7:  import org.springframework.stereotype.Service;  
8:    
9:  import com.blog.samples.domain.Fund;  
10:    
11:  /**  
12:   * The Class FundService.  
13:   */  
14:  @Service  
15:  public class FundService {  
16:    
17:       private static final Logger logger_c = Logger.getLogger(FundService.class);  
18:    
19:       /**  
20:        * Get the fund by id.  
21:        *  
22:        * @param fundId_p  
23:        *      the fund id_p  
24:        * @return the fund by id  
25:        */  
26:       public Fund getFundById(String fundId_p) {  
27:            Fund fund = new Fund();  
28:    
29:            fund.setFundId(fundId_p);  
30:            fund.setFundDescription("High Risk Equity Fund");  
31:            fund.setBidPrice(26.80);  
32:            fund.setOfferPrice(27.40);  
33:            fund.setLastUpdated(new Date());  
34:    
35:            return fund;  
36:       }  
37:    
38:       /**  
39:        * Gets all funds.  
40:        *  
41:        * @return the all funds  
42:        */  
43:       public List<Fund> getAllFunds() {  
44:            List<Fund> funds = new ArrayList<Fund>();  
45:    
46:            for (int i = 0; i < 10; i++) {  
47:                 Fund fund = new Fund();  
48:    
49:                 fund.setFundId("12345" + i);  
50:                 fund.setFundDescription("High Risk Equity Fund " + (i + 1));  
51:                 fund.setBidPrice(26.80 + (Math.random() * 10));  
52:                 fund.setOfferPrice(27.40 + (Math.random() * 10));  
53:                 fund.setLastUpdated(new Date());  
54:    
55:                 funds.add(fund);  
56:            }  
57:            return funds;  
58:       }  
59:    
60:       /**  
61:        * Creates the fund.  
62:        *  
63:        * @param fund_p  
64:        *      the fund_p  
65:        * @return the fund  
66:        */  
67:       public Fund createFund(Fund fund_p) {  
68:    
69:            logger_c.debug("Persisting fund in database: " + fund_p.toString());  
70:    
71:            /* set id and timestamp */  
72:            fund_p.setFundId("12345");  
73:            fund_p.setLastUpdated(new Date());  
74:    
75:            return fund_p;  
76:       }  
77:    
78:       /**  
79:        * Update fund.  
80:        *  
81:        * @param fund_p  
82:        *      the fund_p  
83:        * @return the fund  
84:        */  
85:       public Fund updateFund(Fund fund_p) {  
86:    
87:            logger_c.debug("Updating fund in database: " + fund_p.toString());  
88:    
89:            /* set timestamp */  
90:            fund_p.setLastUpdated(new Date());  
91:    
92:            return fund_p;  
93:       }  
94:    
95:       /**  
96:        * Delete fund.  
97:        *  
98:        * @param fundId_p  
99:        *      the fund id_p  
100:        */  
101:       public void deleteFund(String fundId_p) {  
102:            logger_c.debug("Deleting fund from database: " + fundId_p.toString());  
103:       }  
104:  }  

Running the Application
Our application is complete so we can now build and deploy. I've run this application on Tomcat but you can run it in any Servlet container. If you don't want to build the application step by step as described in this tutorial you can simply download the complete project (attached to this post), build the WAR file and drop it onto your server.

Testing the Application
There are a number of frameworks available that allow you to quickly and easily build RESTful clients. To keep this tutorial simple I've opted to test the application using a REST plugin for Chrome. REST plugins are available for most browsers so if you don't want to use Chrome you don't have to. I chose the Advanced REST Client plugin for chrome which can be downloaded here (https://chrome.google.com/webstore/detail/hgmloofddffdnphfgcellkdfbfbjeloo). Once installed the plugin can be used to test each of the RESTful endpoints we defined earlier.

Test GET fund
We'll start off by getting a specified fund. Figure 2.0 below shows us sending a GET request to http://localhost:8080/rest-sample/rest/funds for fund id 12345.  The HTTP response code (200 OK) and the JSON response returned from the server are shown below the request.  
Figure 2.0 - Testing HTTP GET
Test GET funds
Figure 3.0 below shows us sending a GET request to http://localhost:8080/rest-sample/rest/funds. This time we don't specify a fund id so the request is handled by the getFunds method and returns all funds to the client.  The HTTP response code (200 OK) and the JSON response returned from the server are shown below the request. Note that the JSON response now contains a list of funds.
Figure 3.0 - Testing HTTP GET
Test POST fund
Figure 4.0 below shows us sending a POST request to http://localhost:8080/rest-sample/rest/funds. This time we populate the HTTP body with JSON describing the fund we want to create. The HTTP response code (200 OK) and the JSON response returned from the server is shown below the request.
Figure 4.0 - Testing HTTP POST
Test PUT fund
Figure 5.0 below shows us sending a PUT request to http://localhost:8080/rest-sample/rest/funds. This time we populate the HTTP body with JSON describing the fund we want to update and identify the fund by putting the fund id in the URL.  The HTTP response code (200 OK) and the JSON response returned from the server are shown below the request.  
Figure 5.0 - Testing HTTP PUT
Test DELETE fund
Figure 6.0 below shows us sending a DELETE request to http://localhost:8080/rest-sample/rest/funds. We identify the fund we want to delete by putting the fund id in the URL.  The HTTP response code (200 OK) tells us that the operation succeeded.
Figure 6.0 - Testing HTTP DELETE
Summary
This tutorial has shown you how to build a simple set of RESTful services using the Spring framework. While the examples are trivial they should provide enough detail to get you up and running. Don't forget you can download the attached code and modify it as you see fit. Enjoy!

Update
I've just uploaded the full source code for this tutorial - you can grab it here https://docs.google.com/folder/d/0B_SZOyniHfc1dlJDS0Z3SWJwYTg/edit

47 comments:

  1. Great post! Is there any way you can post the full source code? I am not sure where the jsonView_i variable comes from.

    ReplyDelete
  2. Hi Angus. Apologies for the delay in getting back to you, I've been really busy in work. I'll get the code uploaded this evening or tomorrow at some stage.

    ReplyDelete
  3. Great post, however i could not find the source code or war file as attachment.
    I would appreciate if you could, tell me how to get the code.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Hi Seema - I've just update the post above with the link to the full source code.

    ReplyDelete
  6. Couldn't able to build it.
    Tried two ways:
    1) import it as GIT Hub project - STS can't recognize it as a GIT project.
    2) import it as a maven project - STS failed to compile it.
    Is it possible to share it as exported project so that it can be imported into STS as it is.

    ReplyDelete
  7. Hi Ashutosh - Import the project as a Maven project and then run a Maven clean install. This should pull down all required dependencies from the public Maven repo and build the project. Make sure that your local Maven install is configured to point at public repo like http://repo1.maven.org/maven2/

    ReplyDelete
    Replies
    1. Thanks Brian...a clean and simple RESTFul WS code which is working all fine on my Tomcat. Thanks a lot!!!

      Delete
    2. You're welcome Ashutosh - glad you found it useful

      Delete
  8. This comment has been removed by a blog administrator.

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

    ReplyDelete
  10. I seem to get the following error:

    SEVERE: Servlet /rest-sample threw load() exception
    java.lang.ClassCastException: org.springframework.web.servlet.DispatcherServlet cannot be cast to javax.servlet.Servlet

    ReplyDelete
  11. Ugh, I accidentally erased the rest of my message. Essentially, I get this error after running mvn tomcat:run.

    ReplyDelete
  12. I wanted to thank you for this post. This is one of the better examples I have found. Specifically, because you utilized Maven and Spring to do this...two technologies that make our lives easier and allow us to focus on the actual problem. Thank you!

    ReplyDelete
  13. method GET, work fine to me.!... but when i tried with method: PUT ; DELETE; POST i get this:


    Status Code: 415 Tipo de Medio No Soportado
    Content-Length: 1080
    Content-Type: text/html;charset=utf-8
    Date: Mon, 07 Jan 2013 11:07:34 GMT
    Server: Apache-Coyote/1.1

    ReplyDelete
  14. can someone give me an example for: "body entry" for a POST request.

    ReplyDelete
  15. Hi Brian, Thanks for your post.
    Your explanation gives a good confidence in understanding and developing RESTFUl Services. I tried building the code on my own using explanation but was not very successful so thought let me go ahead and download and import the project and test.
    However I get following message:
    web.servlet.PageNotFound - No mapping found for HTTP request with URI [/rest-sample/] in DispatcherServlet with name 'restservices'
    Is this something expected?
    when I tried testing the link http://localhost:8080/rest-sample/rest/funds using Advanced Rest Client Application I got 404 error.
    Do you know what I have done wrong?

    ReplyDelete
    Replies
    1. Hi Saj,
      If you browse to the following http://localhost:8080/rest-sample/ you will see the error message web.servlet.PageNotFound - No mapping found for HTTP request with URI [/rest-sample/] in DispatcherServlet with name 'restservices'. I presume this is what you're doing. You need to invoke a specific REST method on the service, like GET for example. Try using http://localhost:8080/rest-sample/rest/funds/12345 to retrieve fund details.

      Delete
  16. Thanks a lot Brian it works now...

    ReplyDelete
  17. I just tried this sample. Thanks a lot! I ran into one thing with PUT and POST. The status code returned is "415 Unsupported Media Type" with description "The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ()"

    If I change @RequestBody to @ModelAttribute for the PUT and POST methods in FundsController, they work. A stackoverflow thread makes it sound like that's safe as long as I don't need to pass different content types to those methods. How did you get @RequestBody to work for POST and PUT?

    ReplyDelete
  18. Thanks Brian, that's an excellent example and the availability of the source code is a real boon. Cheers!

    ReplyDelete
  19. Thank you, this was really helpful!

    ReplyDelete
  20. Excellent tutorial. Some tips for readers while trying this tutorial using Chrome Rest Engine:

    1. GET all funds: Please ensure the URL is below (Notice the slash at the end):
    http://localhost:8080/rest-sample/rest/funds/

    2. POST : URL is same as above without ID, as the purpose is create a new fund. Enter the following data into the Body/Payload section(NOT THE HEAD section):
    {"fundId":"12347","bidPrice":26.8,"lastUpdated":"2013-02-15T18:18:13","fundDescription":"No Risk Equity Fund","offerPrice":30.5}}

    3. PUT : URL must include the fundId you want to update, data format is same as point 2, can replace the values

    4. DELETE: URL must include the fundID which you want to delete

    ReplyDelete
  21. I built the war and put it in my tomcat directory, but the URL you gave for testing doesn't give me anything but a 404. I'm not sure what's going on, but it doesn't bode well for developing a rest service if the sample won't run. Everyone else seems to be able to get it to work - what am I missing?
    Thanks,
    Maffy

    ReplyDelete
  22. @Hao - thank you! I was wondering why I couldn't get it to work like everyone else. I do have a question, though. The servlet-mapping entry in the config associates 'restservices' with the '/' -- where does that get used? I'm not sure how to set up my own application's config given that the example uses something I don't see being used by the rest apis....

    Thanks!

    ReplyDelete
  23. thx.
    I worked it out.
    Just a question : How/why did you choose jackson-mapper-asl?
    If it exists, do you know an alternative lib that woul be more 'normalized'/'standardized'?
    By the way if my RESt responses should produce an xml format, would you recommand something from jackson?

    ReplyDelete
  24. Brian,

    I just want to share my experience on using MappingJacksonJsonView in ModelAndView like

    return new ModelAndView(jsonView_i, DATA_FIELD, fund....);

    The problem with this style is that like in Andriod client Rest when using RestTemplate, we are going to get the response surrounded by by {"data": } which makes the auto DE-serialization to the domain object impossible.

    Here I have assumed data is the name of the object defined in DATA_FIELD.
    Thanks,
    Mohammad

    ReplyDelete
  25. I found the code on github.. did a clone of it and did a "mvn clean install" then moved the war to my tomcat and it does not work

    ReplyDelete
  26. In my case I use JSF+primefaces and hence have ManagedBeans in lieu of spring mvc controllers. The managedbeans talk to services. How do I expose it as rest service?

    -Neal

    ReplyDelete
  27. Hi Neal - You'll need to use Spring MVC controllers to expose RESTful services using Spring. You could perhaps continue using JSF and primefaces for the user interface and use Spring MVC to expose a RESTful service layer?

    If you don't fancy having Spring MVC set up alongside JSF you could look at other options for RESTful services, like Jersey.

    ReplyDelete
  28. Thanks Brian for your post. One point : your war didn't work when deploying into jboss server. I need to make one modification in your pom file :


    javax.servlet
    servlet-api
    2.4
    provided

    ReplyDelete
  29. Hi Brian

    I am running mvn package to create war file and I was able to create war file with name rest-sample.war , but i could not understand the output
    pp]
    [INFO] Webapp assembled in [119 msecs]
    [INFO] Building war: C:\Training\workspace\rest-sample\target\rest-sample.war
    [WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ig
    nored
    (webxml attribute is missing from war task, or ignoreWebxml attribute is specifi
    ed as 'true')
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS

    and when I try to deploy it with mvn tomcat:deploy it says that it is not able to find the rest-sample-1.0.war


    [INFO]
    [INFO] --- maven-war-plugin:2.1.1:war (default-war) @ rest-sample ---
    [INFO] Packaging webapp
    [INFO] Assembling webapp [rest-sample] in [C:\Training\workspace\rest-sample\tar
    get\rest-sample-1.0]
    [INFO] Processing war project
    [INFO] Copying webapp resources [C:\Training\workspace\rest-sample\src\main\weba
    pp]
    [INFO] Webapp assembled in [121 msecs]
    [INFO] Building war: C:\Training\workspace\rest-sample\target\rest-sample.war
    [WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ig
    nored
    (webxml attribute is missing from war task, or ignoreWebxml attribute is specifi
    ed as 'true')
    [INFO]
    [INFO] <<< tomcat-maven-plugin:1.1:deploy (default-cli) @ rest-sample <<<
    [INFO]
    [INFO] --- tomcat-maven-plugin:1.1:deploy (default-cli) @ rest-sample ---
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD FAILURE
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 3.308s
    [INFO] Finished at: Tue Jun 18 15:10:55 CDT 2013
    [INFO] Final Memory: 8M/123M
    [INFO] ------------------------------------------------------------------------
    [ERROR] Failed to execute goal org.codehaus.mojo:tomcat-maven-plugin:1.1:deploy
    (default-cli) on project rest-sample: Cannot find war file: C:\Training\workspac
    e\rest-sample\target\rest-sample-1.0.war -> [Help 1]
    [ERROR]
    [ERROR] To see the full stack trace of the errors, re-run Maven with the -e swit
    ch.
    [ERROR] Re-run Maven using the -X switch to enable full debug logging.
    [ERROR]
    [ERROR] For more information about the errors and possible solutions, please rea
    d the following articles:
    [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionE
    xception

    ReplyDelete
  30. Hi Brian, I was able to get it working when I copied the jar file to the webapps folder in tomcat. However I am just not able to deploy,debug or run it from the netbeans ide! i get a 404 exception when I try to run or debug it. I dont see an option to deploy when I right click on the project. Please help.

    ReplyDelete
  31. Hello,

    I am having trouble getting the sample to connect using the Advanced rest client. I have imported the project as a maven build and that all looks exactly the same. However, I am unsure of where to place the war file within my Tomcat. Is there a certain place? Any help is greatly appreciated!

    ReplyDelete
  32. Hello!
    I've got the tomcat to run on the web, but it won't connect to the get hyperlink. It gives me a 404. What may be causing this?

    Also I get this warning:
    "The file cannot be validated as the XML definition "C:\Users\...\workspace\RestSample\rest-sample\src\main\resources\log4j.dtd (The system cannot find the file specified)" that is specified as describing the syntax of the file cannot be located."
    I've tried configuring the xml context but that has not been working, please help! Thank you!

    ReplyDelete
  33. Great post, I really appreciate this. The code worked in first shot. Now I am trying to modify and checking various scenarios.

    ReplyDelete
  34. Hi ,

    Thanks for the post.
    I have a question:
    Below is the code which I am trying to execute.
    import javax.ws.rs.Consumes;
    import javax.ws.rs.GET;
    import javax.ws.rs.POST;
    import javax.ws.rs.PUT;
    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;
    import javax.ws.rs.QueryParam;
    import com.rest.jersey.webservice.util.JsonUtil;
    import com.rest.jersey.webservice.db.DBConnection;
    import org.codehaus.jettison.json.JSONArray;

    /**
    * This is the web service class which handles the request which contains "/uers" in the URL.
    * Even if you not put "/" after the "user", it handles the request after the path as well.
    * In @Path("/user") or @Path("/user/") gives the same result.
    * E.g. - "/status/str" or "/status/resp".
    * For Tomcat @Path("/user/*") is not working.
    * */
    @Path("/user/")
    public class UserDefinition {

    @Path("/add")
    @POST
    @Produces(MediaType.TEXT_PLAIN)
    public String insertDataForPostMethod(){
    String msg = "";
    try {

    System.out.println("POST method working fine.");
    } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } finally {

    }
    return msg = "Success";
    }


    }

    URL: http://localhost:8080/RestfulWebService/api/user/add

    But still I am not able to execute it and it shows me 405 error message.

    Can you please help to find out what exactly I am missing here.



    ARM

    ReplyDelete
  35. Brian,

    Great post. I have been implementing Rest services using Jersey but this method looks clean too. Thanks.

    Atul,

    By default, you make a GET request and your method executes for POST. You "Advanced Rest client" chrome extension to make a POST request or use cUrl's --request POST.

    ReplyDelete
  36. Great tutorial!! but on post i get a 415 -- Unsupported Media Type error

    ReplyDelete
  37. HI, Brain.. Thanks for your information.

    I am able to run the GET requests, but not others.I ran a post request from chrome. I could see status is 201 created. But when i fetch data unable to see resource. and same thing happened with delete as well. Please help me on this.


    Thanks,
    Uma

    ReplyDelete
  38. Hi Brain..

    When i have used requestbody i am getting 415 - Unsupported Media Type error.
    Please help us. When i have replaced with Modify attribute i worked fine.

    Thanks,
    Uma

    ReplyDelete
  39. This is a great post, but could anyone please tell me how to debug this application from eclipse.

    Thanks,
    Samarjit

    ReplyDelete
  40. This comment has been removed by the author.

    ReplyDelete