Spring RESTful Webservice CRUD Example

In this tutorial we show you how to build a RESTFul Web Service using Spring 4. We create a controller that will manage CRUD operations such as Create, Read, Update and Delete using the correct HTTP request methods POST, GET, PUT and DELETE respectively.

Maven Dependencies

We configure the application using Java configuration, so we completely remove the web.xml servlet descriptor from the project. We can use the maven-war-plugin to tell the compiler not to fail on a missing web.xml file, by setting the failOnMissingWebXml to false.

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/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.jeejava</groupId>
	<artifactId>spring-rest-junit</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-rest-junit Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<jdk.version>1.8</jdk.version>
		<junit.version>4.12</junit.version>
		<jackson.version>2.5.3</jackson.version>
		<servlet.version>3.1.0</servlet.version>
		<spring.version>4.3.8.RELEASE</spring.version>
	</properties>

	<dependencies>
		<!-- servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>${servlet.version}</version>
			<scope>provided</scope>
		</dependency>

		<!-- spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- jstl -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
			<scope>provided</scope>
		</dependency>

		<!-- jackson -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>${jackson.version}</version>
		</dependency>

		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${jackson.version}</version>
		</dependency>

		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
			<version>${jackson.version}</version>
		</dependency>

		<!-- junit -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.hamcrest</groupId>
					<artifactId>hamcrest-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.hamcrest</groupId>
			<artifactId>hamcrest-all</artifactId>
			<version>1.3</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-core</artifactId>
			<version>2.7.19</version>
			<exclusions>
				<exclusion>
					<groupId>org.hamcrest</groupId>
					<artifactId>hamcrest-core</artifactId>
				</exclusion>
			</exclusions>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>com.jayway.jsonpath</groupId>
			<artifactId>json-path</artifactId>
			<version>2.2.0</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<finalName>spring-rest-junit</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>${jdk.version}</source>
					<target>${jdk.version}</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.4</version>
				<configuration>
					<warSourceDirectory>src/main/webapp</warSourceDirectory>
					<warName>spring-rest-junit</warName>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Rest Controller

Let us start by implementing our Rest API. This controller exposes the following Rest APIs:

HTTP GET request to /product/1 returns the user with id 1
HTTP POST request to /product with a product object in JSON creates a new product
HTTP PUT request to /product with a product object in JSON updates the product
HTTP DELETE request to /product/5 deletes the product with id 5

package jeejava.rest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.util.UriComponentsBuilder;

import jeejava.model.Product;
import jeejava.sevice.ProductService;

public class SpringRestController {
	@Autowired
	private ProductService productService;

	@RequestMapping(value = "/product/{id}", method = RequestMethod.GET)
	public ResponseEntity<Product> getProductByid(@PathVariable("id") int id) {
		Product product = productService.findProductById(id);
		if (product == null) {
			return new ResponseEntity<Product>(HttpStatus.NOT_FOUND);
		}
		return new ResponseEntity<Product>(product, HttpStatus.OK);
	}

	@RequestMapping(value = "/product", method = RequestMethod.POST)
	public ResponseEntity<Void> saveProduct(@RequestBody Product product, UriComponentsBuilder ucBuilder) {
		if (product == null || product.getName() == null || "".equals(product.getName())) {
			throw new RuntimeException("Product Name and Price are required fields");
		}

		if (productService.isProductAvailable(product)) {
			System.out.println("A Product with name " + product.getName() + " already exist");
			return new ResponseEntity<Void>(HttpStatus.CONFLICT);
		}

		productService.saveProduct(product);
		HttpHeaders headers = new HttpHeaders();
		headers.setLocation(ucBuilder.path("/product/{id}").buildAndExpand(product.getId()).toUri());
		return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
	}

	@RequestMapping(value = "/product", method = RequestMethod.PUT)
	public ResponseEntity<Void> updateProduct(@RequestBody Product product) {
		if (product == null || product.getName() == null || "".equals(product.getName()) || product.getId() <= 0) {
			throw new RuntimeException("Product Name, Id, Price are required fields");
		}

		Product currentProduct = productService.findProductById(product.getId());
		if (currentProduct == null) {
			return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
		}

		productService.updateProduct(product);
		return new ResponseEntity<Void>(HttpStatus.OK);
	}

	@RequestMapping(value = "/product/{id}", method = RequestMethod.DELETE)
	public ResponseEntity<Void> deleteProductByid(@PathVariable("id") int id) {
		Product currentProduct = productService.findProductById(id);
		if (currentProduct == null) {
			return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
		}

		productService.deleteProductById(id);
		return new ResponseEntity<Void>(HttpStatus.OK);
	}

}

Here are the request and response structures of the above REST service end points.

POST  – http://localhost:8085/spring-rest-junit/product/5

ContentType – Application/json

request

{
"name": "Mobile",
"price": 25498.52
}

response – 201 (Created)

GET – http://localhost:8085/spring-rest-junit/product/5

response

{
"id": 5,
"name": "Mobile",
"price": 25498.52
}

PUT http://localhost:8085/spring-rest-junit/product/

request

{
"id": 5,
"name": "Tablet",
"price": 25498.52
}

response – 200 (OK)

DELETE – http://localhost:8085/spring-rest-junit/product/5

response – 200 (OK)

@RestController is a convenience annotation that does nothing more than adding the @Controller and @ResponseBody annotations.
@RequestMapping annotation is used to map URLs such as /product onto an entire class or a particular handler method. A class-level annotation like /product maps a specific request path onto a controller, with additional method-level annotations further narrowing the mapping for a specific HTTP request method like “GET”, “PUT”, “POST” or “DELETE” etc.
RequestMethod is an enumeration of possible HTTP request methods like GET, PUT, POST, DELETE, etc.
@PathVariable annotation indicates that a method parameter should be bound to a URI template variable. To process the @PathVariable annotation, Spring MVC needs to find the matching URI template variable by name. You can specify it in the annotation or, if the URI template variable name matches the method argument name, you can omit that detail.
@RequestBody annotation indicates a method parameter should be bound to the body of the HTTP web request. Behind the scene, a HttpMessageConverter is responsible for converting from the HTTP request message to an object and converting from an object to the HTTP response body.
ResponseEntity extends from HttpEntity which allows you to add a HttpStatus code directly to the response. The ResponseEntity represents the entire HTTP response. You can add header information, status codes and add content to the body.

Servlet Descriptor Initializer

Registers and configures the DispatcherServlet with the location of your Spring configuration file.

package jeejava.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[] { WebMvcConfig.class };
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return null;
	}

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}

}

Spring MVC Configuration

Next, we need to configure Spring 4. We used Java Configuration. We will briefly explain the different configurations.

package jeejava.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "jeejava")
public class WebMvcConfig {

}

@EnableWebMvc imports the Spring MVC configuration from WebMvcConfigurationSupport if it is used in conjunction with @Configuration.
@ComponentScan configures component scanning directives for use with @Configuration. You need to register which packages it can scan to find your Spring components.

Service Class

package jeejava.sevice;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import jeejava.model.Product;

public class ProductService {
	private static AtomicInteger counter = new AtomicInteger();

	private static List<Product> products;

	static {
		products = populateProducts();
	}

	public Product findProductById(int id) {
		for (Product product : products) {
			if (id == product.getId()) {
				return product;
			}
		}
		return null;
	}

	public void saveProduct(Product product) {
		product.setId(counter.incrementAndGet());
		products.add(product);
	}

	public void updateProduct(Product product) {
		int index = products.indexOf(product);
		products.set(index, product);
	}

	public void deleteProductById(int id) {
		Iterator<Product> it = products.iterator();
		while (it.hasNext()) {
			Product product = it.next();
			if (id == product.getId()) {
				it.remove();
			}
		}
	}

	public boolean isProductAvailable(Product product) {
		return findProductById(product.getId()) != null;
	}

	private static List<Product> populateProducts() {
		List<Product> products = new ArrayList<Product>();
		products.add(new Product(counter.incrementAndGet(), "Mobile", 25498.00));
		products.add(new Product(counter.incrementAndGet(), "Desktop", 32658.00));
		products.add(new Product(counter.incrementAndGet(), "Laptop", 52147.00));
		products.add(new Product(counter.incrementAndGet(), "Tab", 18254.00));
		return products;
	}

}

Model Class

package jeejava.model;

public class Product {

	private int id;

	private String name;

	private double price;

	public Product() {
	}

	public Product(int id, String name, double price) {
		this.id = id;
		this.name = name;
		this.price = price;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}

	@Override

	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (int) (id ^ (id >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;

		if (obj == null)
			return false;

		if (getClass() != obj.getClass())
			return false;

		Product other = (Product) obj;

		if (id != other.id)
			return false;

		return true;
	}

}

Thanks for reading.

Soumitra Roy Sarkar

I am a professional Web developer, Enterprise Application developer, Software Engineer and Blogger. Connect me on Roy Tutorials Twitter Facebook  Google Plus Linkedin

Leave a Reply

Your email address will not be published. Required fields are marked *