This tutorial shows you how to build a Spring MVC RESTFul Web Service CRUD Example. In this example, we create a rest controller with CRUD operations like Create, Read, Update and Delete. This rest service consumes and produces JSON.
Table of contents:
1. Project structure
2. Maven Dependencies
3. Create Rest Controller
4. Configure the Dispatcher Servlet
5. Spring MVC Configuration
6. Controller Endpoint
7. Create the Fruit Service
8. Create dummy Data Access Object (DAO)
9. Deploy Spring MVC RESTFul Web Service CRUD Example
Other interesting posts you may like
Now, let us to create the Spring MVC RESTFul Web Service CRUD Example step by step
Project structure
The project structure should be such as below
Maven Dependencies
We need to use the following dependencies that supports to return object with JSON structure
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 |
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javabycode.spring.mvc.rest</groupId> <artifactId>Restful-CRUD-Example</artifactId> <version>1.0.0-SNAPSHOT</version> <name>SPRING-MVC - ${project.artifactId}</name> <url>http://javabycode.com</url> <packaging>war</packaging> <properties> <spring.version>4.3.0.RELEASE</spring.version> <jackson.version>2.7.4</jackson.version> <logback.version>1.1.7</logback.version> </properties> <dependencies> <!-- spring dependency --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- JSON dependency --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <!-- Logging dependency --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency> <!-- servlet api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>restful-crud-example</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project> |
Notices: By setting the failOnMissingWebXml to false we tell the compiler not to fail on a missing web.xml file.
Create Rest Controller
Now, let us create the Rest Controller by implementing Rest API. This is what our Rest Controller exposes:
GET incoming request to /fruits returns a list of fruits
GET incoming request to /fruits/1 returns the fruit with id 1
POST incoming request to /fruits with a fruit object in JSON creates a new fruit
PUT incoming request to /fruits/4 with a fruit object in JSON updates the fruit with id 4
DELETE incoming request to /fruits/1 deletes the fruit with id 1
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 |
package com.javabycode.controller; import com.javabycode.model.Fruit; import com.javabycode.service.FruitService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.*; import org.springframework.web.util.UriComponentsBuilder; import java.util.List; @RestController @RequestMapping("/fruits") public class FruitController { private final Logger LOG = LoggerFactory.getLogger(FruitController.class); @Autowired private FruitService fruitService; @RequestMapping(method = RequestMethod.GET) public ResponseEntity<List<Fruit>> getAll(@RequestParam(value = "offset", defaultValue = "0") int index, @RequestParam(value = "numberOfRecord", defaultValue = "10") int numberOfRecord) { LOG.info("Getting all fruits with index: {}, and count: {}", index, numberOfRecord); List<Fruit> fruits = fruitService.getAll(index, numberOfRecord); if (fruits == null || fruits.isEmpty()) { return new ResponseEntity<List<Fruit>>(HttpStatus.NO_CONTENT); } return new ResponseEntity<List<Fruit>>(fruits, HttpStatus.OK); } @RequestMapping(value = "{id}", method = RequestMethod.GET) public ResponseEntity<Fruit> get(@PathVariable("id") int id) { LOG.info("Getting fruit with id: {}", id); Fruit fruit = fruitService.findById(id); if (fruit == null) { return new ResponseEntity<Fruit>(HttpStatus.NOT_FOUND); } return new ResponseEntity<Fruit>(fruit, HttpStatus.OK); } @RequestMapping(method = RequestMethod.POST) public ResponseEntity<Void> create(@RequestBody Fruit fruit, UriComponentsBuilder ucBuilder) { LOG.info("Creating fruit: {}", fruit); if (fruitService.exists(fruit)) { return new ResponseEntity<Void>(HttpStatus.CONFLICT); } fruitService.create(fruit); HttpHeaders headers = new HttpHeaders(); headers.setLocation(ucBuilder.path("/fruit/{id}").buildAndExpand(fruit.getId()).toUri()); return new ResponseEntity<Void>(headers, HttpStatus.CREATED); } @RequestMapping(value = "{id}", method = RequestMethod.PUT) public ResponseEntity<Fruit> update(@PathVariable int id, @RequestBody Fruit fruit) { LOG.info("Updating fruit: {}", fruit); Fruit currentFruit = fruitService.findById(id); if (currentFruit == null) { return new ResponseEntity<Fruit>(HttpStatus.NOT_FOUND); } currentFruit.setId(fruit.getId()); currentFruit.setName(fruit.getName()); fruitService.update(fruit); return new ResponseEntity<Fruit>(currentFruit, HttpStatus.OK); } @RequestMapping(value = "{id}", method = RequestMethod.DELETE) public ResponseEntity<Void> delete(@PathVariable("id") int id) { LOG.info("Deleting fruit with id: {}", id); Fruit fruit = fruitService.findById(id); if (fruit == null) { return new ResponseEntity<Void>(HttpStatus.NOT_FOUND); } fruitService.delete(id); return new ResponseEntity<Void>(HttpStatus.OK); } } |
@RestController is a convenience annotation that does nothing more than adding the @Controller and @ResponseBody annotations.
@RequestMapping annotation for mapping web requests onto specific handler classes and/or handler methods. Provides a consistent style between Servlet and Portlet environments, with the semantics adapting to the concrete environment. A class-level annotation like /fruits maps a specific request path onto a controller. Method-level annotations narrow the mapping for a specific HTTP request method like GET, PUT, POST or DELETE.
@PathVariable annotation which indicates that a method parameter should be bound to a URI template variable.
The URI Template “/fruits/{id}” specifies the variable name id. When the controller handles this request, the value of id is set to the value found in the appropriate part of the URI. For example, when a request comes in for /fruits/1, the value of id is 1.
@RequestBody annotation indicating a method parameter should be bound to the body of the web request. The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request. Optionally, automatic validation can be applied by annotating the argument with @Valid.
@ResponseBody annotation that indicates a method return value should be bound to the web response body. By annotating the class with @RestController, no longer need to add this annotation to every method individually
ResponseEntity extends from HttpEntity that adds a HttpStatus status code to the response.
HttpHeaders represents HTTP request and response headers, mapping string header names to a list of string values.
CorsFilter
Cross Origin Resource Sharing or CORS is a common issue of the application that uses AJAX. Most browsers block requests from another domain outside the origin domain for security reasons. To fix this issue in Spring MVC 4 we will create a filter to set the Access-Control-Allow-Origin header in the response to accept all clients or only permitted clients access the resources on the other domain. We have the completely tutorial about Cross Origin Resource Sharing so we will don’t discuss here. Please refer to the post Cross Origin Request Blocked Spring MVC Restful Angularjs.
Configure the Dispatcher Servlet
The DispatcherServlet is used to dispatch the requests to the appropriate controller methods. We configure it by extending the AbstractAnnotationConfigDispatcherServletInitializer. Then we register the Spring MVC java Configuration file (Here is MyWebMvcConfig class) with DispatcherServlet in the getServletConfigClasses method and other filter.
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 |
package com.javabycode.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import com.javabycode.filter.CORSFilter; import javax.servlet.Filter; public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { MyWebConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Filter[] getServletFilters() { return new Filter[]{ new CORSFilter()}; } } |
Spring MVC Configuration
The @Configuration annotation indicates that the class can be used by the Spring IoC container as a source of bean definitions.
The @EnableWebMvc is equivalent to in XML. The @EnableWebMvc enables support for the @Controller annotation that uses @RequestMapping to map incomming requests to certain methods.
The @ComponentScan will instruct spring which packages it may scan to discover spring annotated beans.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.javabycode.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @EnableWebMvc @Configuration @ComponentScan("com.javabycode") public class MyWebConfig extends WebMvcConfigurerAdapter { } |
Controller Endpoint
The Fruit POJO will be acts as a model
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 |
package com.javabycode.model; import java.io.Serializable; public class Fruit implements Serializable { private static final long serialVersionUID = 691914827490230925L; private long id; private String name; private String produceBy; private String note; public Fruit() { } public Fruit(long id, String name, String produceBy, String note) { this.id = id; this.name = name; this.produceBy = produceBy; this.note = note; } public long getId() { return id; } public void setId(long id) { this.id = id; } public void setName(String name) { this.name = name; } public void setNote(String note) { this.note = note; } public void setProduceBy(String produceBy) { this.produceBy = produceBy; } public String getName() { return name; } public String getNote() { return note; } public String getProduceBy() { return produceBy; } } |
Create the Fruit Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.javabycode.service; import java.util.List; import com.javabycode.model.Fruit; public interface FruitService { List<Fruit> getAll(int offset, int count); Fruit findById(int id); Fruit findByName(String name); void create(Fruit user); void update(Fruit user); void delete(int id); boolean exists(Fruit user); } |
and implementation class
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 |
package com.javabycode.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.javabycode.dao.FruitDAO; import com.javabycode.model.Fruit; import java.util.List; @Service public class FruitServiceImpl implements FruitService { @Autowired FruitDAO fruitDAO; @Override public List<Fruit> getAll(int offset, int count) { return fruitDAO.getAll(); } @Override public Fruit findById(int id){ return fruitDAO.findById(id); } @Override public Fruit findByName(String name) { return fruitDAO.findByName(name); } @Override public void create(Fruit fruit) { fruitDAO.create(fruit); } @Override public void update(Fruit fruit) { fruitDAO.update(fruit); } @Override public void delete(int id) { fruitDAO.delete(id); } @Override public boolean exists(Fruit fruit) { return fruitDAO.exists(fruit); } } |
Create dummy Data Access Object (DAO)
Here, we are creating the dummy Fruit DAO for this example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.javabycode.dao; import java.util.List; import com.javabycode.model.Fruit; public interface FruitDAO { public List<Fruit> getAll(); public Fruit findById(int id); public Fruit findByName(String name); public void create(Fruit fruit); public void update(Fruit fruit); public void delete(int id); public boolean exists(Fruit fruit) ; } |
and implementation class
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 |
package com.javabycode.dao; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.springframework.stereotype.Repository; import com.javabycode.model.Fruit; @Repository public class FruitDAOImpl implements FruitDAO { // Dummy database. Initialize with some dummy values. private static final AtomicLong generateID = new AtomicLong(); private static List<Fruit> fruits; { fruits = new ArrayList<Fruit>(); fruits.add(new Fruit(generateID.incrementAndGet(), "Apple", "USA", "Delicious flavour")); fruits.add(new Fruit(generateID.incrementAndGet(), "Orange", "Indonesia", "Delicious flavour")); fruits.add(new Fruit(generateID.incrementAndGet(), "Banana", "Cuba", "Good quality")); } /** * Returns list of fruits from dummy database. * * @return list of fruits */ public List<Fruit> getAll() { return fruits; } @Override public Fruit findById(int id) { for (Fruit fruit : fruits){ if (fruit.getId() == id){ return fruit; } } return null; } @Override public Fruit findByName(String name) { for (Fruit fruit : fruits){ if (fruit.getName().equals(name)){ return fruit; } } return null; } @Override public void create(Fruit fruit) { fruit.setId(generateID.incrementAndGet()); fruits.add(fruit); } @Override public void update(Fruit fruit) { for (Fruit fr : fruits) { if (fruit.getId() == fr.getId()){ int index = fruits.indexOf(fr); fruits.set(index, fruit); } } } @Override public void delete(int id) { Fruit fruit = findById(id); fruits.remove(fruit); } @Override public boolean exists(Fruit fruit) { return findByName(fruit.getName()) != null; } } |
Deploy Spring MVC RESTFul Web Service CRUD Example
After building the project by maven we deploy the file war on application server (Tomcat 8 for example). Now, it’s time to test this service. We are using the plugin DHC REST Client on Chrome to call all the below APIs.
1. Getting all the fruits
The GET incoming request http://localhost:8080/restful-crud-example/fruits returns a list of fruits in JSON format.
2. Getting a fruit by id
The GET incoming request http://localhost:8080/restful-crud-example/fruits/1 returns a fruits in JSON format.
3. Creating a new Fruit
The POST incoming request http://localhost:8080/restful-crud-example/fruits
This request is sending JSON format so we need to add a Content-Type header with the value application/json.
We do the step 1 again, we can see that a new fruit has been added.
4. Updating a Fruit
The PUT incoming request http://localhost:8080/restful-crud-example/fruits/4
5. Deleting a Fruit
The DELETE incoming request http://localhost:8081/spring-mvc-rest/fruits/1
That’s all on how to build Spring MVC RESTFul Web Service CRUD Example.
Download complete source code of example, please click link below
Spring-MVC-4-Restful-Web-Service-CRUD-Example.zip (739 downloads)