Skip to content
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.
Java
Branch: master
Clone or download
Latest commit 5edcf58 May 6, 2017
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src Add files via upload Apr 29, 2017
.gitignore Initial commit Apr 29, 2017
LICENSE Initial commit Apr 29, 2017
README.md Update README.md May 6, 2017
pom.xml Add files via upload Apr 29, 2017

README.md

build-a-rest-api-with-spring

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 these simple steps:

  1. Fork it
  2. Create your feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create new Pull Request

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:

  1. Add the needed dependencies.
  2. Create the entity that contains the information of a single contact entry.
  3. Create the repository interface with methods supporting reading, updating, deleting and creating contacts against a H2 database.
  4. Create the service layer that is responsible of mapping contacts into domain objects and vice versa.
  5. 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:

  1. Using HTTP status codes
  2. Using a controller based exception handler
  3. 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.

You can’t perform that action at this time.