In this guide, you will learn how to set up and build a simple REST API with Spring, that provides CRUD operations for entries that are saved into a database. In addition, you will learn how to map HTTP request to specific URL and its response codes, and how to handle unmapped requests.
Contribute Code
If you would like to become an active contributor to this project please follow theses simple steps:
- Fork it
- Create your feature branch
- Commit your changes
- Push to the branch
- Create new Pull Request
Source code can be downloaded from github.
What you’ll need
- About 30 minutes
- A favorite IDE or Spring Tool Suite™ already install
- JDK 6 or later
Introduction
As an example, we will be creating a service that accepts HTTP GET, POST, PUT and DELETE requests for performing basic CRUD operations on contacts. The requirements of our REST API are:
- A POST request send to
http://localhost:8080/contact/
must create a new contact entry in the database by using the information found from the request body and return a 200 response code on success as well as the information of the created contact entry including the unique id in the response body. - A DELETE request send to
http://localhost:8080/contact/{id}
must delete the contact entry referenced by the id is found from the URL and return a 200 response code on success or 404 if the requested contact id was not found. - A GET request send to
http://localhost:8080/contact/
must return a 200 response code on success as well as all contact entries that are found in the database. - A GET request send to
http://localhost:8080/contact/{id}
must return a 200 response code on success as well as the information of the contact entry whose id is found from the URL and return a 200 response code on success or 404 if the requested contact id was not found. - A PUT request send to
http://localhost:8080/contact/{id}
must update the information of an existing contact entry by using the information found from the request body and return a 200 response code on success as well as the information of the updated contact entry or 404 if the requested contact id was not found.
In order to achieve this, we will follow these steps:
- Add the needed dependencies.
- Create the entity that contains the information of a single contact entry.
- Create the repository interface with methods supporting reading, updating, deleting and creating contacts against a H2 database.
- Create the service layer that is responsible of mapping contacts into domain objects and vice versa.
- Create the controller class that processes HTTP requests and returns the correct response back to the client.
Spring Boot does not require any specific code layout to work, however, it is recommend that you locate your main application class in a root package above other classes. Here is the layout we will be using:
com +- canchitodev +- example +- DemoprojectApplication.java | +- domain | +- Contact.java | +- exception | +- ConflictException.java | +- ContentNotSupportedException.java | +- ErrorInformation.java | +- ForbiddenException.java | +- GlobalExceptionHandler.java | +- ObjectNotFoundException.java | +- repository | +- ContactRepository.java | +- service | +- ContactService.java | +- controller +- ContactrController.java
So let’s get started!
Getting Started
This tutorial assumes you can create a project with the help of Spring Initializr or Spring Tool Suite™. If you have not, please follow the steps from this post Build a project with Spring Initializr or Spring Tool Suite™ and include the following dependencies: Web, JPA and H2.
On the other hand, if you already have an empty project, you can just add the needed dependencies by modifying the pom.xml
file as follow:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
Allow me to give a brief description of what each dependency does:
- spring-boot-starter-data-jpa: Uses data access technologies with enhanced support for JPA based data access layers.
- spring-boot-starter-web: Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container
- h2: H2 database engine. Creates an in memory database.
- spring-boot-starter-test: Starter for testing Spring Boot applications with libraries including JUnit, Hamcrest and Mockito.
Creating the Entity Class
An entity is a representation of a database register. In our case, it is a class representing a contact. Let’s define the contact class under the domain package.
package com.canchitodev.example.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import org.hibernate.validator.constraints.Email; @Entity public class Contact { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "uuid", unique = true, nullable = false, length = 255) private Long uuid; @Column(name = "first_name", nullable = false, length = 60) private String firstName; @Column(name = "last_name", nullable = false, length = 60) private String lastName; @Column(name = "telephone", nullable = false, length = 60) private String telephone; @Email @Column(name = "mail", nullable = false, length = 60) private String mail; public Contact() {} public Contact(Long uuid, String firstName, String lastName, String telephone, String mail) { this.uuid = uuid; this.firstName = firstName; this.lastName = lastName; this.telephone = telephone; this.mail = mail; } //Getters and setters removed for simplicity @Override public String toString() { return "Contact [uuid=" + uuid + ", firstName=" + firstName + ", lastName=" + lastName + ", telephone=" + telephone + ", mail=" + mail + "]"; } }
Here you have a Contact
class with five attributes, the uuid
, the firstName
, the lastName, the
telephone
, and the mail
. You also have two constructors. The default constructor only exists because it is needed by JPA. The other constructor is the one you’ll use to create instances of Contact
to be saved to the database.
The @Entity
annotation specifies that this class is a JPA entity. And since there is no @Table
annotation, JPA assumes that it is mapped to a table called Contact
.
The property uuid
is annotated with the @Id
so that JPA will recognize it as the object’s ID. In addition, the annotation @GeneratedValue
tell JPA that this property should be automatically generated following the strategy indicated by GenerationType.AUTO
.
The rest of the properties are annotated with @Column
, which means that they are mapped to a column with the name specified by name. @Column
‘s other properties just indicate that the value cannot be null and its max length.
Finally, you can also see the @Email
annotation. This validates that the column must be a valid e-mail address.
Creating the Repository Class
The repository is an interface which allows CRUD operations on an entity.
package com.canchitodev.example.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import com.canchitodev.example.domain.Contact; @Repository public interface ContactRepository extends JpaRepository<Contact, Long>, JpaSpecificationExecutor<Contact> { }
As you can see, we are extending the interface with JpaRepository
. By doing this, we inherit several methods which will allow us to work with Contact
persistence, including methods for saving, deleting, updating and finding entities. Moreover, we also extend it with JpaSpecificationExecutor
. Thanks to this, we will also be able to searches based on query criteria. For now, just keep in mind, that we will be able to do some basic operations on entities, just by extending it with JpaRepository
.
Creating the Service Class
In the service class, you will write all your logic. Once you have added your required validations and data manipulations, you call the repository. Note that this class is not required, as this could be done in the controller class. However, in my opinion it it good practice to separate the logic from the controller. Mainly because you can use the service in other parts of your application.
package com.canchitodev.example.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.canchitodev.example.domain.Contact; import com.canchitodev.example.repository.ContactRepository; @Service @Transactional public class ContactService { @Autowired private ContactRepository contactRepository; public Contact findById(Long uuid) { Contact contact = this.contactRepository.findOne(uuid); return contact; } public List<Contact> findAll() { return this.contactRepository.findAll(); } public void save(Contact contact) { this.contactRepository.save(contact); } public void update(Contact contact) { Contact entity = this.findById(contact.getUuid()); if(contact.getLastName() == null) contact.setLastName(entity.getLastName()); if(contact.getMail() == null) contact.setMail(entity.getMail()); if(contact.getFirstName() == null) contact.setFirstName(entity.getFirstName()); if(contact.getTelephone() == null) contact.setTelephone(entity.getTelephone()); this.save(contact); } public void delete(Long uuid) { Contact contact = this.findById(uuid); this.contactRepository.delete(contact); } }
The @Service
annotation allows for implementation classes to be autodetected through classpath scanning. Meanwhile, the @Transactional
annotation describes transaction attributes on a method or class.
You might find it curious that in the update()
method, we are sending a Contact
as an argument. this is because as you will see when we implement the controller class, the request body is automatically mapped to a Contact
object, but the fields that are not to be updated are null in this object. As a consequence, we need to get the already stored Contact
, and merge the new information with the one already stored, before saving it.
Creating the Controller Class
A controller is the entry point from where all the HTTP request are handled. These components are easily identified by the @RestController
annotation.
package com.canchitodev.example.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; 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.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.canchitodev.example.domain.Contact; import com.canchitodev.example.service.ContactService; @RestController @RequestMapping("/contact") public class ContactController { @Autowired private ContactService contactService; @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<List<Contact>> findAll() { List<Contact> contacts = this.contactService.findAll(); return new ResponseEntity<List<Contact>>(contacts, HttpStatus.OK); } @RequestMapping(value="/{contactId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Contact> findOne(@PathVariable Long contactId) { Contact contact = this.contactService.findById(contactId); return new ResponseEntity<Contact>(contact, HttpStatus.OK); } @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Contact> create(@RequestBody Contact contact) { this.contactService.save(contact); return new ResponseEntity<Contact>(contact, HttpStatus.CREATED); } @RequestMapping(value="/{contactId}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Contact> update(@PathVariable Long contactId, @RequestBody Contact contact) { contact.setUuid(contactId); this.contactService.update(contact); return new ResponseEntity<Contact>(contact, HttpStatus.OK); } @SuppressWarnings("rawtypes") @RequestMapping(value="/{contactId}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) public @ResponseBody HttpEntity delete(@PathVariable Long contactId) { this.contactService.delete(contactId); return new ResponseEntity(HttpStatus.NO_CONTENT); } }
The @RequestMapping
annotation specifies the URL which the method is mapped to. For instance, a request to http://localhost:8080/contact/
with a request method GET, will return all the Contact objects stored in the database.
Exception Handling
There are at least three methods for handling exceptions:
- Using HTTP status codes
- Using a controller based exception handler
- And finally, using a global exception handler
We are only going to focus on the third option, the global exception handler, as it applies to all the controllers. Any class annotated with @ControllerAdvice
becomes a controller-advice.
First we need to create a class called ErrorInformation
. Its solo purpose is to return the information about the error that was caught.
package com.canchitodev.example.exception; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; public class ErrorInformation { private String message; private String exception; public ErrorInformation(String message, Exception ex) { this.message = message; if (ex != null) { this.exception = ex.getLocalizedMessage(); } } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public void setException(String exception) { this.exception = exception; } @JsonInclude(Include.NON_NULL) public String getException() { return exception; } }
Here is our global exception handler controller class. Notice the class is annotated with @ControllerAdvice
annotation. Also methods are annotated with @ExceptionHandler
annotation. Note that we also created the exception classes that are detected by the exception handler.
package com.canchitodev.example.exception; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; @ControllerAdvice public class GlobalExceptionHandler { @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) // 415 @ExceptionHandler(ContentNotSupportedException.class) @ResponseBody public ErrorInformation handleNotSupported(ContentNotSupportedException e) { return new ErrorInformation("Content is not supported", e); } @ResponseStatus(HttpStatus.CONFLICT) // 409 @ExceptionHandler(ConflictException.class) @ResponseBody public ErrorInformation handleConflict(ConflictException e) { return new ErrorInformation("Conflict", e); } @ResponseStatus(HttpStatus.NOT_FOUND) // 404 @ExceptionHandler(ObjectNotFoundException.class) @ResponseBody public ErrorInformation handleNotFound(ObjectNotFoundException e) { return new ErrorInformation("Not found", e); } @ResponseStatus(HttpStatus.FORBIDDEN) // 403 @ExceptionHandler(ForbiddenException.class) @ResponseBody public ErrorInformation handleForbidden(ForbiddenException e) { return new ErrorInformation("Forbidden", e); } @ResponseStatus(HttpStatus.BAD_REQUEST) // 400 @ExceptionHandler(IllegalArgumentException.class) @ResponseBody public ErrorInformation handleIllegal(IllegalArgumentException e) { return new ErrorInformation("Bad request", e); } @ResponseStatus(HttpStatus.BAD_REQUEST) // 400 @ExceptionHandler(HttpMessageConversionException.class) @ResponseBody public ErrorInformation handleBadMessageConversion(HttpMessageConversionException e) { return new ErrorInformation("Bad request", e); } // Fall back @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 500 @ExceptionHandler(Exception.class) @ResponseBody public ErrorInformation handleOtherException(Exception e) { return new ErrorInformation("Internal server error", e); } }
Finally, we can modify the service class to throw exceptions when necessary, as these exceptions will be caught be our global exception handler and return the respective status code and message body.
Summary
In this guide, you will learned the following:
- How to set up and build a simple REST API with Spring.
- CRUD operations for entries that are saved into a database.
- How to map HTTP request to specific URL and its response codes.
- How to handle unmapped requests.
Hope you did enjoy it. And please, if you have any question, doubt or comment, do not hesitate and write us.
[…] Build a REST API with Spring […]
[…] Build a REST API with Spring […]
[…] Build a REST API with Spring […]